Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/toolkit/mozapps/installer/windows/nsis/   (Firefox Browser Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 244 kB image not shown  

Quelle  common.nsh   Sprache: unbekannt

 
Spracherkennung für: .nsh vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.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 defines and macros for toolkit applications

/**
 * Avoid creating macros / functions that overwrite registers (see the
 * GetLongPath macro for one way to avoid this)!
 *
 * Before using the registers exchange the passed in params and save existing
 * register values to the stack.
 *
 * Exch $R9 ; exhange the original $R9 with the top of the stack
 * Exch 1   ; exchange the top of the stack with 1 below the top of the stack
 * Exch $R8 ; exchange the original $R8 with the top of the stack
 * Exch 2   ; exchange the top of the stack with 2 below the top of the stack
 * Exch $R7 ; exchange the original $R7 with the top of the stack
 * Push $R6 ; push the original $R6 onto the top of the stack
 * Push $R5 ; push the original $R5 onto the top of the stack
 * Push $R4 ; push the original $R4 onto the top of the stack
 *
 * <do stuff>
 *
 * ; Restore the values.
 * Pop $R4  ; restore the value for $R4 from the top of the stack
 * Pop $R5  ; restore the value for $R5 from the top of the stack
 * Pop $R6  ; restore the value for $R6 from the top of the stack
 * Exch $R7 ; exchange the new $R7 value with the top of the stack
 * Exch 2   ; exchange the top of the stack with 2 below the top of the stack
 * Exch $R8 ; exchange the new $R8 value with the top of the stack
 * Exch 1   ; exchange the top of the stack with 2 below the top of the stack
 * Exch $R9 ; exchange the new $R9 value with the top of the stack
 *
 *
 * When inserting macros in common.nsh from another macro in common.nsh that
 * can be used from the uninstaller _MOZFUNC_UN will be undefined when it is
 * inserted. Use the following to redefine _MOZFUNC_UN with its original value
 * (see the RegCleanMain macro for an example).
 *
 * !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
 * !insertmacro ${_MOZFUNC_UN_TMP}FileJoin
 * !insertmacro ${_MOZFUNC_UN_TMP}LineFind
 * !insertmacro ${_MOZFUNC_UN_TMP}TextCompareNoDetails
 * !insertmacro ${_MOZFUNC_UN_TMP}TrimNewLines
 * !undef _MOZFUNC_UN
 * !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
 * !undef _MOZFUNC_UN_TMP
 */

; When including a file provided by NSIS check if its verbose macro is defined
; to prevent loading the file a second time.
!ifmacrondef TEXTFUNC_VERBOSE
  !include TextFunc.nsh
!endif

!ifmacrondef FILEFUNC_VERBOSE
  !include FileFunc.nsh
!endif

!ifmacrondef LOGICLIB_VERBOSITY
  !include LogicLib.nsh
!endif

!ifndef WINMESSAGES_INCLUDED
  !include WinMessages.nsh
!endif

; When including WinVer.nsh check if ___WINVER__NSH___ is defined to prevent
; loading the file a second time.
!ifndef ___WINVER__NSH___
  !include WinVer.nsh
!endif

; When including x64.nsh check if ___X64__NSH___ is defined to prevent
; loading the file a second time.
!ifndef ___X64__NSH___
  !include x64.nsh
!endif

; NSIS provided macros that we have overridden.
!include overrides.nsh

!define SHORTCUTS_LOG "shortcuts_log.ini"
!define TO_BE_DELETED "tobedeleted"

; !define SHCNF_DWORD     0x0003
; !define SHCNF_FLUSH     0x1000
!ifndef SHCNF_DWORDFLUSH
  !define SHCNF_DWORDFLUSH 0x1003
!endif
!ifndef SHCNE_ASSOCCHANGED
  !define SHCNE_ASSOCCHANGED 0x08000000
!endif

################################################################################
# Macros for debugging

/**
 * The following two macros assist with verifying that a macro doesn't
 * overwrite any registers.
 *
 * Usage:
 * ${debugSetRegisters}
 * <do stuff>
 * ${debugDisplayRegisters}
 */

/**
 * Sets all register values to their name to assist with verifying that a macro
 * doesn't overwrite any registers.
 */
!macro debugSetRegisters
  StrCpy $0 "$$0"
  StrCpy $1 "$$1"
  StrCpy $2 "$$2"
  StrCpy $3 "$$3"
  StrCpy $4 "$$4"
  StrCpy $5 "$$5"
  StrCpy $6 "$$6"
  StrCpy $7 "$$7"
  StrCpy $8 "$$8"
  StrCpy $9 "$$9"
  StrCpy $R0 "$$R0"
  StrCpy $R1 "$$R1"
  StrCpy $R2 "$$R2"
  StrCpy $R3 "$$R3"
  StrCpy $R4 "$$R4"
  StrCpy $R5 "$$R5"
  StrCpy $R6 "$$R6"
  StrCpy $R7 "$$R7"
  StrCpy $R8 "$$R8"
  StrCpy $R9 "$$R9"
!macroend
!define debugSetRegisters "!insertmacro debugSetRegisters"

/**
 * Displays all register values to assist with verifying that a macro doesn't
 * overwrite any registers.
 */
!macro debugDisplayRegisters
  MessageBox MB_OK \
      "Register Values:$\n\
       $$0 = $0$\n$$1 = $1$\n$$2 = $2$\n$$3 = $3$\n$$4 = $4$\n\
       $$5 = $5$\n$$6 = $6$\n$$7 = $7$\n$$8 = $8$\n$$9 = $9$\n\
       $$R0 = $R0$\n$$R1 = $R1$\n$$R2 = $R2$\n$$R3 = $R3$\n$$R4 = $R4$\n\
       $$R5 = $R5$\n$$R6 = $R6$\n$$R7 = $R7$\n$$R8 = $R8$\n$$R9 = $R9"
!macroend
!define debugDisplayRegisters "!insertmacro debugDisplayRegisters"


################################################################################
# Modern User Interface (MUI) override macros

; Removed macros in nsis 2.33u (ported from nsis 2.22)
;  MUI_LANGUAGEFILE_DEFINE
;  MUI_LANGUAGEFILE_LANGSTRING_PAGE
;  MUI_LANGUAGEFILE_MULTILANGSTRING_PAGE
;  MUI_LANGUAGEFILE_LANGSTRING_DEFINE
;  MUI_LANGUAGEFILE_UNLANGSTRING_PAGE

!macro MOZ_MUI_LANGUAGEFILE_DEFINE DEFINE NAME

  !ifndef "${DEFINE}"
    !define "${DEFINE}" "${${NAME}}"
  !endif
  !undef "${NAME}"

!macroend

!macro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE PAGE NAME

  !ifdef MUI_${PAGE}PAGE
    LangString "${NAME}" 0 "${${NAME}}"
    !undef "${NAME}"
  !else
    !undef "${NAME}"
  !endif

!macroend

!macro MOZ_MUI_LANGUAGEFILE_MULTILANGSTRING_PAGE PAGE NAME

  !ifdef MUI_${PAGE}PAGE | MUI_UN${PAGE}PAGE
    LangString "${NAME}" 0 "${${NAME}}"
    !undef "${NAME}"
  !else
    !undef "${NAME}"
  !endif

!macroend

!macro MOZ_MUI_LANGUAGEFILE_LANGSTRING_DEFINE DEFINE NAME

  !ifdef "${DEFINE}"
    LangString "${NAME}" 0 "${${NAME}}"
  !endif
  !undef "${NAME}"

!macroend

!macro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE PAGE NAME

  !ifdef MUI_UNINSTALLER
    !ifdef MUI_UN${PAGE}PAGE
      LangString "${NAME}" 0 "${${NAME}}"
      !undef "${NAME}"
    !else
      !undef "${NAME}"
    !endif
  !else
    !undef "${NAME}"
  !endif

!macroend

; Modified version of the following MUI macros to support Mozilla localization.
; MUI_LANGUAGE
; MUI_LANGUAGEFILE_BEGIN
; MOZ_MUI_LANGUAGEFILE_END
; See <NSIS App Dir>/Contrib/Modern UI/System.nsh for more information
!define MUI_INSTALLOPTIONS_READ "!insertmacro MUI_INSTALLOPTIONS_READ"

!macro MOZ_MUI_LANGUAGE LANGUAGE
  !verbose push
  !verbose ${MUI_VERBOSE}
  !include "${LANGUAGE}.nsh"
  !verbose pop
!macroend

!macro MOZ_MUI_LANGUAGEFILE_BEGIN LANGUAGE
  !insertmacro MUI_INSERT
  !ifndef "MUI_LANGUAGEFILE_${LANGUAGE}_USED"
    !define "MUI_LANGUAGEFILE_${LANGUAGE}_USED"
    LoadLanguageFile "${LANGUAGE}.nlf"
  !else
    !error "Modern UI language file ${LANGUAGE} included twice!"
  !endif
!macroend

; Custom version of MUI_LANGUAGEFILE_END. The macro to add the default MUI
; strings and the macros for several strings that are part of the NSIS MUI and
; not in our locale files have been commented out.
!macro MOZ_MUI_LANGUAGEFILE_END

#  !include "${NSISDIR}\Contrib\Modern UI\Language files\Default.nsh"
  !ifdef MUI_LANGUAGEFILE_DEFAULT_USED
    !undef MUI_LANGUAGEFILE_DEFAULT_USED
    !warning "${LANGUAGE} Modern UI language file version doesn't match. Using default English texts for missing strings."
  !endif

  !insertmacro MOZ_MUI_LANGUAGEFILE_DEFINE "MUI_${LANGUAGE}_LANGNAME" "MUI_LANGNAME"

  !ifndef MUI_LANGDLL_PUSHLIST
    !define MUI_LANGDLL_PUSHLIST "'${MUI_${LANGUAGE}_LANGNAME}' ${LANG_${LANGUAGE}} "
  !else
    !ifdef MUI_LANGDLL_PUSHLIST_TEMP
      !undef MUI_LANGDLL_PUSHLIST_TEMP
    !endif
    !define MUI_LANGDLL_PUSHLIST_TEMP "${MUI_LANGDLL_PUSHLIST}"
    !undef MUI_LANGDLL_PUSHLIST
    !define MUI_LANGDLL_PUSHLIST "'${MUI_${LANGUAGE}_LANGNAME}' ${LANG_${LANGUAGE}} ${MUI_LANGDLL_PUSHLIST_TEMP}"
  !endif

  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE WELCOME "MUI_TEXT_WELCOME_INFO_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE WELCOME "MUI_TEXT_WELCOME_INFO_TEXT"

!ifdef MUI_TEXT_LICENSE_TITLE
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE LICENSE "MUI_TEXT_LICENSE_TITLE"
!endif
!ifdef MUI_TEXT_LICENSE_SUBTITLE
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE LICENSE "MUI_TEXT_LICENSE_SUBTITLE"
!endif
!ifdef MUI_INNERTEXT_LICENSE_TOP
  !insertmacro MOZ_MUI_LANGUAGEFILE_MULTILANGSTRING_PAGE LICENSE "MUI_INNERTEXT_LICENSE_TOP"
!endif

#  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE LICENSE "MUI_INNERTEXT_LICENSE_BOTTOM"

!ifdef MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE LICENSE "MUI_INNERTEXT_LICENSE_BOTTOM_CHECKBOX"
!endif

!ifdef MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE LICENSE "MUI_INNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS"
!endif

  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE COMPONENTS "MUI_TEXT_COMPONENTS_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE COMPONENTS "MUI_TEXT_COMPONENTS_SUBTITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_MULTILANGSTRING_PAGE COMPONENTS "MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_MULTILANGSTRING_PAGE COMPONENTS "MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO"

  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE DIRECTORY "MUI_TEXT_DIRECTORY_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE DIRECTORY "MUI_TEXT_DIRECTORY_SUBTITLE"

  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE STARTMENU "MUI_TEXT_STARTMENU_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE STARTMENU "MUI_TEXT_STARTMENU_SUBTITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE STARTMENU "MUI_INNERTEXT_STARTMENU_TOP"
#  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE STARTMENU "MUI_INNERTEXT_STARTMENU_CHECKBOX"

  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE INSTFILES "MUI_TEXT_INSTALLING_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE INSTFILES "MUI_TEXT_INSTALLING_SUBTITLE"

  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE INSTFILES "MUI_TEXT_FINISH_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE INSTFILES "MUI_TEXT_FINISH_SUBTITLE"

  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE INSTFILES "MUI_TEXT_ABORT_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE INSTFILES "MUI_TEXT_ABORT_SUBTITLE"

  !insertmacro MOZ_MUI_LANGUAGEFILE_MULTILANGSTRING_PAGE FINISH "MUI_BUTTONTEXT_FINISH"
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE FINISH "MUI_TEXT_FINISH_INFO_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE FINISH "MUI_TEXT_FINISH_INFO_TEXT"
  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_PAGE FINISH "MUI_TEXT_FINISH_INFO_REBOOT"
  !insertmacro MOZ_MUI_LANGUAGEFILE_MULTILANGSTRING_PAGE FINISH "MUI_TEXT_FINISH_REBOOTNOW"
  !insertmacro MOZ_MUI_LANGUAGEFILE_MULTILANGSTRING_PAGE FINISH "MUI_TEXT_FINISH_REBOOTLATER"
#  !insertmacro MOZ_MUI_LANGUAGEFILE_MULTILANGSTRING_PAGE FINISH "MUI_TEXT_FINISH_RUN"
#  !insertmacro MOZ_MUI_LANGUAGEFILE_MULTILANGSTRING_PAGE FINISH "MUI_TEXT_FINISH_SHOWREADME"

; Support for using the existing MUI_TEXT_ABORTWARNING string
!ifdef MOZ_MUI_CUSTOM_ABORT
    LangString MOZ_MUI_TEXT_ABORTWARNING 0 "${MUI_TEXT_ABORTWARNING}"
!endif

  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_DEFINE MUI_ABORTWARNING "MUI_TEXT_ABORTWARNING"


  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE WELCOME "MUI_UNTEXT_WELCOME_INFO_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE WELCOME "MUI_UNTEXT_WELCOME_INFO_TEXT"

  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE CONFIRM "MUI_UNTEXT_CONFIRM_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE CONFIRM "MUI_UNTEXT_CONFIRM_SUBTITLE"

#  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE LICENSE "MUI_UNTEXT_LICENSE_TITLE"
#  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE LICENSE "MUI_UNTEXT_LICENSE_SUBTITLE"

#  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE LICENSE "MUI_UNINNERTEXT_LICENSE_BOTTOM"
#  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE LICENSE "MUI_UNINNERTEXT_LICENSE_BOTTOM_CHECKBOX"
#  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE LICENSE "MUI_UNINNERTEXT_LICENSE_BOTTOM_RADIOBUTTONS"

#  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE COMPONENTS "MUI_UNTEXT_COMPONENTS_TITLE"
#  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE COMPONENTS "MUI_UNTEXT_COMPONENTS_SUBTITLE"

#  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE DIRECTORY "MUI_UNTEXT_DIRECTORY_TITLE"
#  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE DIRECTORY  "MUI_UNTEXT_DIRECTORY_SUBTITLE"

  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE INSTFILES "MUI_UNTEXT_UNINSTALLING_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE INSTFILES "MUI_UNTEXT_UNINSTALLING_SUBTITLE"

  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE INSTFILES "MUI_UNTEXT_FINISH_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE INSTFILES "MUI_UNTEXT_FINISH_SUBTITLE"

  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE INSTFILES "MUI_UNTEXT_ABORT_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE INSTFILES "MUI_UNTEXT_ABORT_SUBTITLE"

  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE FINISH "MUI_UNTEXT_FINISH_INFO_TITLE"
  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE FINISH "MUI_UNTEXT_FINISH_INFO_TEXT"
  !insertmacro MOZ_MUI_LANGUAGEFILE_UNLANGSTRING_PAGE FINISH "MUI_UNTEXT_FINISH_INFO_REBOOT"

  !insertmacro MOZ_MUI_LANGUAGEFILE_LANGSTRING_DEFINE MUI_UNABORTWARNING "MUI_UNTEXT_ABORTWARNING"

  !ifndef MUI_LANGDLL_LANGUAGES
    !define MUI_LANGDLL_LANGUAGES "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' "
    !define MUI_LANGDLL_LANGUAGES_CP "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' '${LANG_${LANGUAGE}_CP}' "
  !else
    !ifdef MUI_LANGDLL_LANGUAGES_TEMP
      !undef MUI_LANGDLL_LANGUAGES_TEMP
    !endif
    !define MUI_LANGDLL_LANGUAGES_TEMP "${MUI_LANGDLL_LANGUAGES}"
    !undef MUI_LANGDLL_LANGUAGES

    !ifdef MUI_LANGDLL_LANGUAGES_CP_TEMP
      !undef MUI_LANGDLL_LANGUAGES_CP_TEMP
    !endif
    !define MUI_LANGDLL_LANGUAGES_CP_TEMP "${MUI_LANGDLL_LANGUAGES_CP}"
    !undef MUI_LANGDLL_LANGUAGES_CP

    !define MUI_LANGDLL_LANGUAGES "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' ${MUI_LANGDLL_LANGUAGES_TEMP}"
    !define MUI_LANGDLL_LANGUAGES_CP "'${LANGFILE_${LANGUAGE}_NAME}' '${LANG_${LANGUAGE}}' '${LANG_${LANGUAGE}_CP}' ${MUI_LANGDLL_LANGUAGES_CP_TEMP}"
  !endif

!macroend

/**
 * Creates an InstallOptions file with a UTF-16LE BOM and adds the RTL value
 * to the Settings section.
 *
 * @param   _FILE
 *          The name of the file to be created in $PLUGINSDIR.
 */
!macro InitInstallOptionsFile _FILE
  Push $R9

  FileOpen $R9 "$PLUGINSDIR\${_FILE}" w
  FileWriteWord $R9 "65279"
  FileClose $R9
  WriteIniStr "$PLUGINSDIR\${_FILE}" "Settings" "RTL" "$(^RTL)"

  Pop $R9
!macroend


################################################################################
# Macros for handling files in use

/**
 * Checks for files in use in the $INSTDIR directory. To check files in
 * sub-directories this macro would need to be rewritten to create
 * sub-directories in the temporary directory used to backup the files that are
 * checked.
 *
 * Example usage:
 *
 *  ; The first string to be pushed onto the stack MUST be "end" to indicate
 *  ; that there are no more files in the $INSTDIR directory to check.
 *  Push "end"
 *  Push "freebl3.dll"
 *  ; The last file pushed should be the app's main exe so if it is in use this
 *  ; macro will return after the first check.
 *  Push "${FileMainEXE}"
 *  ${CheckForFilesInUse} $R9
 *
 * !IMPORTANT - this macro uses the $R7, $R8, and $R9 registers and makes no
 *              attempt to restore their original values.
 *
 * @return  _RESULT
 *          false if all of the files popped from the stack are not in use.
 *          True if any of the files popped from the stack are in use.
 * $R7 = Temporary backup directory where the files will be copied to.
 * $R8 = value popped from the stack. This will either be a file name for a file
 *       in the $INSTDIR directory or "end" to indicate that there are no
 *       additional files to check.
 * $R9 = _RESULT
 */
!macro CheckForFilesInUse

  !ifndef ${_MOZFUNC_UN}CheckForFilesInUse
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}CheckForFilesInUse "!insertmacro ${_MOZFUNC_UN}CheckForFilesInUseCall"

    Function ${_MOZFUNC_UN}CheckForFilesInUse
      ; Create a temporary backup directory.
      GetTempFileName $R7 "$INSTDIR"
      Delete "$R7"
      SetOutPath "$R7"
      StrCpy $R9 "false"

      Pop $R8
      ${While} $R8 != "end"
        ${Unless} ${FileExists} "$INSTDIR\$R8"
          Pop $R8 ; get next file to check before continuing
          ${Continue}
        ${EndUnless}

        ClearErrors
        CopyFiles /SILENT "$INSTDIR\$R8" "$R7\$R8" ; try to copy
        ${If} ${Errors}
          ; File is in use
          StrCpy $R9 "true"
          ${Break}
        ${EndIf}

        Delete "$INSTDIR\$R8" ; delete original
        ${If} ${Errors}
          ; File is in use
          StrCpy $R9 "true"
          Delete "$R7\$R8" ; delete temp copy
          ${Break}
        ${EndIf}

        Pop $R8 ; get next file to check
      ${EndWhile}

      ; clear stack
      ${While} $R8 != "end"
        Pop $R8
      ${EndWhile}

      ; restore everything
      SetOutPath "$INSTDIR"
      CopyFiles /SILENT "$R7\*" "$INSTDIR\"
      RmDir /r "$R7"
      SetOutPath "$EXEDIR"
      ClearErrors

      Push $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro CheckForFilesInUseCall _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call CheckForFilesInUse
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.CheckForFilesInUseCall _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.CheckForFilesInUse
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.CheckForFilesInUse
  !ifndef un.CheckForFilesInUse
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro CheckForFilesInUse

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

!macro GetCommonDirectory

  !ifndef ${_MOZFUNC_UN}GetCommonDirectory
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}GetCommonDirectory "!insertmacro ${_MOZFUNC_UN}GetCommonDirectoryCall"

    Function ${_MOZFUNC_UN}GetCommonDirectory
      Push $0   ; Save $0

      ; This gets C:\ProgramData or the equivalent.
      ${GetCommonAppDataFolder} $0

      ; Add our subdirectory, this is hardcoded as grandparent of the update directory in
      ; several other places.
      StrCpy $0 "$0\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38"

      Exch $0   ; Restore original $0 and put our $0 on the stack.
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro GetCommonDirectoryCall _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call GetCommonDirectory
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.GetCommonDirectoryCall _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.GetCommonDirectory
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.GetCommonDirectory
  !ifndef un.GetCommonDirectory
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro GetCommonDirectory

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * The macros below will automatically prepend un. to the function names when
 * they are defined (e.g. !define un.RegCleanMain).
 */
!verbose push
!verbose 3
!ifndef _MOZFUNC_VERBOSE
  !define _MOZFUNC_VERBOSE 3
!endif
!verbose ${_MOZFUNC_VERBOSE}
!define MOZFUNC_VERBOSE "!insertmacro MOZFUNC_VERBOSE"
!define _MOZFUNC_UN
!define _MOZFUNC_S
!verbose pop

!macro MOZFUNC_VERBOSE _VERBOSE
  !verbose push
  !verbose 3
  !undef _MOZFUNC_VERBOSE
  !define _MOZFUNC_VERBOSE ${_VERBOSE}
  !verbose pop
!macroend

/**
 * Displays a MessageBox and then calls abort to prevent continuing to the
 * next page when the specified Window Class is found.
 *
 * @param   _WINDOW_CLASS
 *          The Window Class to search for with FindWindow.
 * @param   _MSG
 *          The message text to display in the message box.
 *
 * $R7 = return value from FindWindow
 * $R8 = _WINDOW_CLASS
 * $R9 = _MSG
 */
!macro ManualCloseAppPrompt

  !ifndef ${_MOZFUNC_UN}ManualCloseAppPrompt
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}ManualCloseAppPrompt "!insertmacro ${_MOZFUNC_UN}ManualCloseAppPromptCall"

    Function ${_MOZFUNC_UN}ManualCloseAppPrompt
      Exch $R9
      Exch 1
      Exch $R8
      Push $R7

      FindWindow $R7 "$R8"
      ${If} $R7 <> 0 ; integer comparison
        MessageBox MB_OK|MB_ICONQUESTION "$R9"
        Abort
      ${EndIf}

      Pop $R7
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro ManualCloseAppPromptCall _WINDOW_CLASS _MSG
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_WINDOW_CLASS}"
  Push "${_MSG}"
  Call ManualCloseAppPrompt
  !verbose pop
!macroend

!macro un.ManualCloseAppPromptCall _WINDOW_CLASS _MSG
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_WINDOW_CLASS}"
  Push "${_MSG}"
  Call un.ManualCloseAppPrompt
  !verbose pop
!macroend

!macro un.ManualCloseAppPrompt
  !ifndef un.ManualCloseAppPrompt
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro ManualCloseAppPrompt

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend


################################################################################
# Macros for working with the registry

/**
 * Writes a registry string using SHCTX and the supplied params and logs the
 * action to the install log and the uninstall log if _LOG_UNINSTALL equals 1.
 *
 * Define NO_LOG to prevent all logging when calling this from the uninstaller.
 *
 * @param   _ROOT
 *          The registry key root as defined by NSIS (e.g. HKLM, HKCU, etc.).
 *          This will only be used for logging.
 * @param   _KEY
 *          The subkey in relation to the key root.
 * @param   _NAME
 *          The key value name to write to.
 * @param   _STR
 *          The string to write to the key value name.
 * @param   _LOG_UNINSTALL
 *          0 = don't add to uninstall log, 1 = add to uninstall log.
 *
 * $R5 = _ROOT
 * $R6 = _KEY
 * $R7 = _NAME
 * $R8 = _STR
 * $R9 = _LOG_UNINSTALL
 */
!macro WriteRegStr2

  !ifndef ${_MOZFUNC_UN}WriteRegStr2
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}WriteRegStr2 "!insertmacro ${_MOZFUNC_UN}WriteRegStr2Call"

    Function ${_MOZFUNC_UN}WriteRegStr2
      Exch $R9
      Exch 1
      Exch $R8
      Exch 2
      Exch $R7
      Exch 3
      Exch $R6
      Exch 4
      Exch $R5

      ClearErrors
      WriteRegStr SHCTX "$R6" "$R7" "$R8"

      !ifndef NO_LOG
        ${If} ${Errors}
          ${LogMsg} "** ERROR Adding Registry String: $R5 | $R6 | $R7 | $R8 **"
        ${Else}
          ${If} $R9 == 1 ; add to the uninstall log?
            ${LogUninstall} "RegVal: $R5 | $R6 | $R7"
          ${EndIf}
          ${LogMsg} "Added Registry String: $R5 | $R6 | $R7 | $R8"
        ${EndIf}
      !endif

      Exch $R5
      Exch 4
      Exch $R6
      Exch 3
      Exch $R7
      Exch 2
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro WriteRegStr2Call _ROOT _KEY _NAME _STR _LOG_UNINSTALL
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_ROOT}"
  Push "${_KEY}"
  Push "${_NAME}"
  Push "${_STR}"
  Push "${_LOG_UNINSTALL}"
  Call WriteRegStr2
  !verbose pop
!macroend

!macro un.WriteRegStr2Call _ROOT _KEY _NAME _STR _LOG_UNINSTALL
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_ROOT}"
  Push "${_KEY}"
  Push "${_NAME}"
  Push "${_STR}"
  Push "${_LOG_UNINSTALL}"
  Call un.WriteRegStr2
  !verbose pop
!macroend

!macro un.WriteRegStr2
  !ifndef un.WriteRegStr2
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro WriteRegStr2

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Writes a registry dword using SHCTX and the supplied params and logs the
 * action to the install log and the uninstall log if _LOG_UNINSTALL equals 1.
 *
 * Define NO_LOG to prevent all logging when calling this from the uninstaller.
 *
 * @param   _ROOT
 *          The registry key root as defined by NSIS (e.g. HKLM, HKCU, etc.).
 *          This will only be used for logging.
 * @param   _KEY
 *          The subkey in relation to the key root.
 * @param   _NAME
 *          The key value name to write to.
 * @param   _DWORD
 *          The dword to write to the key value name.
 * @param   _LOG_UNINSTALL
 *          0 = don't add to uninstall log, 1 = add to uninstall log.
 *
 * $R5 = _ROOT
 * $R6 = _KEY
 * $R7 = _NAME
 * $R8 = _DWORD
 * $R9 = _LOG_UNINSTALL
 */
!macro WriteRegDWORD2

  !ifndef ${_MOZFUNC_UN}WriteRegDWORD2
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}WriteRegDWORD2 "!insertmacro ${_MOZFUNC_UN}WriteRegDWORD2Call"

    Function ${_MOZFUNC_UN}WriteRegDWORD2
      Exch $R9
      Exch 1
      Exch $R8
      Exch 2
      Exch $R7
      Exch 3
      Exch $R6
      Exch 4
      Exch $R5

      ClearErrors
      WriteRegDWORD SHCTX "$R6" "$R7" "$R8"

      !ifndef NO_LOG
        ${If} ${Errors}
          ${LogMsg} "** ERROR Adding Registry DWord: $R5 | $R6 | $R7 | $R8 **"
        ${Else}
          ${If} $R9 == 1 ; add to the uninstall log?
            ${LogUninstall} "RegVal: $R5 | $R6 | $R7"
          ${EndIf}
          ${LogMsg} "Added Registry DWord: $R5 | $R6 | $R7 | $R8"
        ${EndIf}
      !endif

      Exch $R5
      Exch 4
      Exch $R6
      Exch 3
      Exch $R7
      Exch 2
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro WriteRegDWORD2Call _ROOT _KEY _NAME _DWORD _LOG_UNINSTALL
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_ROOT}"
  Push "${_KEY}"
  Push "${_NAME}"
  Push "${_DWORD}"
  Push "${_LOG_UNINSTALL}"
  Call WriteRegDWORD2
  !verbose pop
!macroend

!macro un.WriteRegDWORD2Call _ROOT _KEY _NAME _DWORD _LOG_UNINSTALL
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_ROOT}"
  Push "${_KEY}"
  Push "${_NAME}"
  Push "${_DWORD}"
  Push "${_LOG_UNINSTALL}"
  Call un.WriteRegDWORD2
  !verbose pop
!macroend

!macro un.WriteRegDWORD2
  !ifndef un.WriteRegDWORD2
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro WriteRegDWORD2

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Writes a registry string to HKCR using the supplied params and logs the
 * action to the install log and the uninstall log if _LOG_UNINSTALL equals 1.
 *
 * Define NO_LOG to prevent all logging when calling this from the uninstaller.
 *
 * @param   _ROOT
 *          The registry key root as defined by NSIS (e.g. HKLM, HKCU, etc.).
 *          This will only be used for logging.
 * @param   _KEY
 *          The subkey in relation to the key root.
 * @param   _NAME
 *          The key value name to write to.
 * @param   _STR
 *          The string to write to the key value name.
 * @param   _LOG_UNINSTALL
 *          0 = don't add to uninstall log, 1 = add to uninstall log.
 *
 * $R5 = _ROOT
 * $R6 = _KEY
 * $R7 = _NAME
 * $R8 = _STR
 * $R9 = _LOG_UNINSTALL
 */
!macro WriteRegStrHKCR

  !ifndef ${_MOZFUNC_UN}WriteRegStrHKCR
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}WriteRegStrHKCR "!insertmacro ${_MOZFUNC_UN}WriteRegStrHKCRCall"

    Function ${_MOZFUNC_UN}WriteRegStrHKCR
      Exch $R9
      Exch 1
      Exch $R8
      Exch 2
      Exch $R7
      Exch 3
      Exch $R6
      Exch 4
      Exch $R5

      ClearErrors
      WriteRegStr HKCR "$R6" "$R7" "$R8"

      !ifndef NO_LOG
        ${If} ${Errors}
          ${LogMsg} "** ERROR Adding Registry String: $R5 | $R6 | $R7 | $R8 **"
        ${Else}
          ${If} $R9 == 1 ; add to the uninstall log?
            ${LogUninstall} "RegVal: $R5 | $R6 | $R7"
          ${EndIf}
          ${LogMsg} "Added Registry String: $R5 | $R6 | $R7 | $R8"
        ${EndIf}
      !endif

      Exch $R5
      Exch 4
      Exch $R6
      Exch 3
      Exch $R7
      Exch 2
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro WriteRegStrHKCRCall _ROOT _KEY _NAME _STR _LOG_UNINSTALL
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_ROOT}"
  Push "${_KEY}"
  Push "${_NAME}"
  Push "${_STR}"
  Push "${_LOG_UNINSTALL}"
  Call WriteRegStrHKCR
  !verbose pop
!macroend

!macro un.WriteRegStrHKCRCall _ROOT _KEY _NAME _STR _LOG_UNINSTALL
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_ROOT}"
  Push "${_KEY}"
  Push "${_NAME}"
  Push "${_STR}"
  Push "${_LOG_UNINSTALL}"
  Call un.WriteRegStrHKCR
  !verbose pop
!macroend

!macro un.WriteRegStrHKCR
  !ifndef un.WriteRegStrHKCR
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro WriteRegStrHKCR

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

!ifndef KEY_SET_VALUE
  !define KEY_SET_VALUE 0x0002
!endif
!ifndef KEY_WOW64_64KEY
  !define KEY_WOW64_64KEY 0x0100
!endif
!ifndef HAVE_64BIT_BUILD
  !define CREATE_KEY_SAM ${KEY_SET_VALUE}
!else
  !define CREATE_KEY_SAM ${KEY_SET_VALUE}|${KEY_WOW64_64KEY}
!endif

/**
 * Creates a registry key. This will log the actions to the install and
 * uninstall logs. Alternatively you can set a registry value to create the key
 * and then delete the value.
 *
 * Define NO_LOG to prevent all logging when calling this from the uninstaller.
 *
 * @param   _ROOT
 *          The registry key root as defined by NSIS (e.g. HKLM, HKCU, etc.).
 * @param   _KEY
 *          The subkey in relation to the key root.
 * @param   _LOG_UNINSTALL
 *          0 = don't add to uninstall log, 1 = add to uninstall log.
 *
 * $R4 = [out] handle to newly created registry key. If this is not a key
 *       located in one of the predefined registry keys this must be closed
 *       with RegCloseKey (this should not be needed unless someone decides to
 *       do something extremely squirrelly with NSIS).
 * $R5 = return value from RegCreateKeyExW (represented by R5 in the system call).
 * $R6 = [in] hKey passed to RegCreateKeyExW.
 * $R7 = _ROOT
 * $R8 = _KEY
 * $R9 = _LOG_UNINSTALL
 */
!macro CreateRegKey

  !ifndef ${_MOZFUNC_UN}CreateRegKey
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}CreateRegKey "!insertmacro ${_MOZFUNC_UN}CreateRegKeyCall"

    Function ${_MOZFUNC_UN}CreateRegKey
      Exch $R9
      Exch 1
      Exch $R8
      Exch 2
      Exch $R7
      Push $R6
      Push $R5
      Push $R4

      StrCmp $R7 "HKCR" +1 +2
      StrCpy $R6 "0x80000000"
      StrCmp $R7 "HKCU" +1 +2
      StrCpy $R6 "0x80000001"
      StrCmp $R7 "HKLM" +1 +2
      StrCpy $R6 "0x80000002"

      ; see definition of RegCreateKey
      System::Call "Advapi32::RegCreateKeyExW(i R6, w R8, i 0, i 0, i 0,\
                                              i ${CREATE_KEY_SAM}, i 0, *i .R4,\
                                              i 0) i .R5"

      !ifndef NO_LOG
        ; if $R5 is not 0 then there was an error creating the registry key.
        ${If} $R5 <> 0
          ${LogMsg} "** ERROR Adding Registry Key: $R7 | $R8 **"
        ${Else}
          ${If} $R9 == 1 ; add to the uninstall log?
            ${LogUninstall} "RegKey: $R7 | $R8"
          ${EndIf}
          ${LogMsg} "Added Registry Key: $R7 | $R8"
        ${EndIf}
      !endif

      StrCmp $R5 0 +1 +2
      System::Call "Advapi32::RegCloseKey(iR4)"

      Pop $R4
      Pop $R5
      Pop $R6
      Exch $R7
      Exch 2
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro CreateRegKeyCall _ROOT _KEY _LOG_UNINSTALL
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_ROOT}"
  Push "${_KEY}"
  Push "${_LOG_UNINSTALL}"
  Call CreateRegKey
  !verbose pop
!macroend

!macro un.CreateRegKeyCall _ROOT _KEY _LOG_UNINSTALL
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_ROOT}"
  Push "${_KEY}"
  Push "${_LOG_UNINSTALL}"
  Call un.CreateRegKey
  !verbose pop
!macroend

!macro un.CreateRegKey
  !ifndef un.CreateRegKey
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro CreateRegKey

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Helper for checking for the existence of a registry key.
 * SHCTX is the root key to search.
 *
 * @param   _MAIN_KEY
 *          Sub key to iterate for the key in question
 * @param   _KEY
 *          Key name to search for
 * @return  _RESULT
 *          'true' / 'false' result
 */
!macro CheckIfRegistryKeyExists
  !ifndef CheckIfRegistryKeyExists
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define CheckIfRegistryKeyExists "!insertmacro CheckIfRegistryKeyExistsCall"

    Function CheckIfRegistryKeyExists
      ; stack: main key, key
      Exch $R9 ; main key, stack: old R9, key
      Exch 1   ; stack: key, old R9
      Exch $R8 ; key, stack: old R8, old R9
      Push $R7
      Push $R6
      Push $R5

      StrCpy $R5 "false"
      StrCpy $R7 "0" # loop index
      ${Do}
        EnumRegKey $R6 SHCTX "$R9" "$R7"
        ${If} "$R6" == "$R8"
          StrCpy $R5 "true"
          ${Break}
        ${EndIf}
        IntOp $R7 $R7 + 1
      ${LoopWhile} $R6 != ""
      ClearErrors

      StrCpy $R9 $R5

      Pop $R5
      Pop $R6
      Pop $R7 ; stack: old R8, old R9
      Pop $R8 ; stack: old R9
      Exch $R9 ; stack: result
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro CheckIfRegistryKeyExistsCall _MAIN_KEY _KEY _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Push "${_MAIN_KEY}"
  Call CheckIfRegistryKeyExists
  Pop ${_RESULT}
  !verbose pop
!macroend

/**
 * Read the value of an installer pref that's been set by the product.
 *
 * @param   _KEY ($R1)
 *          Sub key containing all the installer prefs
 *          Usually "Software\Mozilla\${AppName}"
 * @param   _PREF ($R2)
 *          Name of the pref to look up
 * @return  _RESULT ($R3)
 *          'true' or 'false' (only boolean prefs are supported)
 *          If no value exists for the requested pref, the result is 'false'
 */
!macro GetInstallerRegistryPref
  !ifndef ${_MOZFUNC_UN}GetInstallerRegistryPref
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}GetInstallerRegistryPref "!insertmacro GetInstallerRegistryPrefCall"

    Function ${_MOZFUNC_UN}GetInstallerRegistryPref
      ; stack: key, pref
      Exch $R1 ; key, stack: old R1, pref
      Exch 1   ; stack: pref, old R1
      Exch $R2 ; pref, stack: old R2, old R1
      Push $R3

      StrCpy $R3 0

      ; These prefs are always stored in the native registry.
      SetRegView 64

      ClearErrors
      ReadRegDWORD $R3 HKCU "$R1\Installer\$AppUserModelID" "$R2"

      SetRegView lastused

      ${IfNot} ${Errors}
      ${AndIf} $R3 != 0
        StrCpy $R1 "true"
      ${Else}
        StrCpy $R1 "false"
      ${EndIf}

      ; stack: old R3, old R2, old R1
      Pop $R3 ; stack: old R2, old R1
      Pop $R2 ; stack: old R1
      Exch $R1 ; stack: result
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro GetInstallerRegistryPrefCall _KEY _PREF _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_PREF}"
  Push "${_KEY}"
  Call GetInstallerRegistryPref
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.GetInstallerRegistryPrefCall _KEY _PREF _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_PREF}"
  Push "${_KEY}"
  Call un.GetInstallerRegistryPref
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.GetInstallerRegistryPref
  !ifndef un.GetInstallerRegistryPref
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro GetInstallerRegistryPref

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

################################################################################
# Macros for adding file and protocol handlers

/**
 * Writes common registry values for a handler using SHCTX.
 *
 * @param   _KEY
 *          The subkey in relation to the key root.
 * @param   _VALOPEN
 *          The path and args to launch the application.
 * @param   _VALICON
 *          The path to the binary that contains the icon group for the default icon
 *          followed by a comma and either the icon group's resource index or the icon
 *          group's resource id prefixed with a minus sign
 * @param   _DISPNAME
 *          The display name for the handler. If emtpy no value will be set.
 * @param   _ISPROTOCOL
 *          Sets protocol handler specific registry values when "true".
 *          Deletes protocol handler specific registry values when "delete".
 *          Otherwise doesn't touch handler specific registry values.
 * @param   _ISDDE
 *          Sets DDE specific registry values when "true".
 *
 * $R3 = string value of the current registry key path.
 * $R4 = _KEY
 * $R5 = _VALOPEN
 * $R6 = _VALICON
 * $R7 = _DISPNAME
 * $R8 = _ISPROTOCOL
 * $R9 = _ISDDE
 */
!macro AddHandlerValues

  !ifndef ${_MOZFUNC_UN}AddHandlerValues
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}AddHandlerValues "!insertmacro ${_MOZFUNC_UN}AddHandlerValuesCall"

    Function ${_MOZFUNC_UN}AddHandlerValues
      Exch $R9
      Exch 1
      Exch $R8
      Exch 2
      Exch $R7
      Exch 3
      Exch $R6
      Exch 4
      Exch $R5
      Exch 5
      Exch $R4
      Push $R3

      StrCmp "$R7" "" +6 +1
      ReadRegStr $R3 SHCTX "$R4" "FriendlyTypeName"

      StrCmp "$R3" "" +1 +3
      WriteRegStr SHCTX "$R4" "" "$R7"
      WriteRegStr SHCTX "$R4" "FriendlyTypeName" "$R7"

      StrCmp "$R8" "true" +1 +2
      WriteRegStr SHCTX "$R4" "URL Protocol" ""
      StrCmp "$R8" "delete" +1 +2
      DeleteRegValue SHCTX "$R4" "URL Protocol"
      StrCpy $R3 ""
      ReadRegDWord $R3 SHCTX "$R4" "EditFlags"
      StrCmp $R3 "" +1 +3  ; Only add EditFlags if a value doesn't exist
      DeleteRegValue SHCTX "$R4" "EditFlags"
      WriteRegDWord SHCTX "$R4" "EditFlags" 0x00000002

      StrCmp "$R6" "" +2 +1
      WriteRegStr SHCTX "$R4\DefaultIcon" "" "$R6"

      StrCmp "$R5" "" +2 +1
      WriteRegStr SHCTX "$R4\shell\open\command" "" "$R5"

!ifdef DDEApplication
      StrCmp "$R9" "true" +1 +11
      WriteRegStr SHCTX "$R4\shell\open\ddeexec" "" "$\"%1$\",,0,0,,,,"
      WriteRegStr SHCTX "$R4\shell\open\ddeexec" "NoActivateHandler" ""
      WriteRegStr SHCTX "$R4\shell\open\ddeexec\Application" "" "${DDEApplication}"
      WriteRegStr SHCTX "$R4\shell\open\ddeexec\Topic" "" "WWW_OpenURL"
      ; The ifexec key may have been added by another application so try to
      ; delete it to prevent it from breaking this app's shell integration.
      ; Also, IE 6 and below doesn't remove this key when it sets itself as the
      ; default handler and if this key exists IE's shell integration breaks.
      DeleteRegKey HKLM "$R4\shell\open\ddeexec\ifexec"
      DeleteRegKey HKCU "$R4\shell\open\ddeexec\ifexec"
!endif

      ClearErrors

      Pop $R3
      Exch $R4
      Exch 5
      Exch $R5
      Exch 4
      Exch $R6
      Exch 3
      Exch $R7
      Exch 2
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro AddHandlerValuesCall _KEY _VALOPEN _VALICON _DISPNAME _ISPROTOCOL _ISDDE
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Push "${_VALOPEN}"
  Push "${_VALICON}"
  Push "${_DISPNAME}"
  Push "${_ISPROTOCOL}"
  Push "${_ISDDE}"
  Call AddHandlerValues
  !verbose pop
!macroend

!macro un.AddHandlerValuesCall _KEY _VALOPEN _VALICON _DISPNAME _ISPROTOCOL _ISDDE
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Push "${_VALOPEN}"
  Push "${_VALICON}"
  Push "${_DISPNAME}"
  Push "${_ISPROTOCOL}"
  Push "${_ISDDE}"
  Call un.AddHandlerValues
  !verbose pop
!macroend

!macro un.AddHandlerValues
  !ifndef un.AddHandlerValues
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro AddHandlerValues

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Writes common registry values for a handler that DOES NOT use DDE using SHCTX.
 *
 * @param   _KEY
 *          The key name in relation to the HKCR root. SOFTWARE\Classes is
 *          prefixed to this value when using SHCTX.
 * @param   _VALOPEN
 *          The path and args to launch the application.
 * @param   _VALICON
 *          The path to the binary that contains the icon group for the default icon
 *          followed by a comma and either the icon group's resource index or the icon
 *          group's resource id prefixed with a minus sign
 * @param   _DISPNAME
 *          The display name for the handler. If emtpy no value will be set.
 * @param   _ISPROTOCOL
 *          Sets protocol handler specific registry values when "true".
 *          Deletes protocol handler specific registry values when "delete".
 *          Otherwise doesn't touch handler specific registry values.
 *
 * $R3 = storage for SOFTWARE\Classes
 * $R4 = string value of the current registry key path.
 * $R5 = _KEY
 * $R6 = _VALOPEN
 * $R7 = _VALICON
 * $R8 = _DISPNAME
 * $R9 = _ISPROTOCOL
 */
!macro AddDisabledDDEHandlerValues

  !ifndef ${_MOZFUNC_UN}AddDisabledDDEHandlerValues
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}AddDisabledDDEHandlerValues "!insertmacro ${_MOZFUNC_UN}AddDisabledDDEHandlerValuesCall"

    Function ${_MOZFUNC_UN}AddDisabledDDEHandlerValues
      Exch $R9 ; _ISPROTOCOL
      Exch 1
      Exch $R8 ; FriendlyTypeName
      Exch 2
      Exch $R7 ; icon index
      Exch 3
      Exch $R6 ; shell\open\command
      Exch 4
      Exch $R5 ; reg key
      Push $R4 ;
      Push $R3 ; base reg class

      StrCpy $R3 "SOFTWARE\Classes"
      StrCmp "$R8" "" +6 +1
      ReadRegStr $R4 SHCTX "$R5" "FriendlyTypeName"

      StrCmp "$R4" "" +1 +3
      WriteRegStr SHCTX "$R3\$R5" "" "$R8"
      WriteRegStr SHCTX "$R3\$R5" "FriendlyTypeName" "$R8"

      StrCmp "$R9" "true" +1 +2
      WriteRegStr SHCTX "$R3\$R5" "URL Protocol" ""
      StrCmp "$R9" "delete" +1 +2
      DeleteRegValue SHCTX "$R3\$R5" "URL Protocol"
      StrCpy $R4 ""
      ReadRegDWord $R4 SHCTX "$R3\$R5" "EditFlags"
      StrCmp $R4 "" +1 +3  ; Only add EditFlags if a value doesn't exist
      DeleteRegValue SHCTX "$R3\$R5" "EditFlags"
      WriteRegDWord SHCTX "$R3\$R5" "EditFlags" 0x00000002

      StrCmp "$R7" "" +2 +1
      WriteRegStr SHCTX "$R3\$R5\DefaultIcon" "" "$R7"

      ; Main command handler for the app
      WriteRegStr SHCTX "$R3\$R5\shell" "" "open"
      WriteRegStr SHCTX "$R3\$R5\shell\open\command" "" "$R6"

      ; Drop support for DDE (bug 491947), and remove old dde entries if
      ; they exist.
      ;
      ; Note, changes in SHCTX should propegate to hkey classes root when
      ; current user or local machine entries are written. Windows will also
      ; attempt to propegate entries when a handler is used. CR entries are a
      ; combination of LM and CU, with CU taking priority.
      ;
      ; To disable dde, an empty shell/ddeexec key must be created in current
      ; user or local machine. Unfortunately, settings have various different
      ; behaviors depending on the windows version. The following code attempts
      ; to address these differences.
      ;
      ; IE does not configure ddeexec, so issues with left over ddeexec keys
      ; in LM are reduced. We configure an empty ddeexec key with an empty default
      ; string in CU to be sure.
      ;
      DeleteRegKey SHCTX "SOFTWARE\Classes\$R5\shell\open\ddeexec"
      WriteRegStr SHCTX "SOFTWARE\Classes\$R5\shell\open\ddeexec" "" ""

      ClearErrors

      Pop $R3
      Pop $R4
      Exch $R5
      Exch 4
      Exch $R6
      Exch 3
      Exch $R7
      Exch 2
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro AddDisabledDDEHandlerValuesCall _KEY _VALOPEN _VALICON _DISPNAME _ISPROTOCOL
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Push "${_VALOPEN}"
  Push "${_VALICON}"
  Push "${_DISPNAME}"
  Push "${_ISPROTOCOL}"
  Call AddDisabledDDEHandlerValues
  !verbose pop
!macroend

!macro un.AddDisabledDDEHandlerValuesCall _KEY _VALOPEN _VALICON _DISPNAME _ISPROTOCOL
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Push "${_VALOPEN}"
  Push "${_VALICON}"
  Push "${_DISPNAME}"
  Push "${_ISPROTOCOL}"
  Call un.AddDisabledDDEHandlerValues
  !verbose pop
!macroend

!macro un.AddDisabledDDEHandlerValues
  !ifndef un.AddDisabledDDEHandlerValues
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro AddDisabledDDEHandlerValues

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend


################################################################################
# Macros for handling DLL registration

!macro RegisterDLL DLL

  ; The x64 regsvr32.exe registers x86 DLL's properly so just use it
  ; when installing on an x64 systems even when installing an x86 application.
  ${If} ${RunningX64}
  ${OrIf} ${IsNativeARM64}
    ${DisableX64FSRedirection}
    ExecWait '"$SYSDIR\regsvr32.exe" /s "${DLL}"'
    ${EnableX64FSRedirection}
  ${Else}
    RegDLL "${DLL}"
  ${EndIf}

!macroend

!macro UnregisterDLL DLL

  ; The x64 regsvr32.exe registers x86 DLL's properly so just use it
  ; when installing on an x64 systems even when installing an x86 application.
  ${If} ${RunningX64}
  ${OrIf} ${IsNativeARM64}
    ${DisableX64FSRedirection}
    ExecWait '"$SYSDIR\regsvr32.exe" /s /u "${DLL}"'
    ${EnableX64FSRedirection}
  ${Else}
    UnRegDLL "${DLL}"
  ${EndIf}

!macroend

!define RegisterDLL "!insertmacro RegisterDLL"
!define UnregisterDLL "!insertmacro UnregisterDLL"


################################################################################
# Macros for retrieving special folders

/**
 * These macro get special folder paths directly, without depending on
 * SetShellVarContext.
 *
 * Usage:
 * ${GetProgramsFolder} $0
 * ${GetLocalAppDataFolder} $0
 * ${GetCommonAppDataFolder} $0
 *
 */
!macro GetSpecialFolder _ID _RESULT
  ; This system call gets the directory path. The arguments are:
  ;   A null ptr for hwnd
  ;   t.s puts the output string on the NSIS stack
  ;   id indicates which dir to get
  ;   false for fCreate (i.e. Do not create the folder if it doesn't exist)
  System::Call "Shell32::SHGetSpecialFolderPathW(p 0, t.s, i ${_ID}, i 0)"
  Pop ${_RESULT}
!macroend

!define CSIDL_PROGRAMS          0x0002
!define CSIDL_LOCAL_APPDATA     0x001c
!define CSIDL_COMMON_APPDATA    0x0023

; Current User's Start Menu Programs
!define GetProgramsFolder       "!insertmacro GetSpecialFolder ${CSIDL_PROGRAMS}"

; Current User's Local App Data (e.g. C:\Users\<user>\AppData\Local)
!define GetLocalAppDataFolder   "!insertmacro GetSpecialFolder ${CSIDL_LOCAL_APPDATA}"

; Common App Data (e.g. C:\ProgramData)
!define GetCommonAppDataFolder  "!insertmacro GetSpecialFolder ${CSIDL_COMMON_APPDATA}"

################################################################################
# Macros for retrieving existing install paths

/**
 * Finds a second installation of the application so we can make informed
 * decisions about registry operations. This uses SHCTX to determine the
 * registry hive so you must call SetShellVarContext first.
 *
 * @param   _KEY
 *          The registry subkey (typically this will be Software\Mozilla).
 * @return  _RESULT
 *          false if a second install isn't found, path to the main exe if a
 *          second install is found.
 *
 * $R3 = stores the long path to $INSTDIR
 * $R4 = counter for the outer loop's EnumRegKey
 * $R5 = return value from ReadRegStr and RemoveQuotesFromPath
 * $R6 = return value from GetParent
 * $R7 = return value from the loop's EnumRegKey
 * $R8 = storage for _KEY
 * $R9 = _KEY and _RESULT
 */
!macro GetSecondInstallPath

  !ifndef ${_MOZFUNC_UN}GetSecondInstallPath
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !insertmacro ${_MOZFUNC_UN_TMP}GetParent
    !insertmacro ${_MOZFUNC_UN_TMP}RemoveQuotesFromPath
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}GetSecondInstallPath "!insertmacro ${_MOZFUNC_UN}GetSecondInstallPathCall"

    Function ${_MOZFUNC_UN}GetSecondInstallPath
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4
      Push $R3

      ${${_MOZFUNC_UN}GetLongPath} "$INSTDIR" $R3

      StrCpy $R4 0       ; set the counter for the loop to 0
      StrCpy $R8 "$R9"   ; Registry key path to search
      StrCpy $R9 "false" ; default return value

      loop:
      EnumRegKey $R7 SHCTX $R8 $R4
      StrCmp $R7 "" end +1  ; if empty there are no more keys to enumerate
      IntOp $R4 $R4 + 1     ; increment the loop's counter
      ClearErrors
      ReadRegStr $R5 SHCTX "$R8\$R7\bin" "PathToExe"
      IfErrors loop

      ${${_MOZFUNC_UN}RemoveQuotesFromPath} "$R5" $R5

      IfFileExists "$R5" +1 loop
      ${${_MOZFUNC_UN}GetLongPath} "$R5" $R5
      ${${_MOZFUNC_UN}GetParent} "$R5" $R6
      StrCmp "$R6" "$R3" loop +1
      StrCmp "$R6\${FileMainEXE}" "$R5" +1 loop
      StrCpy $R9 "$R5"

      end:
      ClearErrors

      Pop $R3
      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro GetSecondInstallPathCall _KEY _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Call GetSecondInstallPath
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.GetSecondInstallPathCall _KEY _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Call un.GetSecondInstallPath
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.GetSecondInstallPath
  !ifndef un.GetSecondInstallPath
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro GetSecondInstallPath

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Finds an existing installation path for the application based on the
 * application's executable name so we can default to using this path for the
 * install. If there is zero or more than one installation of the application
 * then we default to the default installation path. This uses SHCTX to
 * determine the registry hive to read from so you must call SetShellVarContext
 * first.
 *
 * @param   _KEY
 *          The registry subkey (typically this will be Software\Mozilla\App Name).
 * @return  _RESULT
 *          false if a single install location for this app name isn't found,
 *          path to the install directory if a single install location is found.
 *
 * $R5 = counter for the loop's EnumRegKey
 * $R6 = return value from EnumRegKey
 * $R7 = return value from ReadRegStr
 * $R8 = storage for _KEY
 * $R9 = _KEY and _RESULT
 */
!macro GetSingleInstallPath

  !ifndef ${_MOZFUNC_UN}GetSingleInstallPath
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !insertmacro ${_MOZFUNC_UN_TMP}GetParent
    !insertmacro ${_MOZFUNC_UN_TMP}RemoveQuotesFromPath
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}GetSingleInstallPath "!insertmacro ${_MOZFUNC_UN}GetSingleInstallPathCall"

    Function ${_MOZFUNC_UN}GetSingleInstallPath
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5

      StrCpy $R8 $R9
      StrCpy $R9 "false"
      StrCpy $R5 0  ; set the counter for the loop to 0

      loop:
      ClearErrors
      EnumRegKey $R6 SHCTX $R8 $R5
      IfErrors cleanup
      StrCmp $R6 "" cleanup +1  ; if empty there are no more keys to enumerate
      IntOp $R5 $R5 + 1         ; increment the loop's counter
      ClearErrors
      ReadRegStr $R7 SHCTX "$R8\$R6\Main" "PathToExe"
      IfErrors loop
      ${${_MOZFUNC_UN}RemoveQuotesFromPath} "$R7" $R7
      GetFullPathName $R7 "$R7"
      IfErrors loop

      StrCmp "$R9" "false" +1 +3
      StrCpy $R9 "$R7"
      GoTo Loop

      StrCpy $R9 "false"

      cleanup:
      StrCmp $R9 "false" end +1
      ${${_MOZFUNC_UN}GetLongPath} "$R9" $R9
      ${${_MOZFUNC_UN}GetParent} "$R9" $R9

      end:
      ClearErrors

      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro GetSingleInstallPathCall _KEY _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Call GetSingleInstallPath
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.GetSingleInstallPathCall _KEY _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Call un.GetSingleInstallPath
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.GetSingleInstallPath
  !ifndef un.GetSingleInstallPath
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro GetSingleInstallPath

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Find the first existing installation for the application.
 * This is similar to GetSingleInstallPath, except that it always returns the
 * first path it finds, instead of an error when more than one path exists.
 *
 * The shell context and the registry view should already have been set.
 *
 * @param   _KEY
 *          The registry subkey (typically Software\Mozilla\App Name).
 * @return  _RESULT
 *          path to the install directory of the first location found, or
 *          the string "false" if no existing installation was found.
 *
 * $R5 = counter for the loop's EnumRegKey
 * $R6 = return value from EnumRegKey
 * $R7 = return value from ReadRegStr
 * $R8 = storage for _KEY
 * $R9 = _KEY and _RESULT
 */
!macro GetFirstInstallPath
  !ifndef ${_MOZFUNC_UN}GetFirstInstallPath
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !insertmacro ${_MOZFUNC_UN_TMP}GetParent
    !insertmacro ${_MOZFUNC_UN_TMP}RemoveQuotesFromPath
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}GetFirstInstallPath "!insertmacro ${_MOZFUNC_UN}__GetFirstInstallPathCall"

    Function ${_MOZFUNC_UN}__GetFirstInstallPath
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5

      StrCpy $R8 $R9
      StrCpy $R9 "false"
      StrCpy $R5 0

      ${Do}
        ClearErrors
        EnumRegKey $R6 SHCTX $R8 $R5
        ${If} ${Errors}
        ${OrIf} $R6 == ""
          ${Break}
        ${EndIf}

        IntOp $R5 $R5 + 1

        ReadRegStr $R7 SHCTX "$R8\$R6\Main" "PathToExe"
        ${If} ${Errors}
          ${Continue}
        ${EndIf}

        ${${_MOZFUNC_UN}RemoveQuotesFromPath} "$R7" $R7
        GetFullPathName $R7 "$R7"
        ${If} ${Errors}
          ${Continue}
        ${EndIf}

        StrCpy $R9 "$R7"
        ${Break}
      ${Loop}

      ${If} $R9 != "false"
        ${${_MOZFUNC_UN}GetLongPath} "$R9" $R9
        ${${_MOZFUNC_UN}GetParent} "$R9" $R9
      ${EndIf}

      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro __GetFirstInstallPathCall _KEY _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Call __GetFirstInstallPath
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.__GetFirstInstallPathCall _KEY _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Call un.__GetFirstInstallPath
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.__GetFirstInstallPath
  !ifndef un.__GetFirstInstallPath
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro __GetFirstInstallPath

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend


################################################################################
# Macros for working with the file system

/**
 * Attempts to delete a file if it exists. This will fail if the file is in use.
 *
 * @param   _FILE
 *          The path to the file that is to be deleted.
 */
!macro DeleteFile _FILE
  ${If} ${FileExists} "${_FILE}"
    Delete "${_FILE}"
  ${EndIf}
!macroend
!define DeleteFile "!insertmacro DeleteFile"

/**
 * Removes a directory if it exists and is empty.
 *
 * @param   _DIR
 *          The path to the directory that is to be removed.
 */
!macro RemoveDir _DIR
  ${If} ${FileExists} "${_DIR}"
    RmDir "${_DIR}"
  ${EndIf}
!macroend
!define RemoveDir "!insertmacro RemoveDir"

/**
 * Checks whether it is possible to create and delete a directory and a file in
 * the install directory. Creation and deletion of files and directories are
 * checked since a user may have rights for one and not the other. If creation
 * and deletion of a file and a directory are successful this macro will return
 * true... if not, this it return false.
 *
 * @return  _RESULT
 *          true if files and directories can be created and deleted in the
 *          install directory otherwise false.
 *
 * $R8 = temporary filename in the installation directory returned from
 *       GetTempFileName.
 * $R9 = _RESULT
 */
!macro CanWriteToInstallDir

  !ifndef ${_MOZFUNC_UN}CanWriteToInstallDir
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}CanWriteToInstallDir "!insertmacro ${_MOZFUNC_UN}CanWriteToInstallDirCall"

    Function ${_MOZFUNC_UN}CanWriteToInstallDir
      Push $R9
      Push $R8

      StrCpy $R9 "true"

      ; IfFileExists returns false for $INSTDIR when $INSTDIR is the root of a
      ; UNC path so always try to create $INSTDIR
      CreateDirectory "$INSTDIR\"
      GetTempFileName $R8 "$INSTDIR\"

      ${Unless} ${FileExists} $R8 ; Can files be created?
        StrCpy $R9 "false"
        Goto done
      ${EndUnless}

      Delete $R8
      ${If} ${FileExists} $R8 ; Can files be deleted?
        StrCpy $R9 "false"
        Goto done
      ${EndIf}

      CreateDirectory $R8
      ${Unless} ${FileExists} $R8  ; Can directories be created?
        StrCpy $R9 "false"
        Goto done
      ${EndUnless}

      RmDir $R8
      ${If} ${FileExists} $R8  ; Can directories be deleted?
        StrCpy $R9 "false"
        Goto done
      ${EndIf}

      done:

      RmDir "$INSTDIR\" ; Only remove $INSTDIR if it is empty
      ClearErrors

      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro CanWriteToInstallDirCall _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call CanWriteToInstallDir
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.CanWriteToInstallDirCall _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.CanWriteToInstallDir
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.CanWriteToInstallDir
  !ifndef un.CanWriteToInstallDir
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro CanWriteToInstallDir

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Checks whether there is sufficient free space available for the installation
 * directory using GetDiskFreeSpaceExW which respects disk quotas. This macro
 * will calculate the size of all sections that are selected, compare that with
 * the free space available, and if there is sufficient free space it will
 * return true... if not, it will return false.
 *
 * @return  _RESULT
 *          "true" if there is sufficient free space otherwise "false".
 *
 * $R5 = return value from SectionGetSize
 * $R6 = return value from SectionGetFlags
 *       return value from an 'and' comparison of SectionGetFlags (1=selected)
 *       return value for lpFreeBytesAvailable from GetDiskFreeSpaceExW
 *       return value for System::Int64Op $R6 / 1024
 *       return value for System::Int64Op $R6 > $R8
 * $R7 = the counter for enumerating the sections
 *       the temporary file name for the directory created under $INSTDIR passed
 *       to GetDiskFreeSpaceExW.
 * $R8 = sum in KB of all selected sections
 * $R9 = _RESULT
 */
!macro CheckDiskSpace

  !ifndef ${_MOZFUNC_UN}CheckDiskSpace
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}CheckDiskSpace "!insertmacro ${_MOZFUNC_UN}CheckDiskSpaceCall"

    Function ${_MOZFUNC_UN}CheckDiskSpace
      Push $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5

      ClearErrors

      StrCpy $R9 "true" ; default return value
      StrCpy $R8 "0"    ; sum in KB of all selected sections
      StrCpy $R7 "0"    ; counter for enumerating sections

      ; Enumerate the sections and sum up the sizes of the sections that are
      ; selected.
      SectionGetFlags $R7 $R6
      IfErrors +7 +1
      IntOp $R6 ${SF_SELECTED} & $R6
      IntCmp $R6 0 +3 +1 +1
      SectionGetSize $R7 $R5
      IntOp $R8 $R8 + $R5
      IntOp $R7 $R7 + 1
      GoTo -7

      ; The directory passed to GetDiskFreeSpaceExW must exist for the call to
      ; succeed.  Since the CanWriteToInstallDir macro is called prior to this
      ; macro the call to CreateDirectory will always succeed.

      ; IfFileExists returns false for $INSTDIR when $INSTDIR is the root of a
      ; UNC path so always try to create $INSTDIR
      CreateDirectory "$INSTDIR\"
      GetTempFileName $R7 "$INSTDIR\"
      Delete "$R7"
      CreateDirectory "$R7"

      System::Call 'kernel32::GetDiskFreeSpaceExW(w, *l, *l, *l) i(R7, .R6, ., .) .'

      ; Convert to KB for comparison with $R8 which is in KB
      System::Int64Op $R6 / 1024
      Pop $R6

      System::Int64Op $R6 > $R8
      Pop $R6

      IntCmp $R6 1 end +1 +1
      StrCpy $R9 "false"

      end:
      RmDir "$R7"
      RmDir "$INSTDIR\" ; Only remove $INSTDIR if it is empty

      ClearErrors

      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro CheckDiskSpaceCall _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call CheckDiskSpace
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.CheckDiskSpaceCall _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.CheckDiskSpace
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro un.CheckDiskSpace
  !ifndef un.CheckDiskSpace
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro CheckDiskSpace

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
* Returns the path found within a passed in string. The path is quoted or not
* with the exception of an unquoted non 8dot3 path without arguments that is
* also not a DefaultIcon path, is a 8dot3 path or not, has command line
* arguments, or is a registry DefaultIcon path (e.g. <path to binary>,# where #
* is the icon's resuorce id). The string does not need to be a valid path or
* exist. It is up to the caller to pass in a string of one of the forms noted
* above and to verify existence if necessary.
*
* Examples:
* In:  C:\PROGRA~1\MOZILL~1\FIREFOX.EXE -flag "%1"
* In:  C:\PROGRA~1\MOZILL~1\FIREFOX.EXE,0
* In:  C:\PROGRA~1\MOZILL~1\FIREFOX.EXE
* In:  "C:\PROGRA~1\MOZILL~1\FIREFOX.EXE"
* In:  "C:\PROGRA~1\MOZILL~1\FIREFOX.EXE" -flag "%1"
* Out: C:\PROGRA~1\MOZILL~1\FIREFOX.EXE
*
* In:  "C:\Program Files\Mozilla Firefox\firefox.exe" -flag "%1"
* In:  C:\Program Files\Mozilla Firefox\firefox.exe,0
* In:  "C:\Program Files\Mozilla Firefox\firefox.exe"
* Out: C:\Program Files\Mozilla Firefox\firefox.exe
*
* @param   _IN_PATH
*          The string containing the path.
* @param   _OUT_PATH
*          The register to store the path to.
*
* $R7 = counter for the outer loop's EnumRegKey
* $R8 = return value from ReadRegStr
* $R9 = _IN_PATH and _OUT_PATH
*/
!macro GetPathFromString

  !ifndef ${_MOZFUNC_UN}GetPathFromString
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}GetPathFromString "!insertmacro ${_MOZFUNC_UN}GetPathFromStringCall"

    Function ${_MOZFUNC_UN}GetPathFromString
      Exch $R9
      Push $R8
      Push $R7

      StrCpy $R7 0          ; Set the counter to 0.

      ; Handle quoted paths with arguments.
      StrCpy $R8 $R9 1      ; Copy the first char.
      StrCmp $R8 '"' +2 +1  ; Is it a "?
      StrCmp $R8 "'" +1 +9  ; Is it a '?
      StrCpy $R9 $R9 "" 1   ; Remove the first char.
      IntOp $R7 $R7 + 1     ; Increment the counter.
      StrCpy $R8 $R9 1 $R7  ; Starting from the counter copy the next char.
      StrCmp $R8 "" end +1  ; Are there no more chars?
      StrCmp $R8 '"' +2 +1  ; Is it a " char?
      StrCmp $R8 "'" +1 -4  ; Is it a ' char?
      StrCpy $R9 $R9 $R7    ; Copy chars up to the counter.
      GoTo end

      ; Handle DefaultIcon paths. DefaultIcon paths are not quoted and end with
      ; a , and a number.
      IntOp $R7 $R7 - 1     ; Decrement the counter.
      StrCpy $R8 $R9 1 $R7  ; Copy one char from the end minus the counter.
      StrCmp $R8 '' +4 +1   ; Are there no more chars?
      StrCmp $R8 ',' +1 -3  ; Is it a , char?
      StrCpy $R9 $R9 $R7    ; Copy chars up to the end minus the counter.
      GoTo end

      ; Handle unquoted paths with arguments. An unquoted path with arguments
      ; must be an 8dot3 path.
      StrCpy $R7 -1          ; Set the counter to -1 so it will start at 0.
      IntOp $R7 $R7 + 1      ; Increment the counter.
      StrCpy $R8 $R9 1 $R7   ; Starting from the counter copy the next char.
      StrCmp $R8 "" end +1   ; Are there no more chars?
      StrCmp $R8 " " +1 -3   ; Is it a space char?
      StrCpy $R9 $R9 $R7     ; Copy chars up to the counter.

      end:
      ClearErrors

      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro GetPathFromStringCall _IN_PATH _OUT_PATH
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_IN_PATH}"
  Call GetPathFromString
  Pop ${_OUT_PATH}
  !verbose pop
!macroend

!macro un.GetPathFromStringCall _IN_PATH _OUT_PATH
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_IN_PATH}"
  Call un.GetPathFromString
  Pop ${_OUT_PATH}
  !verbose pop
!macroend

!macro un.GetPathFromString
  !ifndef un.GetPathFromString
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro GetPathFromString

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Removes the quotes from each end of a string if present.
 *
 * @param   _IN_PATH
 *          The string containing the path.
 * @param   _OUT_PATH
 *          The register to store the long path.
 *
 * $R7 = storage for single character comparison
 * $R8 = storage for _IN_PATH
 * $R9 = _IN_PATH and _OUT_PATH
 */
!macro RemoveQuotesFromPath

  !ifndef ${_MOZFUNC_UN}RemoveQuotesFromPath
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}RemoveQuotesFromPath "!insertmacro ${_MOZFUNC_UN}RemoveQuotesFromPathCall"

    Function ${_MOZFUNC_UN}RemoveQuotesFromPath
      Exch $R9
      Push $R8
      Push $R7

      StrCpy $R7 "$R9" 1
      StrCmp $R7 "$\"" +1 +2
      StrCpy $R9 "$R9" "" 1

      StrCpy $R7 "$R9" "" -1
      StrCmp $R7 "$\"" +1 +2
      StrCpy $R9 "$R9" -1

      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro RemoveQuotesFromPathCall _IN_PATH _OUT_PATH
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_IN_PATH}"
  Call RemoveQuotesFromPath
  Pop ${_OUT_PATH}
  !verbose pop
!macroend

!macro un.RemoveQuotesFromPathCall _IN_PATH _OUT_PATH
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_IN_PATH}"
  Call un.RemoveQuotesFromPath
  Pop ${_OUT_PATH}
  !verbose pop
!macroend

!macro un.RemoveQuotesFromPath
  !ifndef un.RemoveQuotesFromPath
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro RemoveQuotesFromPath

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Returns the long path for an existing file or directory. GetLongPathNameW
 * may not be available on Win95 if Microsoft Layer for Unicode is not
 * installed and GetFullPathName only returns a long path for the last file or
 * directory that doesn't end with a \ in the path that it is passed. If the
 * path does not exist on the file system this will return an empty string. To
 * provide a consistent result trailing back-slashes are always removed.
 *
 * Note: 1024 used by GetLongPathNameW is the maximum NSIS string length.
 *
 * @param   _IN_PATH
 *          The string containing the path.
 * @param   _OUT_PATH
 *          The register to store the long path.
 *
 * $R4 = counter value when the previous \ was found
 * $R5 = directory or file name found during loop
 * $R6 = return value from GetLongPathNameW and loop counter
 * $R7 = long path from GetLongPathNameW and single char from path for comparison
 * $R8 = storage for _IN_PATH
 * $R9 = _IN_PATH _OUT_PATH
 */
!macro GetLongPath

  !ifndef ${_MOZFUNC_UN}GetLongPath
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}GetLongPath "!insertmacro ${_MOZFUNC_UN}GetLongPathCall"

    Function ${_MOZFUNC_UN}GetLongPath
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4

      ClearErrors

      GetFullPathName $R8 "$R9"
      IfErrors end_GetLongPath +1 ; If the path doesn't exist return an empty string.

      ; Make the drive letter uppercase.
      StrCpy $R9 "$R8" 1    ; Copy the first char.
      StrCpy $R8 "$R8" "" 1 ; Copy everything after the first char.
      ; Convert the first char to uppercase.
      System::Call "User32::CharUpper(w R9 R9)i"
      StrCpy $R8 "$R9$R8"   ; Copy the uppercase char and the rest of the chars.

      ; Do it the hard way.
      StrCpy $R4 0     ; Stores the position in the string of the last \ found.
      StrCpy $R6 -1    ; Set the counter to -1 so it will start at 0.

      loop_GetLongPath:
      IntOp $R6 $R6 + 1      ; Increment the counter.
      StrCpy $R7 $R8 1 $R6   ; Starting from the counter copy the next char.
      StrCmp $R7 "" +2 +1    ; Are there no more chars?
      StrCmp $R7 "\" +1 -3   ; Is it a \?

      ; Copy chars starting from the previously found \ to the counter.
      StrCpy $R5 $R8 $R6 $R4

      ; If this is the first \ found we want to swap R9 with R5 so a \ will
      ; be appended to the drive letter and colon (e.g. C: will become C:\).
      StrCmp $R4 0 +1 +3
      StrCpy $R9 $R5
      StrCpy $R5 ""

      GetFullPathName $R9 "$R9\$R5"

      StrCmp $R7 "" end_GetLongPath +1 ; Are there no more chars?

      ; Store the counter for the current \ and prefix it for StrCpy operations.
      StrCpy $R4 "+$R6"
      IntOp $R6 $R6 + 1      ; Increment the counter so we skip over the \.
      StrCpy $R8 $R8 "" $R6  ; Copy chars starting from the counter to the end.
      StrCpy $R6 -1          ; Reset the counter to -1 so it will start over at 0.
      GoTo loop_GetLongPath

      end_GetLongPath:
      ; If there is a trailing slash remove it
      StrCmp $R9 "" +4 +1
      StrCpy $R8 "$R9" "" -1
      StrCmp $R8 "\" +1 +2
      StrCpy $R9 "$R9" -1

      ClearErrors

      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro GetLongPathCall _IN_PATH _OUT_PATH
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_IN_PATH}"
  Call GetLongPath
  Pop ${_OUT_PATH}
  !verbose pop
!macroend

!macro un.GetLongPathCall _IN_PATH _OUT_PATH
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_IN_PATH}"
  Call un.GetLongPath
  Pop ${_OUT_PATH}
  !verbose pop
!macroend

!macro un.GetLongPath
  !ifndef un.GetLongPath
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro GetLongPath

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend


################################################################################
# Macros for cleaning up the registry and file system

/**
 * Removes registry keys that reference this install location and for paths that
 * no longer exist. This uses SHCTX to determine the registry hive so you must
 * call SetShellVarContext first.
 *
 * @param   _KEY
 *          The registry subkey (typically this will be Software\Mozilla).
 *
 * $0  = loop counter
 * $1  = temporary value used for string searches
 * $R0 = on x64 systems set to 'false' at the beginning of the macro when
 *       enumerating the x86 registry view and set to 'true' when enumerating
 *       the x64 registry view.
 * $R1 = stores the long path to $INSTDIR
 * $R2 = return value from the stack from the GetParent and GetLongPath macros
 * $R3 = return value from the outer loop's EnumRegKey and ESR string
 * $R4 = return value from the inner loop's EnumRegKey
 * $R5 = return value from ReadRegStr
 * $R6 = counter for the outer loop's EnumRegKey
 * $R7 = counter for the inner loop's EnumRegKey
 * $R8 = return value from the stack from the RemoveQuotesFromPath macro
 * $R9 = _KEY
 */
!macro RegCleanMain

  !ifndef ${_MOZFUNC_UN}RegCleanMain
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetParent
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !insertmacro ${_MOZFUNC_UN_TMP}RemoveQuotesFromPath
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}RegCleanMain "!insertmacro ${_MOZFUNC_UN}RegCleanMainCall"

    Function ${_MOZFUNC_UN}RegCleanMain
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4
      Push $R3
      Push $R2
      Push $R1
      Push $R0
      Push $0
      Push $1

      ${${_MOZFUNC_UN}GetLongPath} "$INSTDIR" $R1
      StrCpy $R6 0  ; set the counter for the outer loop to 0

      ${If} ${RunningX64}
      ${OrIf} ${IsNativeARM64}
        StrCpy $R0 "false"
        ; Set the registry to the 32 bit registry for 64 bit installations or to
        ; the 64 bit registry for 32 bit installations at the beginning so it can
        ; easily be set back to the correct registry view when finished.
        !ifdef HAVE_64BIT_BUILD
          SetRegView 32
        !else
          SetRegView 64
        !endif
      ${EndIf}

      outerloop:
      EnumRegKey $R3 SHCTX $R9 $R6
      StrCmp $R3 "" end +1  ; if empty there are no more keys to enumerate
      IntOp $R6 $R6 + 1     ; increment the outer loop's counter
      ClearErrors
      ReadRegStr $R5 SHCTX "$R9\$R3\bin" "PathToExe"
      IfErrors 0 outercontinue
      StrCpy $R7 0  ; set the counter for the inner loop to 0

      innerloop:
      EnumRegKey $R4 SHCTX "$R9\$R3" $R7
      StrCmp $R4 "" outerloop +1  ; if empty there are no more keys to enumerate
      IntOp $R7 $R7 + 1  ; increment the inner loop's counter
      ClearErrors
      ReadRegStr $R5 SHCTX "$R9\$R3\$R4\Main" "PathToExe"
      IfErrors innerloop

      ${${_MOZFUNC_UN}RemoveQuotesFromPath} "$R5" $R8
      ${${_MOZFUNC_UN}GetParent} "$R8" $R2
      ${${_MOZFUNC_UN}GetLongPath} "$R2" $R2
      IfFileExists "$R2" +1 innerloop
      StrCmp "$R2" "$R1" +1 innerloop

      ClearErrors
      DeleteRegKey SHCTX "$R9\$R3\$R4"
      IfErrors innerloop
      IntOp $R7 $R7 - 1 ; decrement the inner loop's counter when the key is deleted successfully.
      ClearErrors
      DeleteRegKey /ifempty SHCTX "$R9\$R3"
      IfErrors innerloop outerdecrement

      outercontinue:
      ${${_MOZFUNC_UN}RemoveQuotesFromPath} "$R5" $R8
      ${${_MOZFUNC_UN}GetParent} "$R8" $R2
      ${${_MOZFUNC_UN}GetLongPath} "$R2" $R2
      IfFileExists "$R2" +1 outerloop
      StrCmp "$R2" "$R1" +1 outerloop

      ClearErrors
      DeleteRegKey SHCTX "$R9\$R3"
      IfErrors outerloop

      outerdecrement:
      IntOp $R6 $R6 - 1 ; decrement the outer loop's counter when the key is deleted successfully.
      GoTo outerloop

      end:
      ; Check if _KEY\${BrandFullNameInternal} refers to a key that's been
      ; removed, either just now by this function or earlier by something else,
      ; and if so either update it to a key that does exist or remove it if we
      ; can't find anything to update it to.
      ; We'll run this check twice, once looking for non-ESR keys and then again
      ; looking specifically for the separate ESR keys.
      StrCpy $R3 ""
      ${For} $0 0 1
        ClearErrors
        ReadRegStr $R5 SHCTX "$R9\${BrandFullNameInternal}$R3" "CurrentVersion"
        ${IfNot} ${Errors}
          ReadRegStr $R5 SHCTX "$R9\${BrandFullNameInternal}\$R5" ""
          ${If} ${Errors}
            ; Key doesn't exist, update or remove CurrentVersion and default.
            StrCpy $R5 ""
            StrCpy $R6 0
            EnumRegKey $R4 SHCTX "$R9\${BrandFullNameInternal}" $R6
            ${While} $R4 != ""
              ClearErrors
              ${WordFind} "$R4" "esr" "E#" $1
              ${If} $R3 == ""
                ; The key we're looking to update is a non-ESR, so we need to
                ; select only another non-ESR to update it with.
                ${If} ${Errors}
                  StrCpy $R5 "$R4"
                  ${Break}
                ${EndIf}
              ${Else}
                ; The key we're looking to update is an ESR, so we need to
                ; select only another ESR to update it with.
                ${IfNot} ${Errors}
                  StrCpy $R5 "$R4"
                  ${Break}
                ${EndIf}
              ${EndIf}

              IntOp $R6 $R6 + 1
              EnumRegKey $R4 SHCTX "$R9\${BrandFullNameInternal}" $R6
            ${EndWhile}

            ${If} $R5 == ""
              ; We didn't find an install to update the key with, so delete the
              ; CurrentVersion value and the entire key if it has no subkeys.
              DeleteRegValue SHCTX "$R9\${BrandFullNameInternal}$R3" "CurrentVersion"
              DeleteRegValue SHCTX "$R9\${BrandFullNameInternal}$R3" ""
              DeleteRegKey /ifempty SHCTX "$R9\${BrandFullNameInternal}$R3"
            ${Else}
              ; We do have another still-existing install, so update the key to
              ; that version.
              WriteRegStr SHCTX "$R9\${BrandFullNameInternal}$R3" \
                                "CurrentVersion" "$R5"
              ${WordFind} "$R5" " " "+1{" $R5
              WriteRegStr SHCTX "$R9\${BrandFullNameInternal}$R3" "" "$R5"
            ${EndIf}
          ${EndIf}
          ; Else, the key referenced in CurrentVersion still exists,
          ; so there's nothing to update or remove.
        ${EndIf}

        ; Set up for the second iteration of the loop, where we'll be looking
        ; for the separate ESR keys.
        StrCpy $R3 " ESR"
      ${Next}

      ${If} ${RunningX64}
      ${OrIf} ${IsNativeARM64}
        ${If} "$R0" == "false"
          ; Set the registry to the correct view.
          !ifdef HAVE_64BIT_BUILD
            SetRegView 64
          !else
            SetRegView 32
          !endif

          StrCpy $R6 0  ; set the counter for the outer loop to 0
          StrCpy $R0 "true"
          GoTo outerloop
        ${EndIf}
      ${EndIf}

      ClearErrors

      Pop $1
      Pop $0
      Pop $R0
      Pop $R1
      Pop $R2
      Pop $R3
      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro RegCleanMainCall _KEY
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Call RegCleanMain
  !verbose pop
!macroend

!macro un.RegCleanMainCall _KEY
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Call un.RegCleanMain
  !verbose pop
!macroend

!macro un.RegCleanMain
  !ifndef un.RegCleanMain
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro RegCleanMain

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend


/**
 * Removes registry keys that reference this install location and for paths that
 * no longer exist.
 *
 * @param   _KEY ($R1)
 *          The registry subkey
 *          (typically this will be Software\Mozilla\${AppName}).
 */
!macro RegCleanPrefs
  !ifndef ${_MOZFUNC_UN}RegCleanPrefs
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}RegCleanPrefs "!insertmacro ${_MOZFUNC_UN}RegCleanPrefsCall"

    Function ${_MOZFUNC_UN}RegCleanPrefs
      Exch $R1 ; get _KEY from the stack

      ; These prefs are always stored in the native registry.
      SetRegView 64

      ; Delete the installer prefs key for this installation, if one exists.
      DeleteRegKey HKCU "$R1\Installer\$AppUserModelID"

      ; If there aren't any more installer prefs keys, delete the parent key.
      DeleteRegKey /ifempty HKCU "$R1\Installer"

      SetRegView lastused

      Pop $R1 ; restore the previous $R1
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro RegCleanPrefsCall _KEY
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Call RegCleanPrefs
  !verbose pop
!macroend

!macro un.RegCleanPrefsCall _KEY
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_KEY}"
  Call un.RegCleanPrefs
  !verbose pop
!macroend

!macro un.RegCleanPrefs
  !ifndef un.RegCleanPrefs
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro RegCleanPrefs

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Removes all registry keys from \Software\Windows\CurrentVersion\Uninstall
 * that reference this install location in both the 32 bit and 64 bit registry
 * view. This macro uses SHCTX to determine the registry hive so you must call
 * SetShellVarContext first.
 *
 * $R3 = on x64 systems set to 'false' at the beginning of the macro when
 *       enumerating the x86 registry view and set to 'true' when enumerating
 *       the x64 registry view.
 * $R4 = stores the long path to $INSTDIR
 * $R5 = return value from ReadRegStr
 * $R6 = string for the base reg key (e.g. Software\Microsoft\Windows\CurrentVersion\Uninstall)
 * $R7 = return value from EnumRegKey
 * $R8 = counter for EnumRegKey
 * $R9 = return value from the stack from the RemoveQuotesFromPath and GetLongPath macros
 */
!macro RegCleanUninstall

  !ifndef ${_MOZFUNC_UN}RegCleanUninstall
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !insertmacro ${_MOZFUNC_UN_TMP}RemoveQuotesFromPath
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}RegCleanUninstall "!insertmacro ${_MOZFUNC_UN}RegCleanUninstallCall"

    Function ${_MOZFUNC_UN}RegCleanUninstall
      Push $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4
      Push $R3

      ${${_MOZFUNC_UN}GetLongPath} "$INSTDIR" $R4
      StrCpy $R6 "Software\Microsoft\Windows\CurrentVersion\Uninstall"
      StrCpy $R7 ""
      StrCpy $R8 0

      ${If} ${RunningX64}
      ${OrIf} ${IsNativeARM64}
        StrCpy $R3 "false"
        ; Set the registry to the 32 bit registry for 64 bit installations or to
        ; the 64 bit registry for 32 bit installations at the beginning so it can
        ; easily be set back to the correct registry view when finished.
        !ifdef HAVE_64BIT_BUILD
          SetRegView 32
        !else
          SetRegView 64
        !endif
      ${EndIf}

      loop:
      EnumRegKey $R7 SHCTX $R6 $R8
      StrCmp $R7 "" end +1
      IntOp $R8 $R8 + 1 ; Increment the counter
      ClearErrors
      ReadRegStr $R5 SHCTX "$R6\$R7" "InstallLocation"
      IfErrors loop
      ${${_MOZFUNC_UN}RemoveQuotesFromPath} "$R5" $R9

      ; Detect when the path is just a drive letter without a trailing
      ; backslash (e.g., "C:"), and add a backslash. If we don't, the Win32
      ; calls in GetLongPath will interpret that syntax as a shorthand
      ; for the working directory, because that's the DOS 2.0 convention,
      ; and will return the path to that directory instead of just the drive.
      ; Back here, we would then successfully match that with our $INSTDIR,
      ; and end up deleting a registry key that isn't really ours.
      StrLen $R5 "$R9"
      ${If} $R5 == 2
        StrCpy $R5 "$R9" 1 1
        ${If} "$R5" == ":"
          StrCpy $R9 "$R9\"
        ${EndIf}
      ${EndIf}

      ${${_MOZFUNC_UN}GetLongPath} "$R9" $R9
      StrCmp "$R9" "$R4" +1 loop
      ClearErrors
      DeleteRegKey SHCTX "$R6\$R7"
      IfErrors loop +1
      IntOp $R8 $R8 - 1 ; Decrement the counter on successful deletion
      GoTo loop

      end:
      ${If} ${RunningX64}
      ${OrIf} ${IsNativeARM64}
        ${If} "$R3" == "false"
          ; Set the registry to the correct view.
          !ifdef HAVE_64BIT_BUILD
            SetRegView 64
          !else
            SetRegView 32
          !endif

          StrCpy $R7 ""
          StrCpy $R8 0
          StrCpy $R3 "true"
          GoTo loop
        ${EndIf}
      ${EndIf}

      ClearErrors

      Pop $R3
      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Pop $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro RegCleanUninstallCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call RegCleanUninstall
  !verbose pop
!macroend

!macro un.RegCleanUninstallCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.RegCleanUninstall
  !verbose pop
!macroend

!macro un.RegCleanUninstall
  !ifndef un.RegCleanUninstall
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro RegCleanUninstall

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Removes an application specific handler registry key under Software\Classes
 * for both HKCU and HKLM when its open command refers to this install
 * location or the install location doesn't exist.
 *
 * @param   _HANDLER_NAME
 *          The registry name for the handler.
 *
 * $R7 = stores the long path to the $INSTDIR
 * $R8 = stores the path to the open command's parent directory
 * $R9 = _HANDLER_NAME
 */
!macro RegCleanAppHandler

  !ifndef ${_MOZFUNC_UN}RegCleanAppHandler
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !insertmacro ${_MOZFUNC_UN_TMP}GetParent
    !insertmacro ${_MOZFUNC_UN_TMP}GetPathFromString
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}RegCleanAppHandler "!insertmacro ${_MOZFUNC_UN}RegCleanAppHandlerCall"

    Function ${_MOZFUNC_UN}RegCleanAppHandler
      Exch $R9
      Push $R8
      Push $R7

      ClearErrors
      ReadRegStr $R8 HKCU "Software\Classes\$R9\shell\open\command" ""
      IfErrors next +1
      ${${_MOZFUNC_UN}GetPathFromString} "$R8" $R8
      ${${_MOZFUNC_UN}GetParent} "$R8" $R8
      IfFileExists "$R8" +3 +1
      DeleteRegKey HKCU "Software\Classes\$R9"
      GoTo next

      ${${_MOZFUNC_UN}GetLongPath} "$R8" $R8
      ${${_MOZFUNC_UN}GetLongPath} "$INSTDIR" $R7
      StrCmp "$R7" "$R8" +1 next
      DeleteRegKey HKCU "Software\Classes\$R9"

      next:
      ReadRegStr $R8 HKLM "Software\Classes\$R9\shell\open\command" ""
      IfErrors end
      ${${_MOZFUNC_UN}GetPathFromString} "$R8" $R8
      ${${_MOZFUNC_UN}GetParent} "$R8" $R8
      IfFileExists "$R8" +3 +1
      DeleteRegKey HKLM "Software\Classes\$R9"
      GoTo end

      ${${_MOZFUNC_UN}GetLongPath} "$R8" $R8
      ${${_MOZFUNC_UN}GetLongPath} "$INSTDIR" $R7
      StrCmp "$R7" "$R8" +1 end
      DeleteRegKey HKLM "Software\Classes\$R9"

      end:

      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro RegCleanAppHandlerCall _HANDLER_NAME
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_HANDLER_NAME}"
  Call RegCleanAppHandler
  !verbose pop
!macroend

!macro un.RegCleanAppHandlerCall _HANDLER_NAME
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_HANDLER_NAME}"
  Call un.RegCleanAppHandler
  !verbose pop
!macroend

!macro un.RegCleanAppHandler
  !ifndef un.RegCleanAppHandler
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro RegCleanAppHandler

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Cleans up the registry for a protocol handler when its open command
 * refers to this install location. For HKCU the registry key is deleted
 * and for HKLM the values set by the application are deleted.
 *
 * @param   _HANDLER_NAME
 *          The registry name for the handler.
 *
 * $R7 = stores the long path to $INSTDIR
 * $R8 = stores the the long path to the open command's parent directory
 * $R9 = _HANDLER_NAME
 */
!macro un.RegCleanProtocolHandler

  !ifndef un.RegCleanProtocolHandler
    !insertmacro un.GetLongPath
    !insertmacro un.GetParent
    !insertmacro un.GetPathFromString

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define un.RegCleanProtocolHandler "!insertmacro un.RegCleanProtocolHandlerCall"

    Function un.RegCleanProtocolHandler
      Exch $R9
      Push $R8
      Push $R7

      ReadRegStr $R8 HKCU "Software\Classes\$R9\shell\open\command" ""
      ${un.GetLongPath} "$INSTDIR" $R7

      StrCmp "$R8" "" next +1
      ${un.GetPathFromString} "$R8" $R8
      ${un.GetParent} "$R8" $R8
      ${un.GetLongPath} "$R8" $R8
      StrCmp "$R7" "$R8" +1 next
      DeleteRegKey HKCU "Software\Classes\$R9"

      next:
      ReadRegStr $R8 HKLM "Software\Classes\$R9\shell\open\command" ""
      StrCmp "$R8" "" end +1
      ${un.GetLongPath} "$INSTDIR" $R7
      ${un.GetPathFromString} "$R8" $R8
      ${un.GetParent} "$R8" $R8
      ${un.GetLongPath} "$R8" $R8
      StrCmp "$R7" "$R8" +1 end
      DeleteRegValue HKLM "Software\Classes\$R9\DefaultIcon" ""
      DeleteRegValue HKLM "Software\Classes\$R9\shell\open" ""
      DeleteRegValue HKLM "Software\Classes\$R9\shell\open\command" ""
      DeleteRegValue HKLM "Software\Classes\$R9\shell\ddeexec" ""
      DeleteRegValue HKLM "Software\Classes\$R9\shell\ddeexec\Application" ""
      DeleteRegValue HKLM "Software\Classes\$R9\shell\ddeexec\Topic" ""

      end:

      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro un.RegCleanProtocolHandlerCall _HANDLER_NAME
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_HANDLER_NAME}"
  Call un.RegCleanProtocolHandler
  !verbose pop
!macroend

/**
 * Cleans up the registry for a file handler when the passed in value equals
 * the default value for the file handler. For HKCU the registry key is deleted
 * and for HKLM the default value is deleted.
 *
 * @param   _HANDLER_NAME
 *          The registry name for the handler.
 * @param   _DEFAULT_VALUE
 *          The value to check for against the handler's default value.
 *
 * $R6 = stores the long path to $INSTDIR
 * $R7 = _DEFAULT_VALUE
 * $R9 = _HANDLER_NAME
 */
!macro RegCleanFileHandler

  !ifndef ${_MOZFUNC_UN}RegCleanFileHandler
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !insertmacro ${_MOZFUNC_UN_TMP}GetParent
    !insertmacro ${_MOZFUNC_UN_TMP}GetPathFromString
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}RegCleanFileHandler "!insertmacro ${_MOZFUNC_UN}RegCleanFileHandlerCall"

    Function ${_MOZFUNC_UN}RegCleanFileHandler
      Exch $R9
      Exch 1
      Exch $R8
      Push $R7

      DeleteRegValue HKCU "Software\Classes\$R9\OpenWithProgids" $R8
      EnumRegValue $R7 HKCU "Software\Classes\$R9\OpenWithProgids" 0
      StrCmp "$R7" "" +1 +2
      DeleteRegKey HKCU "Software\Classes\$R9\OpenWithProgids"
      ReadRegStr $R7 HKCU "Software\Classes\$R9" ""
      StrCmp "$R7" "$R8" +1 +2
      DeleteRegKey HKCU "Software\Classes\$R9"

      DeleteRegValue HKLM "Software\Classes\$R9\OpenWithProgids" $R8
      EnumRegValue $R7 HKLM "Software\Classes\$R9\OpenWithProgids" 0
      StrCmp "$R7" "" +1 +2
      DeleteRegKey HKLM "Software\Classes\$R9\OpenWithProgids"
      ReadRegStr $R7 HKLM "Software\Classes\$R9" ""
      StrCmp "$R7" "$R8" +1 +2
      DeleteRegValue HKLM "Software\Classes\$R9" ""

      ClearErrors

      Pop $R7
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro RegCleanFileHandlerCall _HANDLER_NAME _DEFAULT_VALUE
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_DEFAULT_VALUE}"
  Push "${_HANDLER_NAME}"
  Call RegCleanFileHandler
  !verbose pop
!macroend

!macro un.RegCleanFileHandlerCall _HANDLER_NAME _DEFAULT_VALUE
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_DEFAULT_VALUE}"
  Push "${_HANDLER_NAME}"
  Call un.RegCleanFileHandler
  !verbose pop
!macroend

!macro un.RegCleanFileHandler
  !ifndef un.RegCleanFileHandler
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro RegCleanFileHandler

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Checks if a handler's open command points to this installation directory.
 * Uses SHCTX to determine the registry hive (e.g. HKLM or HKCU) to check.
 *
 * @param   _HANDLER_NAME
 *          The registry name for the handler.
 * @param   _RESULT
 *          true if it is the handler's open command points to this
 *          installation directory and false if it does not.
 *
 * $R7 = stores the value of the open command and the path macros return values
 * $R8 = stores the handler's registry key name
 * $R9 = _DEFAULT_VALUE and _RESULT
 */
!macro IsHandlerForInstallDir

  !ifndef ${_MOZFUNC_UN}IsHandlerForInstallDir
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !insertmacro ${_MOZFUNC_UN_TMP}GetParent
    !insertmacro ${_MOZFUNC_UN_TMP}GetPathFromString
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}IsHandlerForInstallDir "!insertmacro ${_MOZFUNC_UN}IsHandlerForInstallDirCall"

    Function ${_MOZFUNC_UN}IsHandlerForInstallDir
      Exch $R9
      Push $R8
      Push $R7

      StrCpy $R8 "$R9"
      StrCpy $R9 "false"
      ReadRegStr $R7 SHCTX "Software\Classes\$R8\shell\open\command" ""

      ${If} $R7 != ""
        ${GetPathFromString} "$R7" $R7
        ${GetParent} "$R7" $R7
        ${GetLongPath} "$R7" $R7
        ${If} $R7 == $INSTDIR
          StrCpy $R9 "true"
        ${EndIf}
      ${EndIf}

      ClearErrors

      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro IsHandlerForInstallDirCall _HANDLER_NAME _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_HANDLER_NAME}"
  Call IsHandlerForInstallDir
  Pop "${_RESULT}"
  !verbose pop
!macroend

!macro un.IsHandlerForInstallDirCall _HANDLER_NAME _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_HANDLER_NAME}"
  Call un.IsHandlerForInstallDir
  Pop "${_RESULT}"
  !verbose pop
!macroend

!macro un.IsHandlerForInstallDir
  !ifndef un.IsHandlerForInstallDir
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro IsHandlerForInstallDir

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Removes the application's VirtualStore directory if present when the
 * installation directory is a sub-directory of the program files directory.
 *
 * $R3 = Local App Data dir
 * $R4 = $PROGRAMFILES/$PROGRAMFILES64 for CleanVirtualStore_Internal
 * $R5 = various path values.
 * $R6 = length of the long path to $PROGRAMFILES32 or $PROGRAMFILES64
 * $R7 = long path to $PROGRAMFILES32 or $PROGRAMFILES64
 * $R8 = length of the long path to $INSTDIR
 * $R9 = long path to $INSTDIR
 */
!macro CleanVirtualStore

  !ifndef ${_MOZFUNC_UN}CleanVirtualStore
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}CleanVirtualStore "!insertmacro ${_MOZFUNC_UN}CleanVirtualStoreCall"

    Function ${_MOZFUNC_UN}CleanVirtualStore
      Push $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4
      Push $R3

      ${${_MOZFUNC_UN}GetLongPath} "$INSTDIR" $R9
      ${If} "$R9" != ""
        StrLen $R8 "$R9"

        StrCpy $R4 $PROGRAMFILES32
        Call ${_MOZFUNC_UN}CleanVirtualStore_Internal

        ${If} ${RunningX64}
        ${OrIf} ${IsNativeARM64}
          StrCpy $R4 $PROGRAMFILES64
          Call ${_MOZFUNC_UN}CleanVirtualStore_Internal
        ${EndIf}

      ${EndIf}

      ClearErrors

      Pop $R3
      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Pop $R9
    FunctionEnd

    Function ${_MOZFUNC_UN}CleanVirtualStore_Internal
      ${${_MOZFUNC_UN}GetLongPath} "" $R7
      ${If} "$R7" != ""
        StrLen $R6 "$R7"
        ${If} $R8 < $R6
          ; Copy from the start of $INSTDIR the length of $PROGRAMFILES64
          StrCpy $R5 "$R9" $R6
          ${If} "$R5" == "$R7"
            ; Remove the drive letter and colon from the $INSTDIR long path
            StrCpy $R5 "$R9" "" 2
            ${GetLocalAppDataFolder} $R3
            StrCpy $R5 "$R3\VirtualStore$R5"
            ${${_MOZFUNC_UN}GetLongPath} "$R5" $R5
            ${If} "$R5" != ""
            ${AndIf} ${FileExists} "$R5"
              RmDir /r "$R5"
            ${EndIf}
          ${EndIf}
        ${EndIf}
      ${EndIf}
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro CleanVirtualStoreCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call CleanVirtualStore
  !verbose pop
!macroend

!macro un.CleanVirtualStoreCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.CleanVirtualStore
  !verbose pop
!macroend

!macro un.CleanVirtualStore
  !ifndef un.CleanVirtualStore
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro CleanVirtualStore

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Cleans up some old logs in the Maintenance Service directory.
 *
 * @param   _OLD_REL_PATH
 *          The relative path to the profile directory from Local AppData.
 *          Calculated for the old update directory not based on a hash.
 *
 * $R7 = _OLD_REL_PATH
 * $R1 = taskBar ID hash located in registry at SOFTWARE\_OLD_REL_PATH\TaskBarIDs
 * $R6 = long path to $INSTDIR
 */
!macro CleanMaintenanceServiceLogs

  !ifndef ${_MOZFUNC_UN}CleanMaintenanceServiceLogs
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !insertmacro ${_MOZFUNC_UN_TMP}GetCommonDirectory
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}CleanMaintenanceServiceLogs "!insertmacro ${_MOZFUNC_UN}CleanMaintenanceServiceLogsCall"

    Function ${_MOZFUNC_UN}CleanMaintenanceServiceLogs
      Exch $R7
      Push $R6
      Push $R1

      ${${_MOZFUNC_UN}GetLongPath} "$INSTDIR" $R6

      ${If} $R7 != "" ; _OLD_REL_PATH was passed
      ${AndIf} $R6 != "" ; We have the install dir path
        ; Get the taskbar ID hash for this installation path
        ReadRegStr $R1 HKLM "SOFTWARE\$R7\TaskBarIDs" $R6
        ${If} $R1 == ""
          ReadRegStr $R1 HKCU "SOFTWARE\$R7\TaskBarIDs" $R6
        ${EndIf}

        ${If} $R1 != ""
          ; Remove the secure log files that our updater may have created
          ; inside the maintenance service path. There are several files named
          ; with the install hash and an extension indicating the kind of file.
          ; so use a wildcard to delete them all.
          Delete "$PROGRAMFILES32\Mozilla Maintenance Service\UpdateLogs\$R1.*"

          ; If the UpdateLogs directory is now empty, then delete it.
          ; The Maintenance Service uninstaller should do this, but it may not
          ; be up to date enough because of bug 1665193, so doing this here as
          ; well lets us make sure it really happens.
          RmDir "$PROGRAMFILES32\Mozilla Maintenance Service\UpdateLogs"
        ${EndIf}
      ${EndIf}

      ClearErrors

      Pop $R1
      Pop $R6
      Exch $R7
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro CleanMaintenanceServiceLogsCall _OLD_REL_PATH
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_OLD_REL_PATH}"
  Call CleanMaintenanceServiceLogs
  !verbose pop
!macroend

!macro un.CleanMaintenanceServiceLogsCall _OLD_REL_PATH
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_OLD_REL_PATH}"
  Call un.CleanMaintenanceServiceLogs
  !verbose pop
!macroend

!macro un.CleanMaintenanceServiceLogs
  !ifndef un.CleanMaintenanceServiceLogs
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro CleanMaintenanceServiceLogs

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Removes relevant shortcuts from a given shortcuts log.
 *
 * $R3 = return value from ShellLink::GetShortCutWorkingDirectory
 * $R4 = counter for appending to Shortcut for enumerating the ini file entries
 * $R5 = return value from ShellLink::GetShortCutTarget
 * $R6 = the long path to the Start Menu Programs directory (e.g. $SMPROGRAMS)
 * $R7 = the return value from ReadINIStr for the relative path to the applications
 *       directory under the Start Menu Programs directory and the long path to this
 *       directory
 * $R8 = the return value from ReadINIStr for enumerating shortcuts
 * $R9 = [in] long path to the shortcuts ini file
 */
!macro DeleteShortcutsFromLog
  !ifndef ${_MOZFUNC_UN}DeleteShortcutsFromLog
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !insertmacro ${_MOZFUNC_UN_TMP}GetParent
    !insertmacro ${_MOZFUNC_UN_TMP}GetCommonDirectory

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}DeleteShortcutsFromLog "!insertmacro ${_MOZFUNC_UN}DeleteShortcutsFromLogCall"

    Function ${_MOZFUNC_UN}DeleteShortcutsFromLog
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4
      Push $R3

      ${If} ${FileExists} "$R9"
        ; Delete Start Menu shortcuts for this application
        StrCpy $R4 -1
        ${Do}
          IntOp $R4 $R4 + 1 ; Increment the counter
          ClearErrors
          ReadINIStr $R8 "$R9" "STARTMENU" "Shortcut$R4"
          ${If} ${Errors}
            ${ExitDo}
          ${EndIf}

          ${If} ${FileExists} "$SMPROGRAMS\$R8"
            ShellLink::GetShortCutTarget "$SMPROGRAMS\$R8"
            Pop $R5
            ${${_MOZFUNC_UN}GetLongPath} "$R5" $R5
            ; Shortcuts created outside of the installer may
            ; have metadata set on them such that GetShortCutTarget
            ; returns the wrong path on 64-bit systems.
            ; GetShortCutWorkingDirectory is not _quite_ as good
            ; of a test (because it may be unset), but it's not
            ; subject to this bug, and is therefore the next best thing.
            ; https://phabricator.services.mozilla.com/D138197
            ; contains some additional background on this.
            ShellLink::GetShortCutWorkingDirectory "$SMPROGRAMS\$R8"
            Pop $R3
            ${If} "$INSTDIR\${FileMainEXE}" == "$R5"
            ${OrIf} "$INSTDIR" == "$R3"
              Delete "$SMPROGRAMS\$R8"
            ${EndIf}
          ${EndIf}
        ${Loop}
        ; There might also be a shortcut with a different name created by a
        ; previous version of the installer.
        ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
          ShellLink::GetShortCutTarget "$SMPROGRAMS\${BrandFullName}.lnk"
          Pop $R5
          ${${_MOZFUNC_UN}GetLongPath} "$R5" $R5
          ShellLink::GetShortCutWorkingDirectory "$SMPROGRAMS\$R8"
          Pop $R3
          ${If} "$INSTDIR\${FileMainEXE}" == "$R5"
          ${OrIf} "$INSTDIR" == "$R3"
            Delete "$SMPROGRAMS\${BrandFullName}.lnk"
          ${EndIf}
        ${EndIf}

        ; Delete Quick Launch shortcuts for this application
        StrCpy $R4 -1
        ${Do}
          IntOp $R4 $R4 + 1 ; Increment the counter
          ClearErrors
          ReadINIStr $R8 "$R9" "QUICKLAUNCH" "Shortcut$R4"
          ${If} ${Errors}
            ${ExitDo}
          ${EndIf}

          ${If} ${FileExists} "$QUICKLAUNCH\$R8"
            ShellLink::GetShortCutTarget "$QUICKLAUNCH\$R8"
            Pop $R5
            ${${_MOZFUNC_UN}GetLongPath} "$R5" $R5
            ShellLink::GetShortCutWorkingDirectory "$SMPROGRAMS\$R8"
            Pop $R3
            ${If} "$INSTDIR\${FileMainEXE}" == "$R5"
            ${OrIf} "$INSTDIR" == "$R3"
              Delete "$QUICKLAUNCH\$R8"
            ${EndIf}
          ${EndIf}
        ${Loop}
        ; There might also be a shortcut with a different name created by a
        ; previous version of the installer.
        ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
          ShellLink::GetShortCutTarget "$QUICKLAUNCH\${BrandFullName}.lnk"
          Pop $R5
          ${${_MOZFUNC_UN}GetLongPath} "$R5" $R5
          ShellLink::GetShortCutWorkingDirectory "$SMPROGRAMS\$R8"
          Pop $R3
          ${If} "$INSTDIR\${FileMainEXE}" == "$R5"
          ${OrIf} "$INSTDIR" == "$R3"
            Delete "$QUICKLAUNCH\${BrandFullName}.lnk"
          ${EndIf}
        ${EndIf}

        ; Delete Desktop shortcuts for this application
        StrCpy $R4 -1
        ${Do}
          IntOp $R4 $R4 + 1 ; Increment the counter
          ClearErrors
          ReadINIStr $R8 "$R9" "DESKTOP" "Shortcut$R4"
          ${If} ${Errors}
            ${ExitDo}
          ${EndIf}

          ${If} ${FileExists} "$DESKTOP\$R8"
            ShellLink::GetShortCutTarget "$DESKTOP\$R8"
            Pop $R5
            ${${_MOZFUNC_UN}GetLongPath} "$R5" $R5
            ShellLink::GetShortCutWorkingDirectory "$SMPROGRAMS\$R8"
            Pop $R3
            ${If} "$INSTDIR\${FileMainEXE}" == "$R5"
            ${OrIf} "$INSTDIR" == "$R3"
              Delete "$DESKTOP\$R8"
            ${EndIf}
          ${EndIf}
        ${Loop}
        ; There might also be a shortcut with a different name created by a
        ; previous version of the installer.
        ${If} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
          ShellLink::GetShortCutTarget "$DESKTOP\${BrandFullName}.lnk"
          Pop $R5
          ${${_MOZFUNC_UN}GetLongPath} "$R5" $R5
          ; We don't bother checking working directory here because we know
          ; these shortcuts were only created by the installer, and thus
          ; not subject to the bug described above.
          ${If} "$INSTDIR\${FileMainEXE}" == "$R5"
            Delete "$DESKTOP\${BrandFullName}.lnk"
          ${EndIf}
        ${EndIf}

        ${${_MOZFUNC_UN}GetLongPath} "$SMPROGRAMS" $R6

        ; Delete Start Menu Programs shortcuts for this application
        ClearErrors
        ReadINIStr $R7 "$R9" "SMPROGRAMS" "RelativePathToDir"
        ${${_MOZFUNC_UN}GetLongPath} "$R6\$R7" $R7
        ${Unless} "$R7" == ""
          StrCpy $R4 -1
          ${Do}
            IntOp $R4 $R4 + 1 ; Increment the counter
            ClearErrors
            ReadINIStr $R8 "$R9" "SMPROGRAMS" "Shortcut$R4"
            ${If} ${Errors}
              ${ExitDo}
            ${EndIf}

            ${If} ${FileExists} "$R7\$R8"
              ShellLink::GetShortCutTarget "$R7\$R8"
              Pop $R5
              ${${_MOZFUNC_UN}GetLongPath} "$R5" $R5
              ${If} "$INSTDIR\${FileMainEXE}" == "$R5"
                Delete "$R7\$R8"
              ${EndIf}
            ${EndIf}
          ${Loop}

          ; Delete Start Menu Programs directories for this application
          ${Do}
            ClearErrors
            ${If} "$R6" == "$R7"
              ${ExitDo}
            ${EndIf}
            RmDir "$R7"
            ${If} ${Errors}
              ${ExitDo}
            ${EndIf}
            ${${_MOZFUNC_UN}GetParent} "$R7" $R7
          ${Loop}
        ${EndUnless}
      ${EndIf}

      ClearErrors

      Pop $R3
      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro DeleteShortcutsFromLogCall _SHORTCUTS_LOG
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push ${_SHORTCUTS_LOG}
  Call DeleteShortcutsFromLog
  !verbose pop
!macroend

!macro un.DeleteShortcutsFromLogCall _SHORTCUTS_LOG
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push ${_SHORTCUTS_LOG}
  Call un.DeleteShortcutsFromLog
  !verbose pop
!macroend

!macro un.DeleteShortcutsFromLog
  !ifndef un.DeleteShortcutsFromLog
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro DeleteShortcutsFromLog

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Deletes shortcuts and Start Menu directories under Programs as specified by
 * the shortcuts log ini files and on Windows 7 unpins TaskBar and Start Menu
 * shortcuts. The shortcuts will not be deleted if the shortcut target isn't for
 * this install location which is determined by the shortcut having a target of
 * $INSTDIR\${FileMainEXE} _or_ the working directory of the shortcut being
 * equal to $INSTDIR. The context (All Users or Current User) of the
 * $DESKTOP and $SMPROGRAMS constants depends on the
 * SetShellVarContext setting and must be set by the caller of this macro. There
 * is no All Users context for $QUICKLAUNCH but this will not cause a problem
 * since the macro will just continue past the $QUICKLAUNCH shortcut deletion
 * section on subsequent calls.
 *
 * The meat of this is farmed out to DeleteShortcutsFromLog to facilitate
 * the processing of multiple shortcuts logs.
 *
 * The ini file sections must have the following format (the order of the
 * sections in the ini file is not important):
 * [SMPROGRAMS] -- this section is optional and largely irrelevant these days
 * ; RelativePath is the directory relative from the Start Menu
 * ; Programs directory.
 * RelativePath=Mozilla App
 * ; Shortcut1 is the first shortcut, Shortcut2 is the second shortcut, and so
 * ; on. There must not be a break in the sequence of the numbers.
 * Shortcut1=Mozilla App.lnk
 * Shortcut2=Mozilla App (Safe Mode).lnk
 * [DESKTOP]
 * ; Shortcut1 is the first shortcut, Shortcut2 is the second shortcut, and so
 * ; on. There must not be a break in the sequence of the numbers.
 * Shortcut1=Mozilla App.lnk
 * Shortcut2=Mozilla App (Safe Mode).lnk
 * [QUICKLAUNCH]
 * ; Shortcut1 is the first shortcut, Shortcut2 is the second shortcut, and so
 * ; on. There must not be a break in the sequence of the numbers for the
 * ; suffix.
 * Shortcut1=Mozilla App.lnk
 * Shortcut2=Mozilla App (Safe Mode).lnk
 * [STARTMENU]
 * ; Shortcut1 is the first shortcut, Shortcut2 is the second shortcut, and so
 * ; on. There must not be a break in the sequence of the numbers for the
 * ; suffix.
 * Shortcut1=Mozilla App.lnk
 * Shortcut2=Mozilla App (Safe Mode).lnk
 *
 * $R4 = return value from ShellLink::GetShortCutWorkingDirectory
 * $R5 = return value from ShellLink::GetShortCutTarget and
 *       ApplicationID::UninstallPinnedItem
 * $R6 = find handle
 * $R7 = path to the $QUICKLAUNCH\User Pinned directory
 * $R8 = return filename from FindFirst / FindNext
 * $R9 = long path to the shortcut log ini file and path to the ProgramData
 *       directory that the application stores additional shortcut logs in
 *       (Typically c:\ProgramData\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38)
 */
!macro DeleteShortcuts

  !ifndef ${_MOZFUNC_UN}DeleteShortcuts
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !insertmacro ${_MOZFUNC_UN_TMP}GetParent
    !insertmacro ${_MOZFUNC_UN_TMP}DeleteShortcutsFromLog
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}DeleteShortcuts "!insertmacro ${_MOZFUNC_UN}DeleteShortcutsCall"

    Function ${_MOZFUNC_UN}DeleteShortcuts
      Push $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4

      ; We call ApplicationID::UninstallPinnedItem once per shortcut here
      ; (and explicitly not in DeleteShortcutsFromLog). Calling it again later
      ; would remove the association of side by side installations.
      ; Since shortcuts that are pinned can later be removed without removing
      ; the pinned shortcut unpin the pinned shortcuts for the application's
      ; main exe using the pinned shortcuts themselves.
      StrCpy $R7 "$QUICKLAUNCH\User Pinned"

      ${If} ${FileExists} "$R7\TaskBar"
        ; Delete TaskBar pinned shortcuts for the application's main exe
        FindFirst $R6 $R8 "$R7\TaskBar\*.lnk"
        ${Do}
          ${If} ${FileExists} "$R7\TaskBar\$R8"
            ShellLink::GetShortCutTarget "$R7\TaskBar\$R8"
            Pop $R5
            ${${_MOZFUNC_UN}GetLongPath} "$R5" $R5
            ShellLink::GetShortCutWorkingDirectory "$SMPROGRAMS\$R8"
            Pop $R4
            ${If} "$R5" == "$INSTDIR\${FileMainEXE}"
            ${OrIf} "$R4" == "$INSTDIR"
              ApplicationID::UninstallPinnedItem "$R7\TaskBar\$R8"
              Pop $R5
            ${EndIf}
          ${EndIf}
          ClearErrors
          FindNext $R6 $R8
          ${If} ${Errors}
            ${ExitDo}
          ${EndIf}
        ${Loop}
        FindClose $R6
      ${EndIf}

      ${If} ${FileExists} "$R7\StartMenu"
        ; Delete Start Menu pinned shortcuts for the application's main exe
        FindFirst $R6 $R8 "$R7\StartMenu\*.lnk"
        ${Do}
          ${If} ${FileExists} "$R7\StartMenu\$R8"
            ShellLink::GetShortCutTarget "$R7\StartMenu\$R8"
            Pop $R5
            ${${_MOZFUNC_UN}GetLongPath} "$R5" $R5
            ShellLink::GetShortCutWorkingDirectory "$SMPROGRAMS\$R8"
            Pop $R4
            ${If} "$R5" == "$INSTDIR\${FileMainEXE}"
            ${OrIf} "$R4" == "$INSTDIR"
                ApplicationID::UninstallPinnedItem "$R7\StartMenu\$R8"
                Pop $R5
            ${EndIf}
          ${EndIf}
          ClearErrors
          FindNext $R6 $R8
          ${If} ${Errors}
            ${ExitDo}
          ${EndIf}
        ${Loop}
        FindClose $R6
      ${EndIf}

      ${${_MOZFUNC_UN}GetLongPath} "$INSTDIR\uninstall\${SHORTCUTS_LOG}" $R9
      ${${_MOZFUNC_UN}DeleteShortcutsFromLog} $R9

      ${${_MOZFUNC_UN}GetCommonDirectory} $R9
      ; Shortcut logs created by an application are in a different directory,
      ; and named after both the application and the user SID, eg:
      ; Firefox_S-1-5-21-1004336348-1177238915-682003330-512_shortcuts.ini
      FindFirst $R6 $R8 "$R9\*_shortcuts.ini"
      ${DoUntil} ${Errors}
        ${If} ${FileExists} "$R9\$R8"
          ${${_MOZFUNC_UN}DeleteShortcutsFromLog} "$R9\$R8"
        ${EndIf}
        ClearErrors
        FindNext $R6 $R8
      ${Loop}

      ClearErrors

      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Pop $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro DeleteShortcutsCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call DeleteShortcuts
  !verbose pop
!macroend

!macro un.DeleteShortcutsCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.DeleteShortcuts
  !verbose pop
!macroend

!macro un.DeleteShortcuts
  !ifndef un.DeleteShortcuts
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro DeleteShortcuts

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend


################################################################################
# Macros for parsing and updating the uninstall.log

/**
 * Updates the uninstall.log with new files added by software update.
 *
 * When modifying this macro be aware that LineFind uses all registers except
 * $R0-$R3 and TextCompareNoDetails uses all registers except $R0-$R9 so be
 * cautious. Callers of this macro are not affected.
 */
!macro UpdateUninstallLog

  !ifndef UpdateUninstallLog
    !insertmacro FileJoin
    !insertmacro LineFind
    !insertmacro TextCompareNoDetails
    !insertmacro TrimNewLines

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define UpdateUninstallLog "!insertmacro UpdateUninstallLogCall"

    Function UpdateUninstallLog
      Push $R3
      Push $R2
      Push $R1
      Push $R0

      ClearErrors

      GetFullPathName $R3 "$INSTDIR\uninstall"
      ${If} ${FileExists} "$R3\uninstall.update"
        ${LineFind} "$R3\uninstall.update" "" "1:-1" "CleanupUpdateLog"

        GetTempFileName $R2 "$R3"
        FileOpen $R1 "$R2" w
        ${TextCompareNoDetails} "$R3\uninstall.update" "$R3\uninstall.log" "SlowDiff" "CreateUpdateDiff"
        FileClose $R1

        IfErrors +2 0
        ${FileJoin} "$R3\uninstall.log" "$R2" "$R3\uninstall.log"

        ${DeleteFile} "$R2"
      ${EndIf}

      ClearErrors

      Pop $R0
      Pop $R1
      Pop $R2
      Pop $R3
    FunctionEnd

    ; This callback MUST use labels vs. relative line numbers.
    Function CleanupUpdateLog
      StrCpy $R2 "$R9" 12
      StrCmp "$R2" "EXECUTE ADD " +1 skip
      StrCpy $R9 "$R9" "" 12

      Push $R6
      Push $R5
      Push $R4
      StrCpy $R4 ""         ; Initialize to an empty string.
      StrCpy $R6 -1         ; Set the counter to -1 so it will start at 0.

      loop:
      IntOp $R6 $R6 + 1     ; Increment the counter.
      StrCpy $R5 $R9 1 $R6  ; Starting from the counter copy the next char.
      StrCmp $R5 "" copy    ; Are there no more chars?
      StrCmp $R5 "/" +1 +2  ; Is the char a /?
      StrCpy $R5 "\"        ; Replace the char with a \.

      StrCpy $R4 "$R4$R5"
      GoTo loop

      copy:
      StrCpy $R9 "File: \$R4"
      Pop $R6
      Pop $R5
      Pop $R4
      GoTo end

      skip:
      StrCpy $0 "SkipWrite"

      end:
      Push $0
    FunctionEnd

    Function CreateUpdateDiff
      ${TrimNewLines} "$9" $9
      ${If} $9 != ""
        FileWrite $R1 "$9$\r$\n"
      ${EndIf}

      Push 0
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro UpdateUninstallLogCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call UpdateUninstallLog
  !verbose pop
!macroend

/**
 * Copies files from a source directory to a destination directory with logging
 * to the uninstall.log. If any destination files are in use a reboot will be
 * necessary to complete the installation and the reboot flag (see IfRebootFlag
 * in the NSIS documentation).
 *
 * @param   _PATH_TO_SOURCE
 *          Source path to copy the files from. This must not end with a \.
 *
 * @param   _PATH_TO_DESTINATION
 *          Destination path to copy the files to. This must not end with a \.
 *
 * @param   _PREFIX_ERROR_CREATEDIR
 *          Prefix for the directory creation error message. The directory path
 *          will be inserted below this string.
 *
 * @param   _SUFFIX_ERROR_CREATEDIR
 *          Suffix for the directory creation error message. The directory path
 *          will be inserted above this string.
 *
 * $0  = destination file's parent directory used in the create_dir label
 * $R0 = copied value from $R6 (e.g. _PATH_TO_SOURCE)
 * $R1 = copied value from $R7 (e.g. _PATH_TO_DESTINATION)
 * $R2 = string length of the path to source
 * $R3 = relative path from the path to source
 * $R4 = copied value from $R8 (e.g. _PREFIX_ERROR_CREATEDIR)
 * $R5 = copied value from $R9 (e.g. _SUFFIX_ERROR_CREATEDIR)
 * note: the LocateNoDetails macro uses these registers so we copy the values
 *       to other registers.
 * $R6 = initially _PATH_TO_SOURCE and then set to "size" ($R6="" if directory,
 *       $R6="0" if file with /S=)"path\name" in callback
 * $R7 = initially _PATH_TO_DESTINATION and then set to "name" in callback
 * $R8 = initially _PREFIX_ERROR_CREATEDIR and then set to "path" in callback
 * $R9 = initially _SUFFIX_ERROR_CREATEDIR and then set to "path\name" in
 *       callback
 */
!macro CopyFilesFromDir

  !ifndef CopyFilesFromDir
    !insertmacro LocateNoDetails
    !insertmacro OnEndCommon
    !insertmacro WordReplace

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define CopyFilesFromDir "!insertmacro CopyFilesFromDirCall"

    Function CopyFilesFromDir
      Exch $R9
      Exch 1
      Exch $R8
      Exch 2
      Exch $R7
      Exch 3
      Exch $R6
      Push $R5
      Push $R4
      Push $R3
      Push $R2
      Push $R1
      Push $R0
      Push $0

      StrCpy $R0 "$R6"
      StrCpy $R1 "$R7"
      StrCpy $R4 "$R8"
      StrCpy $R5 "$R9"

      StrLen $R2 "$R0"

      ${LocateNoDetails} "$R0" "/L=FD" "CopyFileCallback"

      Pop $0
      Pop $R0
      Pop $R1
      Pop $R2
      Pop $R3
      Pop $R4
      Pop $R5
      Exch $R6
      Exch 3
      Exch $R7
      Exch 2
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    Function CopyFileCallback
      StrCpy $R3 $R8 "" $R2 ; $R3 always begins with a \.

      retry:
      ClearErrors
      StrCmp $R6 "" +1 copy_file
      IfFileExists "$R1$R3\$R7" end +1
      StrCpy $0 "$R1$R3\$R7"

      create_dir:
      ClearErrors
      CreateDirectory "$0"
      IfFileExists "$0" +1 err_create_dir  ; protect against looping.
      ${LogMsg} "Created Directory: $0"
      StrCmp $R6 "" end copy_file

      err_create_dir:
      ${LogMsg} "** ERROR Creating Directory: $0 **"
      MessageBox MB_RETRYCANCEL|MB_ICONQUESTION "$R4$\r$\n$\r$\n$0$\r$\n$\r$\n$R5" IDRETRY retry
      ${OnEndCommon}
      Quit

      copy_file:
      StrCpy $0 "$R1$R3"
      StrCmp "$0" "$INSTDIR" +2 +1
      IfFileExists "$0" +1 create_dir

      ClearErrors
      ${DeleteFile} "$R1$R3\$R7"
      IfErrors +1 dest_clear
      ClearErrors
      Rename "$R1$R3\$R7" "$R1$R3\$R7.moz-delete"
      IfErrors +1 reboot_delete

      ; file will replace destination file on reboot
      Rename "$R9" "$R9.moz-upgrade"
      CopyFiles /SILENT "$R9.moz-upgrade" "$R1$R3"
      Rename /REBOOTOK "$R1$R3\$R7.moz-upgrade" "$R1$R3\$R7"
      ${LogMsg} "Copied File: $R1$R3\$R7.moz-upgrade"
      ${LogMsg} "Delayed Install File (Reboot Required): $R1$R3\$R7"
      GoTo log_uninstall

      ; file will be deleted on reboot
      reboot_delete:
      CopyFiles /SILENT $R9 "$R1$R3"
      Delete /REBOOTOK "$R1$R3\$R7.moz-delete"
      ${LogMsg} "Installed File: $R1$R3\$R7"
      ${LogMsg} "Delayed Delete File (Reboot Required): $R1$R3\$R7.moz-delete"
      GoTo log_uninstall

      ; destination file doesn't exist - coast is clear
      dest_clear:
      CopyFiles /SILENT $R9 "$R1$R3"
      ${LogMsg} "Installed File: $R1$R3\$R7"

      log_uninstall:
      ; If the file is installed into the installation directory remove the
      ; installation directory's path from the file path when writing to the
      ; uninstall.log so it will be a relative path. This allows the same
      ; helper.exe to be used with zip builds if we supply an uninstall.log.
      ${WordReplace} "$R1$R3\$R7" "$INSTDIR" "" "+" $R3
      ${LogUninstall} "File: $R3"

      end:
      Push 0
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro CopyFilesFromDirCall _PATH_TO_SOURCE _PATH_TO_DESTINATION \
                            _PREFIX_ERROR_CREATEDIR _SUFFIX_ERROR_CREATEDIR
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_PATH_TO_SOURCE}"
  Push "${_PATH_TO_DESTINATION}"
  Push "${_PREFIX_ERROR_CREATEDIR}"
  Push "${_SUFFIX_ERROR_CREATEDIR}"
  Call CopyFilesFromDir
  !verbose pop
!macroend

/**
 * Parses the uninstall.log on install to first remove a previous installation's
 * files and then their directories if empty prior to installing.
 *
 * When modifying this macro be aware that LineFind uses all registers except
 * $R0-$R3 so be cautious. Callers of this macro are not affected.
 */
!macro OnInstallUninstall

  !ifndef OnInstallUninstall
    !insertmacro GetParent
    !insertmacro LineFind
    !insertmacro TrimNewLines

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define OnInstallUninstall "!insertmacro OnInstallUninstallCall"

    Function OnInstallUninstall
      Push $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4
      Push $R3
      Push $R2
      Push $R1
      Push $R0
      Push $TmpVal

      IfFileExists "$INSTDIR\uninstall\uninstall.log" +1 end

      ${LogHeader} "Removing Previous Installation"

      ; Copy the uninstall log file to a temporary file
      GetTempFileName $TmpVal
      CopyFiles /SILENT /FILESONLY "$INSTDIR\uninstall\uninstall.log" "$TmpVal"

      ; Delete files
      ${LineFind} "$TmpVal" "/NUL" "1:-1" "RemoveFilesCallback"

      ; Remove empty directories
      ${LineFind} "$TmpVal" "/NUL" "1:-1" "RemoveDirsCallback"

      ; Delete the temporary uninstall log file
      Delete /REBOOTOK "$TmpVal"

      ; Delete the uninstall log file
      Delete "$INSTDIR\uninstall\uninstall.log"

      end:
      ClearErrors

      Pop $TmpVal
      Pop $R0
      Pop $R1
      Pop $R2
      Pop $R3
      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Pop $R9
    FunctionEnd

    Function RemoveFilesCallback
      ${TrimNewLines} "$R9" $R9
      StrCpy $R1 "$R9" 5       ; Copy the first five chars

      StrCmp "$R1" "File:" +1 end
      StrCpy $R9 "$R9" "" 6    ; Copy string starting after the 6th char
      StrCpy $R0 "$R9" 1       ; Copy the first char

      StrCmp "$R0" "\" +1 end  ; If this isn't a relative path goto end
      StrCmp "$R9" "\install.log" end +1 ; Skip the install.log
      StrCmp "$R9" "\MapiProxy_InUse.dll" end +1 ; Skip the MapiProxy_InUse.dll
      StrCmp "$R9" "\mozMapi32_InUse.dll" end +1 ; Skip the mozMapi32_InUse.dll

      StrCpy $R1 "$INSTDIR$R9" ; Copy the install dir path and suffix it with the string
      IfFileExists "$R1" +1 end

      ClearErrors
      Delete "$R1"
      ${Unless} ${Errors}
        ${LogMsg} "Deleted File: $R1"
        Goto end
      ${EndUnless}

      ClearErrors
      Rename "$R1" "$R1.moz-delete"
      ${Unless} ${Errors}
        Delete /REBOOTOK "$R1.moz-delete"
        ${LogMsg} "Delayed Delete File (Reboot Required): $R1.moz-delete"
        GoTo end
      ${EndUnless}

      ; Check if the file exists in the source. If it does the new file will
      ; replace the existing file when the system is rebooted. If it doesn't
      ; the file will be deleted when the system is rebooted.
      ${Unless} ${FileExists} "$EXEDIR\core$R9"
      ${AndUnless}  ${FileExists} "$EXEDIR\optional$R9"
        Delete /REBOOTOK "$R1"
        ${LogMsg} "Delayed Delete File (Reboot Required): $R1"
      ${EndUnless}

      end:
      ClearErrors

      Push 0
    FunctionEnd

    ; Using locate will leave file handles open to some of the directories
    ; which will prevent the deletion of these directories. This parses the
    ; uninstall.log and uses the file entries to find / remove empty
    ; directories.
    Function RemoveDirsCallback
      ${TrimNewLines} "$R9" $R9
      StrCpy $R0 "$R9" 5          ; Copy the first five chars
      StrCmp "$R0" "File:" +1 end

      StrCpy $R9 "$R9" "" 6       ; Copy string starting after the 6th char
      StrCpy $R0 "$R9" 1          ; Copy the first char

      StrCpy $R1 "$INSTDIR$R9"    ; Copy the install dir path and suffix it with the string
      StrCmp "$R0" "\" loop end   ; If this isn't a relative path goto end

      loop:
      ${GetParent} "$R1" $R1         ; Get the parent directory for the path
      StrCmp "$R1" "$INSTDIR" end +1 ; If the directory is the install dir goto end

      IfFileExists "$R1" +1 loop  ; Only try to remove the dir if it exists
      ClearErrors
      RmDir "$R1"     ; Remove the dir
      IfErrors end +1  ; If we fail there is no use trying to remove its parent dir
      ${LogMsg} "Deleted Directory: $R1"
      GoTo loop

      end:
      ClearErrors

      Push 0
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro OnInstallUninstallCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call OnInstallUninstall
  !verbose pop
!macroend

/**
 * Parses the precomplete file to remove an installation's files and
 * directories.
 *
 * @param   _CALLBACK
 *          The function address of a callback function for progress or "false"
 *          if there is no callback function.
 *
 * $R3 = false if all files were deleted or moved to the tobedeleted directory.
 *       true if file(s) could not be moved to the tobedeleted directory.
 * $R4 = Path to temporary precomplete file.
 * $R5 = File handle for the temporary precomplete file.
 * $R6 = String returned from FileRead.
 * $R7 = First seven characters of the string returned from FileRead.
 * $R8 = Temporary file path used to rename files that are in use.
 * $R9 = _CALLBACK
 */
!macro RemovePrecompleteEntries

  !ifndef ${_MOZFUNC_UN}RemovePrecompleteEntries
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetParent
    !insertmacro ${_MOZFUNC_UN_TMP}TrimNewLines
    !insertmacro ${_MOZFUNC_UN_TMP}WordReplace
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}RemovePrecompleteEntries "!insertmacro ${_MOZFUNC_UN}RemovePrecompleteEntriesCall"

    Function ${_MOZFUNC_UN}RemovePrecompleteEntries
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4
      Push $R3

      ${If} ${FileExists} "$INSTDIR\precomplete"
        StrCpy $R3 "false"

        RmDir /r "$INSTDIR\${TO_BE_DELETED}"
        CreateDirectory "$INSTDIR\${TO_BE_DELETED}"
        GetTempFileName $R4 "$INSTDIR\${TO_BE_DELETED}"
        Delete "$R4"
        Rename "$INSTDIR\precomplete" "$R4"

        ClearErrors
        ; Rename and then remove files
        FileOpen $R5 "$R4" r
        ${Do}
          FileRead $R5 $R6
          ${If} ${Errors}
            ${Break}
          ${EndIf}

          ${${_MOZFUNC_UN}TrimNewLines} "$R6" $R6
          ; Replace all occurrences of "/" with "\".
          ${${_MOZFUNC_UN}WordReplace} "$R6" "/" "\" "+" $R6

          ; Copy the first 7 chars
          StrCpy $R7 "$R6" 7
          ${If} "$R7" == "remove "
            ; Copy the string starting after the 8th char
            StrCpy $R6 "$R6" "" 8
            ; Copy all but the last char to remove the double quote.
            StrCpy $R6 "$R6" -1
            ${If} ${FileExists} "$INSTDIR\$R6"
              ${Unless} "$R9" == "false"
                Call $R9
              ${EndUnless}

              ClearErrors
              Delete "$INSTDIR\$R6"
              ${If} ${Errors}
                GetTempFileName $R8 "$INSTDIR\${TO_BE_DELETED}"
                Delete "$R8"
                ClearErrors
                Rename "$INSTDIR\$R6" "$R8"
                ${Unless} ${Errors}
                  Delete /REBOOTOK "$R8"

                  ClearErrors
                ${EndUnless}
!ifdef __UNINSTALL__
                ${If} ${Errors}
                  Delete /REBOOTOK "$INSTDIR\$R6"
                  StrCpy $R3 "true"
                  ClearErrors
                ${EndIf}
!endif
              ${EndIf}
            ${EndIf}
          ${ElseIf} "$R7" == "rmdir $\""
            ; Copy the string starting after the 7th char.
            StrCpy $R6 "$R6" "" 7
            ; Copy all but the last two chars to remove the slash and the double quote.
            StrCpy $R6 "$R6" -2
            ${If} ${FileExists} "$INSTDIR\$R6"
              ; Ignore directory removal errors
              RmDir "$INSTDIR\$R6"
              ClearErrors
            ${EndIf}
          ${EndIf}
        ${Loop}
        FileClose $R5

        ; Delete the temporary precomplete file
        Delete /REBOOTOK "$R4"

        RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}"

        ${If} ${RebootFlag}
        ${AndIf} "$R3" == "false"
          ; Clear the reboot flag if all files were deleted or moved to the
          ; tobedeleted directory.
          SetRebootFlag false
        ${EndIf}
      ${EndIf}

      ClearErrors

      Pop $R3
      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro RemovePrecompleteEntriesCall _CALLBACK
  !verbose push
  Push "${_CALLBACK}"
  !verbose ${_MOZFUNC_VERBOSE}
  Call RemovePrecompleteEntries
  !verbose pop
!macroend

!macro un.RemovePrecompleteEntriesCall _CALLBACK
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_CALLBACK}"
  Call un.RemovePrecompleteEntries
  !verbose pop
!macroend

!macro un.RemovePrecompleteEntries
  !ifndef un.RemovePrecompleteEntries
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro RemovePrecompleteEntries

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Parses the uninstall.log to unregister dll's, remove files, and remove
 * empty directories for this installation.
 *
 * When modifying this macro be aware that LineFind uses all registers except
 * $R0-$R3 so be cautious. Callers of this macro are not affected.
 */
!macro un.ParseUninstallLog

  !ifndef un.ParseUninstallLog
    !insertmacro un.GetParent
    !insertmacro un.LineFind
    !insertmacro un.TrimNewLines

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define un.ParseUninstallLog "!insertmacro un.ParseUninstallLogCall"

    Function un.ParseUninstallLog
      Push $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4
      Push $R3
      Push $R2
      Push $R1
      Push $R0
      Push $TmpVal

      IfFileExists "$INSTDIR\uninstall\uninstall.log" +1 end

      ; Copy the uninstall log file to a temporary file
      GetTempFileName $TmpVal
      CopyFiles /SILENT /FILESONLY "$INSTDIR\uninstall\uninstall.log" "$TmpVal"

      ; Unregister DLL's
      ${un.LineFind} "$TmpVal" "/NUL" "1:-1" "un.UnRegDLLsCallback"

      ; Delete files
      ${un.LineFind} "$TmpVal" "/NUL" "1:-1" "un.RemoveFilesCallback"

      ; Remove empty directories
      ${un.LineFind} "$TmpVal" "/NUL" "1:-1" "un.RemoveDirsCallback"

      ; Delete the temporary uninstall log file
      Delete /REBOOTOK "$TmpVal"

      end:

      Pop $TmpVal
      Pop $R0
      Pop $R1
      Pop $R2
      Pop $R3
      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Pop $R9
    FunctionEnd

    Function un.RemoveFilesCallback
      ${un.TrimNewLines} "$R9" $R9
      StrCpy $R1 "$R9" 5

      StrCmp "$R1" "File:" +1 end
      StrCpy $R9 "$R9" "" 6
      StrCpy $R0 "$R9" 1

      StrCpy $R1 "$INSTDIR$R9"
      StrCmp "$R0" "\" +2 +1
      StrCpy $R1 "$R9"

      IfFileExists "$R1" +1 end
      Delete "$R1"
      IfErrors +1 end
      ClearErrors
      Rename "$R1" "$R1.moz-delete"
      IfErrors +1 +3
      Delete /REBOOTOK "$R1"
      GoTo end

      Delete /REBOOTOK "$R1.moz-delete"

      end:
      ClearErrors

      Push 0
    FunctionEnd

    Function un.UnRegDLLsCallback
      ${un.TrimNewLines} "$R9" $R9
      StrCpy $R1 "$R9" 7

      StrCmp $R1 "DLLReg:" +1 end
      StrCpy $R9 "$R9" "" 8
      StrCpy $R0 "$R9" 1

      StrCpy $R1 "$INSTDIR$R9"
      StrCmp $R0 "\" +2 +1
      StrCpy $R1 "$R9"

      ${UnregisterDLL} $R1

      end:
      ClearErrors

      Push 0
    FunctionEnd

    ; Using locate will leave file handles open to some of the directories
    ; which will prevent the deletion of these directories. This parses the
    ; uninstall.log and uses the file entries to find / remove empty
    ; directories.
    Function un.RemoveDirsCallback
      ${un.TrimNewLines} "$R9" $R9
      StrCpy $R0 "$R9" 5          ; Copy the first five chars
      StrCmp "$R0" "File:" +1 end

      StrCpy $R9 "$R9" "" 6       ; Copy string starting after the 6th char
      StrCpy $R0 "$R9" 1          ; Copy the first char

      StrCpy $R1 "$INSTDIR$R9"    ; Copy the install dir path and suffix it with the string
      StrCmp "$R0" "\" loop       ; If this is a relative path goto the loop
      StrCpy $R1 "$R9"            ; Already a full path so copy the string

      loop:
      ${un.GetParent} "$R1" $R1   ; Get the parent directory for the path
      StrCmp "$R1" "$INSTDIR" end ; If the directory is the install dir goto end

      ; We only try to remove empty directories but the Desktop, StartMenu, and
      ; QuickLaunch directories can be empty so guard against removing them.
      SetShellVarContext all          ; Set context to all users
      StrCmp "$R1" "$DESKTOP" end     ; All users desktop
      StrCmp "$R1" "$STARTMENU" end   ; All users start menu

      SetShellVarContext current      ; Set context to all users
      StrCmp "$R1" "$DESKTOP" end     ; Current user desktop
      StrCmp "$R1" "$STARTMENU" end   ; Current user start menu
      StrCmp "$R1" "$QUICKLAUNCH" end ; Current user quick launch

      IfFileExists "$R1" +1 +3  ; Only try to remove the dir if it exists
      ClearErrors
      RmDir "$R1"    ; Remove the dir
      IfErrors end   ; If we fail there is no use trying to remove its parent dir

      StrCmp "$R0" "\" loop end  ; Only loop when the path is relative to the install dir

      end:
      ClearErrors

      Push 0
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro un.ParseUninstallLogCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.ParseUninstallLog
  !verbose pop
!macroend

/**
 * Finds a valid Start Menu shortcut in the uninstall log and returns the
 * relative path from the Start Menu's Programs directory to the shortcut's
 * directory.
 *
 * When modifying this macro be aware that LineFind uses all registers except
 * $R0-$R3 so be cautious. Callers of this macro are not affected.
 *
 * @return  _REL_PATH_TO_DIR
 *          The relative path to the application's Start Menu directory from the
 *          Start Menu's Programs directory.
 */
!macro FindSMProgramsDir

  !ifndef FindSMProgramsDir
    !insertmacro GetParent
    !insertmacro LineFind
    !insertmacro TrimNewLines

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define FindSMProgramsDir "!insertmacro FindSMProgramsDirCall"

    Function FindSMProgramsDir
      Exch $R3
      Push $R2
      Push $R1
      Push $R0

      StrCpy $R3 ""
      ${If} ${FileExists} "$INSTDIR\uninstall\uninstall.log"
        ${LineFind} "$INSTDIR\uninstall\uninstall.log" "/NUL" "1:-1" "FindSMProgramsDirRelPath"
      ${EndIf}
      ClearErrors

      Pop $R0
      Pop $R1
      Pop $R2
      Exch $R3
    FunctionEnd

    ; This callback MUST use labels vs. relative line numbers.
    Function FindSMProgramsDirRelPath
      Push 0
      ${TrimNewLines} "$R9" $R9
      StrCpy $R4 "$R9" 5

      StrCmp "$R4" "File:" +1 end_FindSMProgramsDirRelPath
      StrCpy $R9 "$R9" "" 6
      StrCpy $R4 "$R9" 1

      StrCmp "$R4" "\" end_FindSMProgramsDirRelPath +1

      SetShellVarContext all
      ${GetLongPath} "$SMPROGRAMS" $R4
      StrLen $R2 "$R4"
      StrCpy $R1 "$R9" $R2
      StrCmp "$R1" "$R4" +1 end_FindSMProgramsDirRelPath
      IfFileExists "$R9" +1 end_FindSMProgramsDirRelPath
      ShellLink::GetShortCutTarget "$R9"
      Pop $R0
      StrCmp "$INSTDIR\${FileMainEXE}" "$R0" +1 end_FindSMProgramsDirRelPath
      ${GetParent} "$R9" $R3
      IntOp $R2 $R2 + 1
      StrCpy $R3 "$R3" "" $R2

      Pop $R4             ; Remove the previously pushed 0 from the stack and
      push "StopLineFind" ; push StopLineFind to stop finding more lines.

      end_FindSMProgramsDirRelPath:
      ClearErrors

    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro FindSMProgramsDirCall _REL_PATH_TO_DIR
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call FindSMProgramsDir
  Pop ${_REL_PATH_TO_DIR}
  !verbose pop
!macroend


################################################################################
# Macros for custom branding

/**
 * Sets BrandFullName and / or BrandShortName to values provided in the specified
 * ini file and defaults to BrandShortName and BrandFullName as defined in
 * branding.nsi when the associated ini file entry is not specified.
 *
 * ini file format:
 * [Branding]
 * BrandFullName=Custom Full Name
 * BrandShortName=Custom Short Name
 *
 * @param   _PATH_TO_INI
 *          Path to the ini file.
 *
 * $R6 = return value from ReadINIStr
 * $R7 = stores BrandShortName
 * $R8 = stores BrandFullName
 * $R9 = _PATH_TO_INI
 */
!macro SetBrandNameVars

  !ifndef ${_MOZFUNC_UN}SetBrandNameVars
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}WordReplace
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    ; Prevent declaring vars twice when the SetBrandNameVars macro is
    ; inserted into both the installer and uninstaller.
    !ifndef SetBrandNameVars
      Var BrandFullName
      Var BrandFullNameDA
      Var BrandShortName
      Var BrandProductName
    !endif

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}SetBrandNameVars "!insertmacro ${_MOZFUNC_UN}SetBrandNameVarsCall"

    Function ${_MOZFUNC_UN}SetBrandNameVars
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5

      StrCpy $R8 "${BrandFullName}"
      StrCpy $R7 "${BrandShortName}"
      StrCpy $R6 "${BrandProductName}"

      IfFileExists "$R9" +1 finish

      ClearErrors
      ReadINIStr $R5 $R9 "Branding" "BrandFullName"
      IfErrors +2 +1
      StrCpy $R8 "$R5"

      ClearErrors
      ReadINIStr $R5 $R9 "Branding" "BrandShortName"
      IfErrors +2 +1
      StrCpy $R7 "$R5"

      ClearErrors
      ReadINIStr $R5 $R9 "Branding" "BrandProductName"
      IfErrors +2 +1
      StrCpy $R6 "$R5"

      finish:
      StrCpy $BrandFullName "$R8"
      ${${_MOZFUNC_UN}WordReplace} "$R8" "&" "&&" "+" $R8
      StrCpy $BrandFullNameDA "$R8"
      StrCpy $BrandShortName "$R7"
      StrCpy $BrandProductName "$R6"

      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro SetBrandNameVarsCall _PATH_TO_INI
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_PATH_TO_INI}"
  Call SetBrandNameVars
  !verbose pop
!macroend

!macro un.SetBrandNameVarsCall _PATH_TO_INI
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_PATH_TO_INI}"
  Call un.SetBrandNameVars
  !verbose pop
!macroend

!macro un.SetBrandNameVars
  !ifndef un.SetBrandNameVars
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro SetBrandNameVars

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Replaces the wizard's header image with the one specified.
 *
 * @param   _PATH_TO_IMAGE
 *          Fully qualified path to the bitmap to use for the header image.
 *
 * $R8 = hwnd for the control returned from GetDlgItem.
 * $R9 = _PATH_TO_IMAGE
 */
!macro ChangeMUIHeaderImage

  !ifndef ${_MOZFUNC_UN}ChangeMUIHeaderImage
    Var hHeaderBitmap

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}ChangeMUIHeaderImage "!insertmacro ${_MOZFUNC_UN}ChangeMUIHeaderImageCall"

    Function ${_MOZFUNC_UN}ChangeMUIHeaderImage
      Exch $R9
      Push $R8

      GetDlgItem $R8 $HWNDPARENT 1046
      ${SetStretchedImageOLE} $R8 "$R9" $hHeaderBitmap
      ; There is no way to specify a show function for a custom page so hide
      ; and then show the control to force the bitmap to redraw.
      ShowWindow $R8 ${SW_HIDE}
      ShowWindow $R8 ${SW_SHOW}

      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro ChangeMUIHeaderImageCall _PATH_TO_IMAGE
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_PATH_TO_IMAGE}"
  Call ChangeMUIHeaderImage
  !verbose pop
!macroend

!macro un.ChangeMUIHeaderImageCall _PATH_TO_IMAGE
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_PATH_TO_IMAGE}"
  Call un.ChangeMUIHeaderImage
  !verbose pop
!macroend

!macro un.ChangeMUIHeaderImage
  !ifndef un.ChangeMUIHeaderImage
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro ChangeMUIHeaderImage

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Replaces the sidebar image on the wizard's welcome and finish pages.
 *
 * @param   _PATH_TO_IMAGE
 *          Fully qualified path to the bitmap to use for the header image.
 *
 * $R8 = hwnd for the bitmap control
 * $R9 = _PATH_TO_IMAGE
 */
!macro ChangeMUISidebarImage

  !ifndef ${_MOZFUNC_UN}ChangeMUISidebarImage
    Var hSidebarBitmap

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}ChangeMUISidebarImage "!insertmacro ${_MOZFUNC_UN}ChangeMUISidebarImageCall"

    Function ${_MOZFUNC_UN}ChangeMUISidebarImage
      Exch $R9
      Push $R8

      ; Make sure we're not about to leak an existing handle.
      ${If} $hSidebarBitmap <> 0
        System::Call "gdi32::DeleteObject(p $hSidebarBitmap)"
        StrCpy $hSidebarBitmap 0
      ${EndIf}
      ; The controls on the welcome and finish pages aren't in the dialog
      ; template, they're always created manually from the INI file, so we need
      ; to query it to find the right HWND.
      ReadINIStr $R8 "$PLUGINSDIR\ioSpecial.ini" "Field 1" "HWND"
      ${SetStretchedImageOLE} $R8 "$R9" $hSidebarBitmap

      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro ChangeMUISidebarImageCall _PATH_TO_IMAGE
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_PATH_TO_IMAGE}"
  Call ChangeMUISidebarImage
  !verbose pop
!macroend

!macro un.ChangeMUISidebarImageCall _PATH_TO_IMAGE
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_PATH_TO_IMAGE}"
  Call un.ChangeMUISidebarImage
  !verbose pop
!macroend

!macro un.ChangeMUISidebarImage
  !ifndef un.ChangeMUISidebarImage
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro ChangeMUISidebarImage

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

################################################################################
# User interface callback helper defines and macros

/* Install type defines */
!ifndef INSTALLTYPE_BASIC
  !define INSTALLTYPE_BASIC     1
!endif

!ifndef INSTALLTYPE_CUSTOM
  !define INSTALLTYPE_CUSTOM    2
!endif

/**
 * Checks whether to display the current page (e.g. if not performing a custom
 * install don't display the custom pages).
 */
!macro CheckCustomCommon

  !ifndef CheckCustomCommon
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define CheckCustomCommon "!insertmacro CheckCustomCommonCall"

    Function CheckCustomCommon

      ; Abort if not a custom install
      IntCmp $InstallType ${INSTALLTYPE_CUSTOM} +2 +1 +1
      Abort

    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro CheckCustomCommonCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call CheckCustomCommon
  !verbose pop
!macroend

/**
 * Unloads dll's and releases references when the installer and uninstaller
 * exit.
 */
!macro OnEndCommon

  !ifndef ${_MOZFUNC_UN}OnEndCommon
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}UnloadUAC
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}OnEndCommon "!insertmacro ${_MOZFUNC_UN}OnEndCommonCall"

    Function ${_MOZFUNC_UN}OnEndCommon

      ${${_MOZFUNC_UN}UnloadUAC}
      StrCmp $hHeaderBitmap "" +3 +1
      System::Call "gdi32::DeleteObject(i s)" $hHeaderBitmap
      StrCpy $hHeaderBitmap ""
      ; If ChangeMUISidebarImage was called, then we also need to clean up the
      ; GDI bitmap handle that it would have created.
      !ifdef ${_MOZFUNC_UN}ChangeMUISidebarImage
        StrCmp $hSidebarBitmap "" +3 +1
        System::Call "gdi32::DeleteObject(i s)" $hSidebarBitmap
        StrCpy $hSidebarBitmap ""
      !endif

      System::Free 0

    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro OnEndCommonCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call OnEndCommon
  !verbose pop
!macroend

!macro un.OnEndCommonCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.OnEndCommon
  !verbose pop
!macroend

!macro un.OnEndCommon
  !ifndef un.OnEndCommon
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro OnEndCommon

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Reads a flag option from the command line and sets a variable with its state,
 * if the option is present on the command line.
 *
 * @param   FULL_COMMAND_LINE
 *          The entire installer command line, such as from ${GetParameters}
 * @param   OPTION
 *          Name of the option to look for
 * @param   OUTPUT
 *          Variable/register to write the output to. Will be set to "0" if the
 *          option was present with the value "false", will be set to "1" if the
 *          option was present with another value, and will be untouched if the
 *          option was not on the command line at all.
 */
!macro InstallGetOption FULL_COMMAND_LINE OPTION OUTPUT
  Push $0
  ClearErrors
  ${GetOptions} ${FULL_COMMAND_LINE} "/${OPTION}" $0
  ${IfNot} ${Errors}
    ; Any valid command-line option triggers a silent installation.
    SetSilent silent

    ${If} $0 == "=false"
      StrCpy ${OUTPUT} "0"
    ${Else}
      StrCpy ${OUTPUT} "1"
    ${EndIf}
  ${EndIf}
  Pop $0
!macroend
!define InstallGetOption "!insertmacro InstallGetOption"

/**
 * Called from the installer's .onInit function not to be confused with the
 * uninstaller's .onInit or the uninstaller's un.onInit functions.
 *
 * @param   _WARN_UNSUPPORTED_MSG
 *          Message displayed when the Windows version is not supported.
 *
 * $R4 = keeps track of whether a custom install path was specified on either
 *       the command line or in an INI file
 * $R5 = return value from the GetSize macro
 * $R6 = general string values, return value from GetTempFileName, return
 *       value from the GetSize macro
 * $R7 = full path to the configuration ini file
 * $R8 = used for OS Version and Service Pack detection and the return value
 *       from the GetParameters macro
 * $R9 = _WARN_UNSUPPORTED_MSG
 */
!macro InstallOnInitCommon

  !ifndef InstallOnInitCommon
    !insertmacro ElevateUAC
    !insertmacro GetOptions
    !insertmacro GetParameters
    !insertmacro GetSize

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define InstallOnInitCommon "!insertmacro InstallOnInitCommonCall"

    Function InstallOnInitCommon
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4

      ; Don't install on systems that don't support SSE2. The parameter value of
      ; 10 is for PF_XMMI64_INSTRUCTIONS_AVAILABLE which will check whether the
      ; SSE2 instruction set is available.
      System::Call "kernel32::IsProcessorFeaturePresent(i 10)i .R8"
      ${If} "$R8" == "0"
        MessageBox MB_OK|MB_ICONSTOP "$R9"
        ; Nothing initialized so no need to call OnEndCommon
        Quit
      ${EndIf}

      ; Windows 8.1/Server 2012 R2 and lower are not supported.
      ${Unless} ${AtLeastWin10}
        MessageBox MB_OK|MB_ICONSTOP "$R9"
        ; Nothing initialized so no need to call OnEndCommon
        Quit
      ${EndUnless}

      !ifdef HAVE_64BIT_BUILD
        SetRegView 64
      !endif

      StrCpy $R4 0 ; will be set to 1 if a custom install path is set

      ${GetParameters} $R8
      ${If} $R8 != ""
        ; Default install type
        StrCpy $InstallType ${INSTALLTYPE_BASIC}

        ${Unless} ${Silent}
          ; NSIS should check for /S for us, but we've had issues with it such
          ; as bug 506867 in the past, so we'll check for it ourselves also.
          ClearErrors
          ${GetOptions} "$R8" "/S" $R7
          ${Unless} ${Errors}
            SetSilent silent
          ${Else}
            ; NSIS dropped support for the deprecated -ms argument, but we don't
            ; want to break backcompat, so we'll check for it here too.
            ClearErrors
            ${GetOptions} "$R8" "-ms" $R7
            ${Unless} ${Errors}
              SetSilent silent
            ${EndUnless}
          ${EndUnless}
        ${EndUnless}

        ; Support for specifying an installation configuration file.
        ClearErrors
        ${GetOptions} "$R8" "/INI=" $R7
        ${Unless} ${Errors}
          ; The configuration file must also exist
          ${If} ${FileExists} "$R7"
            ; Any valid command-line option triggers a silent installation.
            SetSilent silent

            ReadINIStr $R8 $R7 "Install" "InstallDirectoryName"
            ${If} $R8 != ""
              StrCpy $R4 1
              !ifdef HAVE_64BIT_BUILD
                StrCpy $INSTDIR "$PROGRAMFILES64\$R8"
              !else
                StrCpy $INSTDIR "$PROGRAMFILES32\$R8"
              !endif
            ${Else}
              ReadINIStr $R8 $R7 "Install" "InstallDirectoryPath"
              ${If} $R8 != ""
                StrCpy $R4 1
                StrCpy $INSTDIR "$R8"
              ${EndIf}
            ${EndIf}

            ReadINIStr $R8 $R7 "Install" "DesktopShortcut"
            ${If} $R8 == "false"
              StrCpy $AddDesktopSC "0"
            ${Else}
              StrCpy $AddDesktopSC "1"
            ${EndIf}

            ReadINIStr $R8 $R7 "Install" "StartMenuShortcuts"
            ${If} $R8 == "false"
              StrCpy $AddStartMenuSC "0"
            ${Else}
              StrCpy $AddStartMenuSC "1"
            ${EndIf}

            ; We still accept the plural version for backwards compatibility,
            ; but the singular version takes priority.
            ClearErrors
            ReadINIStr $R8 $R7 "Install" "StartMenuShortcut"
            ${If} $R8 == "false"
              StrCpy $AddStartMenuSC "0"
            ${ElseIfNot} ${Errors}
              StrCpy $AddStartMenuSC "1"
            ${EndIf}

            !ifdef MOZ_PRIVATE_BROWSING
              ReadINIStr $R8 $R7 "Install" "PrivateBrowsingShortcut"
              ${If} $R8 == "false"
                StrCpy $AddPrivateBrowsingSC "0"
              ${ElseIfNot} ${Errors}
                StrCpy $AddPrivateBrowsingSC "1"
              ${EndIf}
            !endif

            ReadINIStr $R8 $R7 "Install" "TaskbarShortcut"
            ${If} $R8 == "false"
              StrCpy $AddTaskbarSC "0"
            ${Else}
              StrCpy $AddTaskbarSC "1"
            ${EndIf}

            ReadINIStr $R8 $R7 "Install" "MaintenanceService"
            ${If} $R8 == "false"
              StrCpy $InstallMaintenanceService "0"
            ${Else}
              StrCpy $InstallMaintenanceService "1"
            ${EndIf}

            ReadINIStr $R8 $R7 "Install" "RegisterDefaultAgent"
            ${If} $R8 == "false"
              StrCpy $RegisterDefaultAgent "0"
            ${Else}
              StrCpy $RegisterDefaultAgent "1"
            ${EndIf}

            !ifdef MOZ_OPTIONAL_EXTENSIONS
              ReadINIStr $R8 $R7 "Install" "OptionalExtensions"
              ${If} $R8 == "false"
                StrCpy $InstallOptionalExtensions "0"
              ${Else}
                StrCpy $InstallOptionalExtensions "1"
              ${EndIf}
            !endif

            !ifndef NO_STARTMENU_DIR
              ReadINIStr $R8 $R7 "Install" "StartMenuDirectoryName"
              ${If} $R8 != ""
                StrCpy $StartMenuDir "$R8"
              ${EndIf}
            !endif
          ${EndIf}
        ${EndUnless}

        ; Check for individual command line parameters after evaluating the INI
        ; file, because these should override the INI entires.
        ${GetParameters} $R8
        ${GetOptions} $R8 "/InstallDirectoryName=" $R7
        ${If} $R7 != ""
          StrCpy $R4 1
          !ifdef HAVE_64BIT_BUILD
            StrCpy $INSTDIR "$PROGRAMFILES64\$R7"
          !else
            StrCpy $INSTDIR "$PROGRAMFILES32\$R7"
          !endif
        ${Else}
          ${GetOptions} $R8 "/InstallDirectoryPath=" $R7
          ${If} $R7 != ""
            StrCpy $R4 1
            StrCpy $INSTDIR "$R7"
          ${EndIf}
        ${EndIf}

        ${InstallGetOption} $R8 "DesktopShortcut" $AddDesktopSC
        ${InstallGetOption} $R8 "StartMenuShortcuts" $AddStartMenuSC
        ; We still accept the plural version for backwards compatibility,
        ; but the singular version takes priority.
        ${InstallGetOption} $R8 "StartMenuShortcut" $AddStartMenuSC
        !ifdef MOZ_PRIVATE_BROWSING
          ${InstallGetOption} $R8 "PrivateBrowsingShortcut" $AddPrivateBrowsingSC
        !endif
        ${InstallGetOption} $R8 "TaskbarShortcut" $AddTaskbarSC
        ${InstallGetOption} $R8 "MaintenanceService" $InstallMaintenanceService
        ${InstallGetOption} $R8 "RegisterDefaultAgent" $RegisterDefaultAgent
        !ifdef MOZ_OPTIONAL_EXTENSIONS
          ${InstallGetOption} $R8 "OptionalExtensions" $InstallOptionalExtensions
        !endif

        ; Installing the service always requires elevated privileges.
        ${If} $InstallMaintenanceService == "1"
          ${ElevateUAC}
        ${EndIf}
      ${EndIf}

      ${If} $R4 == 1
        ; Any valid command-line option triggers a silent installation.
        SetSilent silent

        ; Quit if we are unable to create the installation directory or we are
        ; unable to write to a file in the installation directory.
        ClearErrors
        ${If} ${FileExists} "$INSTDIR"
          GetTempFileName $R6 "$INSTDIR"
          FileOpen $R5 "$R6" w
          FileWrite $R5 "Write Access Test"
          FileClose $R5
          Delete $R6
          ${If} ${Errors}
            ; Attempt to elevate and then try again.
            ${ElevateUAC}
            GetTempFileName $R6 "$INSTDIR"
            FileOpen $R5 "$R6" w
            FileWrite $R5 "Write Access Test"
            FileClose $R5
            Delete $R6
            ${If} ${Errors}
              ; Nothing initialized so no need to call OnEndCommon
              Quit
            ${EndIf}
          ${EndIf}
        ${Else}
          CreateDirectory "$INSTDIR"
          ${If} ${Errors}
            ; Attempt to elevate and then try again.
            ${ElevateUAC}
            CreateDirectory "$INSTDIR"
            ${If} ${Errors}
              ; Nothing initialized so no need to call OnEndCommon
              Quit
            ${EndIf}
          ${EndIf}
        ${EndIf}
      ${Else}
        ; If we weren't given a custom path parameter, then try to elevate now.
        ; We'll check the user's permission level later on to determine the
        ; default install path (which will be the real install path for /S).
        ; If an INI file is used, we try to elevate down that path when needed.
        ${ElevateUAC}
      ${EndIf}

      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro InstallOnInitCommonCall _WARN_UNSUPPORTED_MSG
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_WARN_UNSUPPORTED_MSG}"
  Call InstallOnInitCommon
  !verbose pop
!macroend

/**
 * Called from the uninstaller's .onInit function not to be confused with the
 * installer's .onInit or the uninstaller's un.onInit functions.
 */
!macro UninstallOnInitCommon

  !ifndef UninstallOnInitCommon
    !insertmacro ElevateUAC
    !insertmacro GetLongPath
    !insertmacro GetOptions
    !insertmacro GetParameters
    !insertmacro GetParent
    !insertmacro UnloadUAC
    !insertmacro UpdateShortcutAppModelIDs
    !insertmacro UpdateUninstallLog

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define UninstallOnInitCommon "!insertmacro UninstallOnInitCommonCall"

    Function UninstallOnInitCommon
      ; Prevents breaking apps that don't use SetBrandNameVars
      !ifdef SetBrandNameVars
        ${SetBrandNameVars} "$EXEDIR\distribution\setup.ini"
      !endif

      ; Prevent launching the application when a reboot is required and this
      ; executable is the main application executable
      IfFileExists "$EXEDIR\${FileMainEXE}.moz-upgrade" +1 +4
      MessageBox MB_YESNO|MB_ICONEXCLAMATION "$(WARN_RESTART_REQUIRED_UPGRADE)" IDNO +2
      Reboot
      Quit ; Nothing initialized so no need to call OnEndCommon

      ${GetParent} "$EXEDIR" $INSTDIR
      ${GetLongPath} "$INSTDIR" $INSTDIR
      IfFileExists "$INSTDIR\${FileMainEXE}" +2 +1
      Quit ; Nothing initialized so no need to call OnEndCommon

!ifmacrodef InitHashAppModelId
      ; setup the application model id registration value
      !ifdef AppName
      ${InitHashAppModelId} "$INSTDIR" "Software\Mozilla\${AppName}\TaskBarIDs"
      !endif
!endif

      ; Prevents breaking apps that don't use SetBrandNameVars
      !ifdef SetBrandNameVars
        ${SetBrandNameVars} "$INSTDIR\distribution\setup.ini"
      !endif

      ; Application update uses a directory named tobedeleted in the $INSTDIR to
      ; delete files on OS reboot when they are in use. Try to delete this
      ; directory if it exists.
      ${If} ${FileExists} "$INSTDIR\${TO_BE_DELETED}"
        RmDir /r "$INSTDIR\${TO_BE_DELETED}"
      ${EndIf}

      ; Prevent all operations (e.g. set as default, postupdate, etc.) when a
      ; reboot is required and the executable launched is helper.exe
      IfFileExists "$INSTDIR\${FileMainEXE}.moz-upgrade" +1 +4
      MessageBox MB_YESNO|MB_ICONEXCLAMATION "$(WARN_RESTART_REQUIRED_UPGRADE)" IDNO +2
      Reboot
      Quit ; Nothing initialized so no need to call OnEndCommon

      !ifdef HAVE_64BIT_BUILD
        SetRegView 64
      !endif

      ${GetParameters} $R0

      ${Unless} ${Silent}
        ; Manually check for /S in the command line due to Bug 506867
        ClearErrors
        ${GetOptions} "$R0" "/S" $R2
        ${Unless} ${Errors}
          SetSilent silent
        ${Else}
          ; Support for the deprecated -ms command line argument.
          ClearErrors
          ${GetOptions} "$R0" "-ms" $R2
          ${Unless} ${Errors}
            SetSilent silent
          ${EndUnless}
        ${EndUnless}
      ${EndUnless}

      StrCmp "$R0" "" continue +1

      ; Require elevation if the user can elevate
      hideshortcuts:
      ClearErrors
      ${GetOptions} "$R0" "/HideShortcuts" $R2
      IfErrors showshortcuts +1
!ifndef NONADMIN_ELEVATE
      ${ElevateUAC}
!endif
      ${HideShortcuts}
      GoTo finish

      ; Require elevation if the user can elevate
      showshortcuts:
      ClearErrors
      ${GetOptions} "$R0" "/ShowShortcuts" $R2
      IfErrors defaultappuser +1
!ifndef NONADMIN_ELEVATE
      ${ElevateUAC}
!endif
      ${ShowShortcuts}
      GoTo finish

      ; Require elevation if the the StartMenuInternet registry keys require
      ; updating and the user can elevate
      defaultappuser:
      ClearErrors
      ${GetOptions} "$R0" "/SetAsDefaultAppUser" $R2
      IfErrors defaultappglobal +1
      ${SetAsDefaultAppUser}
      GoTo finish

      ; Require elevation if the user can elevate
      defaultappglobal:
      ClearErrors
      ${GetOptions} "$R0" "/SetAsDefaultAppGlobal" $R2
      IfErrors postupdate +1
      ${ElevateUAC}
      ${SetAsDefaultAppGlobal}
      GoTo finish

      ; Do not attempt to elevate. The application launching this executable is
      ; responsible for elevation if it is required.
      postupdate:
      ${WordReplace} "$R0" "$\"" "" "+" $R0
      ClearErrors
      ${GetOptions} "$R0" "/PostUpdate" $R2
      IfErrors continue +1
      ; If the uninstall.log does not exist don't perform post update
      ; operations. This prevents updating the registry for zip builds.
      IfFileExists "$EXEDIR\uninstall.log" +2 +1
      Quit ; Nothing initialized so no need to call OnEndCommon
      ${PostUpdate}
      ClearErrors
      ${GetOptions} "$R0" "/UninstallLog=" $R2
      IfErrors updateuninstalllog +1
      StrCmp "$R2" "" finish +1
      GetFullPathName $R3 "$R2"
      IfFileExists "$R3" +1 finish
      Delete "$INSTDIR\uninstall\*wizard*"
      Delete "$INSTDIR\uninstall\uninstall.log"
      CopyFiles /SILENT /FILESONLY "$R3" "$INSTDIR\uninstall\"
      ${GetParent} "$R3" $R4
      Delete "$R3"
      RmDir "$R4"
      GoTo finish

      ; Do not attempt to elevate. The application launching this executable is
      ; responsible for elevation if it is required.
      updateuninstalllog:
      ${UpdateUninstallLog}

      finish:
      ${UnloadUAC}
      ${RefreshShellIcons}
      Quit ; Nothing initialized so no need to call OnEndCommon

      continue:

      ; If the uninstall.log does not exist don't perform uninstall
      ; operations. This prevents running the uninstaller for zip builds.
      IfFileExists "$INSTDIR\uninstall\uninstall.log" +2 +1
      Quit ; Nothing initialized so no need to call OnEndCommon

      ; When silent, try to avoid elevation if we have a chance to succeed.  We
      ; can succeed when we can write to (hence delete from) the install
      ; directory and when we can clean up all registry entries.  Now, the
      ; installer when elevated writes privileged registry entries for the use
      ; of the Maintenance Service, even when the service is not and will not be
      ; installed.  (In fact, even when a service installed in the future will
      ; never update the installation, for example due to not being in a
      ; privileged location.)  In practice this means we can only truly silently
      ; remove an unelevated install: an elevated installer writing to an
      ; unprivileged install directory will still write privileged registry
      ; entries, requiring an elevated uninstaller to completely clean up.
      ;
      ; This avoids a wrinkle, whereby an uninstaller which runs unelevated will
      ; never itself launch the Maintenance Service uninstaller, because it will
      ; fail to remove its own service registration (removing the relevant
      ; registry key would require elevation).  Therefore the check for the
      ; service being unused will fail, which will prevent running the service
      ; uninstaller.  That's both subtle and possibly leaves the service
      ; registration hanging around, which might be a security risk.
      ;
      ; That is why we look for a privileged service registration for this
      ; installation when deciding to elevate, and elevate unconditionally if we
      ; find one, regardless of the result of the write check that would avoid
      ; elevation.

      ; The reason for requiring elevation, or "" for not required.
      StrCpy $R4 ""

      ${IfNot} ${Silent}
        ; In normal operation, require elevation if the user can elevate so that
        ; we are most likely to succeed.
        StrCpy $R4 "not silent"
      ${EndIf}

      GetTempFileName $R6 "$INSTDIR"
      FileOpen $R5 "$R6" w
      FileWrite $R5 "Write Access Test"
      FileClose $R5
      Delete $R6
      ${If} ${Errors}
        StrCpy $R4 "write"
      ${EndIf}

      !ifdef MOZ_MAINTENANCE_SERVICE
        ; We don't necessarily have $MaintCertKey, so use temporary registers.
        ServicesHelper::PathToUniqueRegistryPath "$INSTDIR"
        Pop $R5

        ${If} $R5 != ""
          ; Always use the 64bit registry for certs on 64bit systems.
          ${If} ${RunningX64}
          ${OrIf} ${IsNativeARM64}
            SetRegView 64
          ${EndIf}

          EnumRegKey $R6 HKLM $R5 0
          ClearErrors

          ${If} ${RunningX64}
          ${OrIf} ${IsNativeARM64}
            SetRegView lastused
          ${EndIf}

          ${IfNot} "$R6" == ""
            StrCpy $R4 "mms"
          ${EndIf}
        ${EndIf}
      !endif

      ${If} "$R4" != ""
        ; In the future, we might not try to elevate to remain truly silent.  Or
        ; we might add a command line arguments to specify behaviour.  One
        ; reason to not do that immediately is that we have no great way to
        ; signal that we exited without taking action.
        ${ElevateUAC}
      ${EndIf}

      ; Now we've elevated, try the write access test again.
      ClearErrors
      GetTempFileName $R6 "$INSTDIR"
      FileOpen $R5 "$R6" w
      FileWrite $R5 "Write Access Test"
      FileClose $R5
      Delete $R6
      ${If} ${Errors}
        ; Nothing initialized so no need to call OnEndCommon
        Quit
      ${EndIf}

      !ifdef MOZ_MAINTENANCE_SERVICE
        ; And verify that if we need to, we're going to clean up the registry
        ; correctly.
        ${If} "$R4" == "mms"
          WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" "Write Test"
          ${If} ${Errors}
            ; Nothing initialized so no need to call OnEndCommon
            Quit
          ${Endif}
          DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
        ${EndIf}
      !endif

      ; If we made it this far then this installer is being used as an uninstaller.
      WriteUninstaller "$EXEDIR\uninstaller.exe"

      ${If} ${Silent}
        StrCpy $R1 "$\"$EXEDIR\uninstaller.exe$\" /S"
      ${Else}
        StrCpy $R1 "$\"$EXEDIR\uninstaller.exe$\""
      ${EndIf}

      ; When the uninstaller is launched it copies itself to the temp directory
      ; so it won't be in use so it can delete itself.
      ExecWait $R1
      ${DeleteFile} "$EXEDIR\uninstaller.exe"
      ${UnloadUAC}
      SetErrorLevel 0
      Quit ; Nothing initialized so no need to call OnEndCommon

    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro UninstallOnInitCommonCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call UninstallOnInitCommon
  !verbose pop
!macroend

/**
 * Called from the uninstaller's un.onInit function not to be confused with the
 * installer's .onInit or the uninstaller's .onInit functions.
 */
!macro un.UninstallUnOnInitCommon

  !ifndef un.UninstallUnOnInitCommon
    !insertmacro un.GetLongPath
    !insertmacro un.GetParent
    !insertmacro un.SetBrandNameVars

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define un.UninstallUnOnInitCommon "!insertmacro un.UninstallUnOnInitCommonCall"

    Function un.UninstallUnOnInitCommon
      ${un.GetParent} "$INSTDIR" $INSTDIR
      ${un.GetLongPath} "$INSTDIR" $INSTDIR
      ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}"
        Abort
      ${EndUnless}

      !ifdef HAVE_64BIT_BUILD
        SetRegView 64
      !endif

      ; Prevents breaking apps that don't use SetBrandNameVars
      !ifdef un.SetBrandNameVars
        ${un.SetBrandNameVars} "$INSTDIR\distribution\setup.ini"
      !endif

      ; Initialize $hHeaderBitmap to prevent redundant changing of the bitmap if
      ; the user clicks the back button
      StrCpy $hHeaderBitmap ""
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro un.UninstallUnOnInitCommonCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.UninstallUnOnInitCommon
  !verbose pop
!macroend

/**
 * Called from the MUI leaveOptions function to set the value of $INSTDIR.
 */
!macro LeaveOptionsCommon

  !ifndef LeaveOptionsCommon
    !insertmacro CanWriteToInstallDir
    !insertmacro GetLongPath

!ifndef NO_INSTDIR_FROM_REG
    !insertmacro GetSingleInstallPath
!endif

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define LeaveOptionsCommon "!insertmacro LeaveOptionsCommonCall"

    Function LeaveOptionsCommon
      Push $R9

      StrCpy $R9 "false"

!ifndef NO_INSTDIR_FROM_REG
      SetShellVarContext all      ; Set SHCTX to HKLM

      ${If} ${IsNativeAMD64}
      ${OrIf} ${IsNativeARM64}
        SetRegView 64
        ${GetSingleInstallPath} "Software\Mozilla\${BrandFullNameInternal}" $R9
        SetRegView lastused
      ${EndIf}

      StrCmp "$R9" "false" +1 finish_get_install_dir

      SetRegView 32
      ${GetSingleInstallPath} "Software\Mozilla\${BrandFullNameInternal}" $R9
      SetRegView lastused

      StrCmp "$R9" "false" +1 finish_get_install_dir

      SetShellVarContext current  ; Set SHCTX to HKCU
      ${GetSingleInstallPath} "Software\Mozilla\${BrandFullNameInternal}" $R9

      finish_get_install_dir:
      StrCmp "$R9" "false" +2 +1
      StrCpy $INSTDIR "$R9"
!endif

      ; If the user doesn't have write access to the installation directory set
      ; the installation directory to a subdirectory of the user's local
      ; application directory (e.g. non-roaming).
      ${CanWriteToInstallDir} $R9
      ${If} "$R9" == "false"
        ; NOTE: This SetShellVarContext isn't directly needed anymore, but to
        ; leave the state consistent with earlier code I'm leaving it here.
        SetShellVarContext all      ; Set SHCTX to All Users
        ${GetLocalAppDataFolder} $R9
        StrCpy $INSTDIR "$R9\${BrandFullName}\"
        ${CanWriteToInstallDir} $R9
      ${EndIf}

      IfFileExists "$INSTDIR" +3 +1
      Pop $R9
      Return

      ; Always display the long path if the path already exists.
      ${GetLongPath} "$INSTDIR" $INSTDIR

      ; The call to GetLongPath returns a long path without a trailing
      ; back-slash. Append a \ to the path to prevent the directory
      ; name from being appended when using the NSIS create new folder.
      ; http://www.nullsoft.com/free/nsis/makensis.htm#InstallDir
      StrCpy $INSTDIR "$INSTDIR\"

      Pop $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro LeaveOptionsCommonCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call LeaveOptionsCommon
  !verbose pop
!macroend

/**
 * Called from the MUI preDirectory function to verify there is enough disk
 * space for the installation and the installation directory is writable.
 *
 * $R9 = returned value from CheckDiskSpace and CanWriteToInstallDir macros
 */
!macro PreDirectoryCommon

  !ifndef PreDirectoryCommon
    !insertmacro CanWriteToInstallDir
    !insertmacro CheckDiskSpace

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define PreDirectoryCommon "!insertmacro PreDirectoryCommonCall"

    Function PreDirectoryCommon
      Push $R9

      IntCmp $InstallType ${INSTALLTYPE_CUSTOM} end +1 +1
      ${CanWriteToInstallDir} $R9
      StrCmp "$R9" "false" end +1
      ${CheckDiskSpace} $R9
      StrCmp "$R9" "false" end +1
      Abort

      end:

      Pop $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro PreDirectoryCommonCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call PreDirectoryCommon
  !verbose pop
!macroend

/**
 * Called from the MUI leaveDirectory function
 *
 * @param   _WARN_DISK_SPACE
 *          Message displayed when there isn't enough disk space to perform the
 *          installation.
 * @param   _WARN_WRITE_ACCESS
 *          Message displayed when the installer does not have write access to
 *          $INSTDIR.
 *
 * $R7 = returned value from CheckDiskSpace and CanWriteToInstallDir macros
 * $R8 = _WARN_DISK_SPACE
 * $R9 = _WARN_WRITE_ACCESS
 */
!macro LeaveDirectoryCommon

  !ifndef LeaveDirectoryCommon
    !insertmacro CheckDiskSpace
    !insertmacro CanWriteToInstallDir

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define LeaveDirectoryCommon "!insertmacro LeaveDirectoryCommonCall"

    Function LeaveDirectoryCommon
      Exch $R9
      Exch 1
      Exch $R8
      Push $R7

      ${CanWriteToInstallDir} $R7
      ${If} $R7 == "false"
        MessageBox MB_OK|MB_ICONEXCLAMATION "$R9"
        Abort
      ${EndIf}

      ${CheckDiskSpace} $R7
      ${If} $R7 == "false"
        MessageBox MB_OK|MB_ICONEXCLAMATION "$R8"
        Abort
      ${EndIf}

      Pop $R7
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro LeaveDirectoryCommonCall _WARN_DISK_SPACE _WARN_WRITE_ACCESS
  !verbose push
  Push "${_WARN_DISK_SPACE}"
  Push "${_WARN_WRITE_ACCESS}"
  !verbose ${_MOZFUNC_VERBOSE}
  Call LeaveDirectoryCommon
  !verbose pop
!macroend


################################################################################
# Install Section common macros.

/**
 * Performs common cleanup operations prior to the actual installation.
 * This macro should be called first when installation starts.
 */
!macro InstallStartCleanupCommon

  !ifndef InstallStartCleanupCommon
    !insertmacro CleanVirtualStore
    !insertmacro EndUninstallLog
    !insertmacro OnInstallUninstall

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define InstallStartCleanupCommon "!insertmacro InstallStartCleanupCommonCall"

    Function InstallStartCleanupCommon

      ; Remove files not removed by parsing the uninstall.log
      Delete "$INSTDIR\install_wizard.log"
      Delete "$INSTDIR\install_status.log"

      RmDir /r "$INSTDIR\updates"
      Delete "$INSTDIR\updates.xml"
      Delete "$INSTDIR\active-update.xml"

      ; Remove files from the uninstall directory.
      ${If} ${FileExists} "$INSTDIR\uninstall"
        Delete "$INSTDIR\uninstall\*wizard*"
        Delete "$INSTDIR\uninstall\uninstall.ini"
        Delete "$INSTDIR\uninstall\cleanup.log"
        Delete "$INSTDIR\uninstall\uninstall.update"
        ${OnInstallUninstall}
      ${EndIf}

      ; Since we write to the uninstall.log in this directory during the
      ; installation create the directory if it doesn't already exist.
      IfFileExists "$INSTDIR\uninstall" +2 +1
      CreateDirectory "$INSTDIR\uninstall"

      ; Application update uses a directory named tobedeleted in the $INSTDIR to
      ; delete files on OS reboot when they are in use. Try to delete this
      ; directory if it exists.
      ${If} ${FileExists} "$INSTDIR\${TO_BE_DELETED}"
        RmDir /r "$INSTDIR\${TO_BE_DELETED}"
      ${EndIf}

      ; Remove files that may be left behind by the application in the
      ; VirtualStore directory.
      ${CleanVirtualStore}
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro InstallStartCleanupCommonCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call InstallStartCleanupCommon
  !verbose pop
!macroend

/**
 * Performs common cleanup operations after the actual installation.
 * This macro should be called last during the installation.
 */
!macro InstallEndCleanupCommon

  !ifndef InstallEndCleanupCommon
    !insertmacro EndUninstallLog

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define InstallEndCleanupCommon "!insertmacro InstallEndCleanupCommonCall"

    Function InstallEndCleanupCommon

      ; Close the file handle to the uninstall.log
      ${EndUninstallLog}

    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro InstallEndCleanupCommonCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call InstallEndCleanupCommon
  !verbose pop
!macroend


################################################################################
# UAC Related Macros

/**
 * Provides UAC elevation support (requires the UAC plugin).
 *
 * $0 = return values from calls to the UAC plugin (always uses $0)
 * $R9 = return values from GetParameters and GetOptions macros
 */
!macro ElevateUAC

  !ifndef ${_MOZFUNC_UN}ElevateUAC
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetOptions
    !insertmacro ${_MOZFUNC_UN_TMP}GetParameters
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}ElevateUAC "!insertmacro ${_MOZFUNC_UN}ElevateUACCall"

    Function ${_MOZFUNC_UN}ElevateUAC
      Push $R9
      Push $0

!ifndef NONADMIN_ELEVATE
      UAC::IsAdmin
      ; If the user is not an admin already
      ${If} "$0" != "1"
        UAC::SupportsUAC
        ; If the system supports UAC
        ${If} "$0" == "1"
          UAC::GetElevationType
          ; If the user account has a split token
          ${If} "$0" == "3"
            UAC::RunElevated
            UAC::Unload
            ; Nothing besides UAC initialized so no need to call OnEndCommon
            Quit
          ${EndIf}
        ${EndIf}
      ${Else}
        ${GetParameters} $R9
        ${If} $R9 != ""
          ClearErrors
          ${GetOptions} "$R9" "/UAC:" $0
          ; If the command line contains /UAC then we need to initialize
          ; the UAC plugin to use UAC::ExecCodeSegment to execute code in
          ; the non-elevated context.
          ${Unless} ${Errors}
            UAC::RunElevated
          ${EndUnless}
        ${EndIf}
      ${EndIf}
!else
      UAC::IsAdmin
      ; If the user is not an admin already
      ${If} "$0" != "1"
        UAC::SupportsUAC
        ; If the system supports UAC require that the user elevate
        ${If} "$0" == "1"
          UAC::GetElevationType
          ; If the user account has a split token
          ${If} "$0" == "3"
            UAC::RunElevated
            ${If} "$0" == "0" ; Was elevation successful
              UAC::Unload
              ; Nothing besides UAC initialized so no need to call OnEndCommon
              Quit
            ${EndIf}
            ; Unload UAC since the elevation request was not successful and
            ; install anyway.
            UAC::Unload
          ${EndIf}
        ${Else}
          ; Check if UAC is enabled. If the user has turned UAC on or off
          ; without rebooting this value will be incorrect. This is an
          ; edgecase that we have to live with when trying to allow
          ; installing when the user doesn't have privileges such as a public
          ; computer while trying to also achieve UAC elevation. When this
          ; happens the user will be presented with the runas dialog if the
          ; value is 1 and won't be presented with the UAC dialog when the
          ; value is 0.
          ReadRegDWord $R9 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" "EnableLUA"
          ${If} "$R9" == "1"
            ; This will display the UAC version of the runas dialog which
            ; requires a password for an existing user account.
            UAC::RunElevated
            ${If} "$0" == "0" ; Was elevation successful
              UAC::Unload
              ; Nothing besides UAC initialized so no need to call OnEndCommon
              Quit
            ${EndIf}
            ; Unload UAC since the elevation request was not successful and
            ; install anyway.
            UAC::Unload
          ${EndIf}
        ${EndIf}
      ${Else}
        ClearErrors
        ${${_MOZFUNC_UN}GetParameters} $R9
        ${${_MOZFUNC_UN}GetOptions} "$R9" "/UAC:" $R9
        ; If the command line contains /UAC then we need to initialize the UAC
        ; plugin to use UAC::ExecCodeSegment to execute code in the
        ; non-elevated context.
        ${Unless} ${Errors}
          UAC::RunElevated
        ${EndUnless}
      ${EndIf}
!endif

      ClearErrors

      Pop $0
      Pop $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro ElevateUACCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call ElevateUAC
  !verbose pop
!macroend

!macro un.ElevateUACCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.ElevateUAC
  !verbose pop
!macroend

!macro un.ElevateUAC
  !ifndef un.ElevateUAC
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro ElevateUAC

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Unloads the UAC plugin so the NSIS plugins can be removed when the installer
 * and uninstaller exit.
 *
 * $R9 = return values from GetParameters and GetOptions macros
 */
!macro UnloadUAC

  !ifndef ${_MOZFUNC_UN}UnloadUAC
    !define _MOZFUNC_UN_TMP_UnloadUAC ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP_UnloadUAC}GetOptions
    !insertmacro ${_MOZFUNC_UN_TMP_UnloadUAC}GetParameters
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP_UnloadUAC}
    !undef _MOZFUNC_UN_TMP_UnloadUAC

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}UnloadUAC "!insertmacro ${_MOZFUNC_UN}UnloadUACCall"

    Function ${_MOZFUNC_UN}UnloadUAC
      Push $R9

      ClearErrors
      ${${_MOZFUNC_UN}GetParameters} $R9
      ${${_MOZFUNC_UN}GetOptions} "$R9" "/UAC:" $R9
      ; If the command line contains /UAC then we need to unload the UAC plugin
      IfErrors +2 +1
      UAC::Unload

      ClearErrors

      Pop $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro UnloadUACCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call UnloadUAC
  !verbose pop
!macroend

!macro un.UnloadUACCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.UnloadUAC
  !verbose pop
!macroend

!macro un.UnloadUAC
  !ifndef un.UnloadUAC
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro UnloadUAC

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend


################################################################################
# Macros for uninstall.log and install.log logging
#
# Since these are used by other macros they should be inserted first. All of
# these macros can be easily inserted using the _LoggingCommon macro.

/**
 * Adds all logging macros in the correct order in one fell swoop as well as
 * the vars for the install.log and uninstall.log file handles.
 */
!macro _LoggingCommon
  Var /GLOBAL fhInstallLog
  Var /GLOBAL fhUninstallLog
  !insertmacro StartInstallLog
  !insertmacro EndInstallLog
  !insertmacro StartUninstallLog
  !insertmacro EndUninstallLog
!macroend
!define _LoggingCommon "!insertmacro _LoggingCommon"

/**
 * Creates a file named install.log in the install directory (e.g. $INSTDIR)
 * and adds the installation started message to the install.log for this
 * installation. This also adds the fhInstallLog and fhUninstallLog vars used
 * for logging.
 *
 * $fhInstallLog = filehandle for $INSTDIR\install.log
 *
 * @param   _APP_NAME
 *          Typically the BrandFullName
 * @param   _AB_CD
 *          The locale identifier
 * @param   _APP_VERSION
 *          The application version
 * @param   _GRE_VERSION
 *          The Gecko Runtime Engine version
 *
 * $R6 = _APP_NAME
 * $R7 = _AB_CD
 * $R8 = _APP_VERSION
 * $R9 = _GRE_VERSION
 */
!macro StartInstallLog

  !ifndef StartInstallLog
    !insertmacro GetTime

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define StartInstallLog "!insertmacro StartInstallLogCall"

    Function StartInstallLog
      Exch $R9
      Exch 1
      Exch $R8
      Exch 2
      Exch $R7
      Exch 3
      Exch $R6
      Push $R5
      Push $R4
      Push $R3
      Push $R2
      Push $R1
      Push $R0
      Push $9

      ${DeleteFile} "$INSTDIR\install.log"
      FileOpen $fhInstallLog "$INSTDIR\install.log" w
      FileWriteWord $fhInstallLog "65279"

      ${GetTime} "" "L" $9 $R0 $R1 $R2 $R3 $R4 $R5
      FileWriteUTF16LE $fhInstallLog "$R6 Installation Started: $R1-$R0-$9 $R3:$R4:$R5"
      ${WriteLogSeparator}

      ${LogHeader} "Installation Details"
      ${LogMsg} "Install Dir: $INSTDIR"
      ${LogMsg} "Locale     : $R7"
      ${LogMsg} "App Version: $R8"
      ${LogMsg} "GRE Version: $R9"

      ${If} ${IsWin10}
        ${LogMsg} "OS Name    : Windows 10"
      ${ElseIf} ${AtLeastWin10}
        ${LogMsg} "OS Name    : Above Windows 10"
      ${Else}
        ${LogMsg} "OS Name    : Unable to detect"
      ${EndIf}

      ${LogMsg} "Target CPU : ${ARCH}"

      Pop $9
      Pop $R0
      Pop $R1
      Pop $R2
      Pop $R3
      Pop $R4
      Pop $R5
      Exch $R6
      Exch 3
      Exch $R7
      Exch 2
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro StartInstallLogCall _APP_NAME _AB_CD _APP_VERSION _GRE_VERSION
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_APP_NAME}"
  Push "${_AB_CD}"
  Push "${_APP_VERSION}"
  Push "${_GRE_VERSION}"
  Call StartInstallLog
  !verbose pop
!macroend

/**
 * Writes the installation finished message to the install.log and closes the
 * file handles to the install.log and uninstall.log
 *
 * @param   _APP_NAME
 *
 * $R9 = _APP_NAME
 */
!macro EndInstallLog

  !ifndef EndInstallLog
    !insertmacro GetTime

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define EndInstallLog "!insertmacro EndInstallLogCall"

    Function EndInstallLog
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5
      Push $R4
      Push $R3
      Push $R2

      ${WriteLogSeparator}
      ${GetTime} "" "L" $R2 $R3 $R4 $R5 $R6 $R7 $R8
      FileWriteUTF16LE $fhInstallLog "$R9 Installation Finished: $R4-$R3-$R2 $R6:$R7:$R8$\r$\n"
      FileClose $fhInstallLog

      Pop $R2
      Pop $R3
      Pop $R4
      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro EndInstallLogCall _APP_NAME
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_APP_NAME}"
  Call EndInstallLog
  !verbose pop
!macroend

/**
 * Opens the file handle to the uninstall.log.
 *
 * $fhUninstallLog = filehandle for $INSTDIR\uninstall\uninstall.log
 */
!macro StartUninstallLog

  !ifndef StartUninstallLog
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define StartUninstallLog "!insertmacro StartUninstallLogCall"

    Function StartUninstallLog
      FileOpen $fhUninstallLog "$INSTDIR\uninstall\uninstall.log" w
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro StartUninstallLogCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call StartUninstallLog
  !verbose pop
!macroend

/**
 * Closes the file handle to the uninstall.log.
 */
!macro EndUninstallLog

  !ifndef EndUninstallLog

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define EndUninstallLog "!insertmacro EndUninstallLogCall"

    Function EndUninstallLog
      FileClose $fhUninstallLog
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro EndUninstallLogCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call EndUninstallLog
  !verbose pop
!macroend

/**
 * Adds a section header to the human readable log.
 *
 * @param   _HEADER
 *          The header text to write to the log.
 */
!macro LogHeader _HEADER
  ${WriteLogSeparator}
  FileWriteUTF16LE $fhInstallLog "${_HEADER}"
  ${WriteLogSeparator}
!macroend
!define LogHeader "!insertmacro LogHeader"

/**
 * Adds a section message to the human readable log.
 *
 * @param   _MSG
 *          The message text to write to the log.
 */
!macro LogMsg _MSG
  FileWriteUTF16LE $fhInstallLog "  ${_MSG}$\r$\n"
!macroend
!define LogMsg "!insertmacro LogMsg"

/**
 * Adds an uninstall entry to the uninstall log.
 *
 * @param   _MSG
 *          The message text to write to the log.
 */
!macro LogUninstall _MSG
  FileWrite $fhUninstallLog "${_MSG}$\r$\n"
!macroend
!define LogUninstall "!insertmacro LogUninstall"

/**
 * Adds a section divider to the human readable log.
 */
!macro WriteLogSeparator
  FileWriteUTF16LE $fhInstallLog "$\r$\n----------------------------------------\
                                  ---------------------------------------$\r$\n"
!macroend
!define WriteLogSeparator "!insertmacro WriteLogSeparator"


################################################################################
# Macros for managing the shortcuts log ini file

/**
 * Adds the most commonly used shortcut logging macros for the installer in one
 * fell swoop.
 */
!macro _LoggingShortcutsCommon
  !insertmacro LogDesktopShortcut
  !insertmacro LogQuickLaunchShortcut
  !insertmacro LogSMProgramsShortcut
!macroend
!define _LoggingShortcutsCommon "!insertmacro _LoggingShortcutsCommon"

/**
 * Creates the shortcuts log ini file with a UTF-16LE BOM if it doesn't exist.
 */
!macro initShortcutsLog
  Push $R9

  IfFileExists "$INSTDIR\uninstall\${SHORTCUTS_LOG}" +4 +1
  FileOpen $R9 "$INSTDIR\uninstall\${SHORTCUTS_LOG}" w
  FileWriteWord $R9 "65279"
  FileClose $R9

  Pop $R9
!macroend
!define initShortcutsLog "!insertmacro initShortcutsLog"

/**
 * Adds shortcut entries to the shortcuts log ini file. This macro is primarily
 * a helper used by the LogDesktopShortcut, LogQuickLaunchShortcut, and
 * LogSMProgramsShortcut macros but it can be used by other code if desired. If
 * the value already exists the the value is not written to the file.
 *
 * @param   _SECTION_NAME
 *          The section name to write to in the shortcut log ini file
 * @param   _FILE_NAME
 *          The shortcut's file name
 *
 * $R6 = return value from ReadIniStr for the shortcut file name
 * $R7 = counter for supporting multiple shortcuts in the same location
 * $R8 = _SECTION_NAME
 * $R9 = _FILE_NAME
 */
!macro LogShortcut

  !ifndef LogShortcut
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define LogShortcut "!insertmacro LogShortcutCall"

    Function LogShortcut
      Exch $R9
      Exch 1
      Exch $R8
      Push $R7
      Push $R6

      ClearErrors

      !insertmacro initShortcutsLog

      StrCpy $R6 ""
      StrCpy $R7 -1

      StrCmp "$R6" "$R9" +5 +1 ; if the shortcut already exists don't add it
      IntOp $R7 $R7 + 1 ; increment the counter
      ReadIniStr $R6 "$INSTDIR\uninstall\${SHORTCUTS_LOG}" "$R8" "Shortcut$R7"
      IfErrors +1 -3
      WriteINIStr "$INSTDIR\uninstall\${SHORTCUTS_LOG}" "$R8" "Shortcut$R7" "$R9"

      ClearErrors

      Pop $R6
      Pop $R7
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro LogShortcutCall _SECTION_NAME _FILE_NAME
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_SECTION_NAME}"
  Push "${_FILE_NAME}"
  Call LogShortcut
  !verbose pop
!macroend

/**
 * Adds a Desktop shortcut entry to the shortcuts log ini file.
 *
 * @param   _FILE_NAME
 *          The shortcut file name (e.g. shortcut.lnk)
 */
!macro LogDesktopShortcut

  !ifndef LogDesktopShortcut
    !insertmacro LogShortcut

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define LogDesktopShortcut "!insertmacro LogDesktopShortcutCall"

    Function LogDesktopShortcut
      Call LogShortcut
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro LogDesktopShortcutCall _FILE_NAME
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "DESKTOP"
  Push "${_FILE_NAME}"
  Call LogDesktopShortcut
  !verbose pop
!macroend

/**
 * Adds a QuickLaunch shortcut entry to the shortcuts log ini file.
 *
 * @param   _FILE_NAME
 *          The shortcut file name (e.g. shortcut.lnk)
 */
!macro LogQuickLaunchShortcut

  !ifndef LogQuickLaunchShortcut
    !insertmacro LogShortcut

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define LogQuickLaunchShortcut "!insertmacro LogQuickLaunchShortcutCall"

    Function LogQuickLaunchShortcut
      Call LogShortcut
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro LogQuickLaunchShortcutCall _FILE_NAME
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "QUICKLAUNCH"
  Push "${_FILE_NAME}"
  Call LogQuickLaunchShortcut
  !verbose pop
!macroend

/**
 * Adds a Start Menu shortcut entry to the shortcuts log ini file.
 *
 * @param   _FILE_NAME
 *          The shortcut file name (e.g. shortcut.lnk)
 */
!macro LogStartMenuShortcut

  !ifndef LogStartMenuShortcut
    !insertmacro LogShortcut

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define LogStartMenuShortcut "!insertmacro LogStartMenuShortcutCall"

    Function LogStartMenuShortcut
      Call LogShortcut
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro LogStartMenuShortcutCall _FILE_NAME
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "STARTMENU"
  Push "${_FILE_NAME}"
  Call LogStartMenuShortcut
  !verbose pop
!macroend

/**
 * Adds a Start Menu Programs shortcut entry to the shortcuts log ini file.
 *
 * @param   _FILE_NAME
 *          The shortcut file name (e.g. shortcut.lnk)
 */
!macro LogSMProgramsShortcut

  !ifndef LogSMProgramsShortcut
    !insertmacro LogShortcut

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define LogSMProgramsShortcut "!insertmacro LogSMProgramsShortcutCall"

    Function LogSMProgramsShortcut
      Call LogShortcut
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro LogSMProgramsShortcutCall _FILE_NAME
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "SMPROGRAMS"
  Push "${_FILE_NAME}"
  Call LogSMProgramsShortcut
  !verbose pop
!macroend

/**
 * Adds the relative path from the Start Menu Programs directory for the
 * application's Start Menu directory if it is different from the existing value
 * to the shortcuts log ini file.
 *
 * @param   _REL_PATH_TO_DIR
 *          The relative path from the Start Menu Programs directory to the
 *          program's directory.
 *
 * $R9 = _REL_PATH_TO_DIR
 */
!macro LogSMProgramsDirRelPath _REL_PATH_TO_DIR
  Push $R9

  !insertmacro initShortcutsLog

  ReadINIStr $R9 "$INSTDIR\uninstall\${SHORTCUTS_LOG}" "SMPROGRAMS" "RelativePathToDir"
  StrCmp "$R9" "${_REL_PATH_TO_DIR}" +2 +1
  WriteINIStr "$INSTDIR\uninstall\${SHORTCUTS_LOG}" "SMPROGRAMS" "RelativePathToDir" "${_REL_PATH_TO_DIR}"

  Pop $R9
!macroend
!define LogSMProgramsDirRelPath "!insertmacro LogSMProgramsDirRelPath"

/**
 * Copies the value for the relative path from the Start Menu programs directory
 * (e.g. $SMPROGRAMS) to the Start Menu directory as it is stored in the
 * shortcuts log ini file to the variable specified in the first parameter.
 */
!macro GetSMProgramsDirRelPath _VAR
  ReadINIStr ${_VAR} "$INSTDIR\uninstall\${SHORTCUTS_LOG}" "SMPROGRAMS" \
             "RelativePathToDir"
!macroend
!define GetSMProgramsDirRelPath "!insertmacro GetSMProgramsDirRelPath"

/**
 * Copies the shortcuts log ini file path to the variable specified in the
 * first parameter.
 */
!macro GetShortcutsLogPath _VAR
  StrCpy ${_VAR} "$INSTDIR\uninstall\${SHORTCUTS_LOG}"
!macroend
!define GetShortcutsLogPath "!insertmacro GetShortcutsLogPath"


################################################################################
# Macros for managing specific Windows version features

/**
 * Sets the permitted layered service provider (LSP) categories
 * for the application. Consumers should call this after an
 * installation log section has completed since this macro will log the results
 * to the installation log along with a header.
 *
 * !IMPORTANT - When calling this macro from an uninstaller do not specify a
 *              parameter. The paramter is hardcoded with 0x00000000 to remove
 *              the LSP category for the application when performing an
 *              uninstall.
 *
 * @param   _LSP_CATEGORIES
 *          The permitted LSP categories for the application. When called by an
 *          uninstaller this will always be 0x00000000.
 *
 * $R5 = error code popped from the stack for the WSCSetApplicationCategory call
 * $R6 = return value from the WSCSetApplicationCategory call
 * $R7 = string length for the long path to the main application executable
 * $R8 = long path to the main application executable
 * $R9 = _LSP_CATEGORIES
 */
!macro SetAppLSPCategories

  !ifndef ${_MOZFUNC_UN}SetAppLSPCategories
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}SetAppLSPCategories "!insertmacro ${_MOZFUNC_UN}SetAppLSPCategoriesCall"

    Function ${_MOZFUNC_UN}SetAppLSPCategories
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5

      ${${_MOZFUNC_UN}GetLongPath} "$INSTDIR\${FileMainEXE}" $R8
      StrLen $R7 "$R8"

      ; Remove existing categories by setting the permitted categories to
      ; 0x00000000 since new categories are ANDed with existing categories. If
      ; the param value stored in $R9 is 0x00000000 then skip the removal since
      ; the categories  will be removed by the second call to
      ; WSCSetApplicationCategory.
      StrCmp "$R9" "0x00000000" +2 +1
      System::Call "Ws2_32::WSCSetApplicationCategory(w R8, i R7, w n, i 0,\
                                                      i 0x00000000, i n, *i) i"

      ; Set the permitted LSP categories
      System::Call "Ws2_32::WSCSetApplicationCategory(w R8, i R7, w n, i 0,\
                                                      i R9, i n, *i .s) i.R6"
      Pop $R5

!ifndef NO_LOG
      ${LogHeader} "Setting Permitted LSP Categories"
      StrCmp "$R6" 0 +3 +1
      ${LogMsg} "** ERROR Setting LSP Categories: $R5 **"
      GoTo +2
      ${LogMsg} "Permitted LSP Categories: $R9"
!endif

      ClearErrors

      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro SetAppLSPCategoriesCall _LSP_CATEGORIES
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_LSP_CATEGORIES}"
  Call SetAppLSPCategories
  !verbose pop
!macroend

!macro un.SetAppLSPCategoriesCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "0x00000000"
  Call un.SetAppLSPCategories
  !verbose pop
!macroend

!macro un.SetAppLSPCategories
  !ifndef un.SetAppLSPCategories
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro SetAppLSPCategories

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Checks if any pinned TaskBar lnk files point to the executable's path passed
 * to the macro.
 *
 * @param   _EXE_PATH
 *          The executable path
 * @return  _RESULT
 *          false if no pinned shotcuts were found for this install location.
 *          true if pinned shotcuts were found for this install location.
 *
 * $R5 = stores whether a TaskBar lnk file has been found for the executable
 * $R6 = long path returned from GetShortCutTarget and GetLongPath
 * $R7 = file name returned from FindFirst and FindNext
 * $R8 = find handle for FindFirst and FindNext
 * $R9 = _EXE_PATH and _RESULT
 */
!macro IsPinnedToTaskBar

  !ifndef IsPinnedToTaskBar
    !insertmacro GetLongPath

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define IsPinnedToTaskBar "!insertmacro IsPinnedToTaskBarCall"

    Function IsPinnedToTaskBar
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5

      StrCpy $R5 "false"

      ${If} ${FileExists} "$QUICKLAUNCH\User Pinned\TaskBar"
        FindFirst $R8 $R7 "$QUICKLAUNCH\User Pinned\TaskBar\*.lnk"
        ${Do}
          ${If} ${FileExists} "$QUICKLAUNCH\User Pinned\TaskBar\$R7"
            ShellLink::GetShortCutTarget "$QUICKLAUNCH\User Pinned\TaskBar\$R7"
            Pop $R6
            ${GetLongPath} "$R6" $R6
            ${If} "$R6" == "$R9"
              StrCpy $R5 "true"
              ${ExitDo}
            ${EndIf}
          ${EndIf}
          ClearErrors
          FindNext $R8 $R7
          ${If} ${Errors}
            ${ExitDo}
          ${EndIf}
        ${Loop}
        FindClose $R8
      ${EndIf}

      ClearErrors

      StrCpy $R9 $R5

      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro IsPinnedToTaskBarCall _EXE_PATH _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_EXE_PATH}"
  Call IsPinnedToTaskBar
  Pop ${_RESULT}
  !verbose pop
!macroend

/**
 * Checks if any pinned Start Menu lnk files point to the executable's path
 * passed to the macro.
 *
 * @param   _EXE_PATH
 *          The executable path
 * @return  _RESULT
 *          false if no pinned shotcuts were found for this install location.
 *          true if pinned shotcuts were found for this install location.
 *
 * $R5 = stores whether a Start Menu lnk file has been found for the executable
 * $R6 = long path returned from GetShortCutTarget and GetLongPath
 * $R7 = file name returned from FindFirst and FindNext
 * $R8 = find handle for FindFirst and FindNext
 * $R9 = _EXE_PATH and _RESULT
 */
!macro IsPinnedToStartMenu

  !ifndef IsPinnedToStartMenu
    !insertmacro GetLongPath

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define IsPinnedToStartMenu "!insertmacro IsPinnedToStartMenuCall"

    Function IsPinnedToStartMenu
      Exch $R9
      Push $R8
      Push $R7
      Push $R6
      Push $R5

      StrCpy $R5 "false"

      ${If} ${FileExists} "$QUICKLAUNCH\User Pinned\StartMenu"
        FindFirst $R8 $R7 "$QUICKLAUNCH\User Pinned\StartMenu\*.lnk"
        ${Do}
          ${If} ${FileExists} "$QUICKLAUNCH\User Pinned\StartMenu\$R7"
            ShellLink::GetShortCutTarget "$QUICKLAUNCH\User Pinned\StartMenu\$R7"
            Pop $R6
            ${GetLongPath} "$R6" $R6
            ${If} "$R6" == "$R9"
              StrCpy $R5 "true"
              ${ExitDo}
            ${EndIf}
          ${EndIf}
          ClearErrors
          FindNext $R8 $R7
          ${If} ${Errors}
            ${ExitDo}
          ${EndIf}
        ${Loop}
        FindClose $R8
      ${EndIf}

      ClearErrors

      StrCpy $R9 $R5

      Pop $R5
      Pop $R6
      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro IsPinnedToStartMenuCall _EXE_PATH _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_EXE_PATH}"
  Call IsPinnedToStartMenu
  Pop ${_RESULT}
  !verbose pop
!macroend

/**
 * Gets the number of pinned shortcut lnk files pinned to the Task Bar.
 *
 * @return  _RESULT
 *          number of pinned shortcut lnk files.
 *
 * $R7 = file name returned from FindFirst and FindNext
 * $R8 = find handle for FindFirst and FindNext
 * $R9 = _RESULT
 */
!macro PinnedToTaskBarLnkCount

  !ifndef PinnedToTaskBarLnkCount
    !insertmacro GetLongPath

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define PinnedToTaskBarLnkCount "!insertmacro PinnedToTaskBarLnkCountCall"

    Function PinnedToTaskBarLnkCount
      Push $R9
      Push $R8
      Push $R7

      StrCpy $R9 0

      ${If} ${FileExists} "$QUICKLAUNCH\User Pinned\TaskBar"
        FindFirst $R8 $R7 "$QUICKLAUNCH\User Pinned\TaskBar\*.lnk"
        ${Do}
          ${If} ${FileExists} "$QUICKLAUNCH\User Pinned\TaskBar\$R7"
            IntOp $R9 $R9 + 1
          ${EndIf}
          ClearErrors
          FindNext $R8 $R7
          ${If} ${Errors}
            ${ExitDo}
          ${EndIf}
        ${Loop}
        FindClose $R8
      ${EndIf}

      ClearErrors

      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro PinnedToTaskBarLnkCountCall _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call PinnedToTaskBarLnkCount
  Pop ${_RESULT}
  !verbose pop
!macroend

/**
 * Gets the number of pinned shortcut lnk files pinned to the Start Menu.
 *
 * @return  _RESULT
 *          number of pinned shortcut lnk files.
 *
 * $R7 = file name returned from FindFirst and FindNext
 * $R8 = find handle for FindFirst and FindNext
 * $R9 = _RESULT
 */
!macro PinnedToStartMenuLnkCount

  !ifndef PinnedToStartMenuLnkCount
    !insertmacro GetLongPath

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define PinnedToStartMenuLnkCount "!insertmacro PinnedToStartMenuLnkCountCall"

    Function PinnedToStartMenuLnkCount
      Push $R9
      Push $R8
      Push $R7

      StrCpy $R9 0

      ${If} ${FileExists} "$QUICKLAUNCH\User Pinned\StartMenu"
        FindFirst $R8 $R7 "$QUICKLAUNCH\User Pinned\StartMenu\*.lnk"
        ${Do}
          ${If} ${FileExists} "$QUICKLAUNCH\User Pinned\StartMenu\$R7"
            IntOp $R9 $R9 + 1
          ${EndIf}
          ClearErrors
          FindNext $R8 $R7
          ${If} ${Errors}
            ${ExitDo}
          ${EndIf}
        ${Loop}
        FindClose $R8
      ${EndIf}

      ClearErrors

      Pop $R7
      Pop $R8
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro PinnedToStartMenuLnkCountCall _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call PinnedToStartMenuLnkCount
  Pop ${_RESULT}
  !verbose pop
!macroend

/**
 * Update Start Menu / TaskBar lnk files that point to the executable's path
 * passed to the macro and all other shortcuts installed by the application with
 * the current application user model ID. Requires ApplicationID.
 *
 * NOTE: this does not update Desktop shortcut application user model ID due to
 *       bug 633728.
 *
 * @param   _EXE_PATH
 *          The main application executable path
 * @param   _APP_ID
 *          The application user model ID for the current install
 * @return  _RESULT
 *          false if no pinned shotcuts were found for this install location.
 *          true if pinned shotcuts were found for this install location.
 */
!macro UpdateShortcutAppModelIDs

  !ifndef UpdateShortcutAppModelIDs
    !insertmacro GetLongPath

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define UpdateShortcutAppModelIDs "!insertmacro UpdateShortcutAppModelIDsCall"

    Function UpdateShortcutAppModelIDs
      ; stack: path, appid
      Exch $R9 ; stack: $R9, appid | $R9 = path
      Exch 1   ; stack: appid, $R9
      Exch $R8 ; stack: $R8, $R9   | $R8 = appid
      Push $R7 ; stack: $R7, $R8, $R9
      Push $R6
      Push $R5
      Push $R4
      Push $R3 ; stack: $R3, $R5, $R6, $R7, $R8, $R9
      Push $R2

      ; $R9 = main application executable path
      ; $R8 = appid
      ; $R7 = path to the application's start menu programs directory
      ; $R6 = path to the shortcut log ini file
      ; $R5 = shortcut filename
      ; $R4 = GetShortCutTarget result

      StrCpy $R3 "false"

      ; installed shortcuts
      ${${_MOZFUNC_UN}GetLongPath} "$INSTDIR\uninstall\${SHORTCUTS_LOG}" $R6
      ${If} ${FileExists} "$R6"
        ; Update the Start Menu shortcuts' App ID for this application
        StrCpy $R2 -1
        ${Do}
          IntOp $R2 $R2 + 1 ; Increment the counter
          ClearErrors
          ReadINIStr $R5 "$R6" "STARTMENU" "Shortcut$R2"
          ${If} ${Errors}
            ${ExitDo}
          ${EndIf}

          ${If} ${FileExists} "$SMPROGRAMS\$R5"
            ShellLink::GetShortCutTarget "$SMPROGRAMS\$$R5"
            Pop $R4
            ${GetLongPath} "$R4" $R4
            ${If} "$R4" == "$R9" ; link path == install path
              ApplicationID::Set "$SMPROGRAMS\$R5" "$R8" "true"
              Pop $R4
            ${EndIf}
          ${EndIf}
        ${Loop}

        ; Update the Quick Launch shortcuts' App ID for this application
        StrCpy $R2 -1
        ${Do}
          IntOp $R2 $R2 + 1 ; Increment the counter
          ClearErrors
          ReadINIStr $R5 "$R6" "QUICKLAUNCH" "Shortcut$R2"
          ${If} ${Errors}
            ${ExitDo}
          ${EndIf}

          ${If} ${FileExists} "$QUICKLAUNCH\$R5"
            ShellLink::GetShortCutTarget "$QUICKLAUNCH\$R5"
            Pop $R4
            ${GetLongPath} "$R4" $R4
            ${If} "$R4" == "$R9" ; link path == install path
              ApplicationID::Set "$QUICKLAUNCH\$R5" "$R8" "true"
              Pop $R4
            ${EndIf}
          ${EndIf}
        ${Loop}

        ; Update the Start Menu Programs shortcuts' App ID for this application
        ClearErrors
        ReadINIStr $R7 "$R6" "SMPROGRAMS" "RelativePathToDir"
        ${Unless} ${Errors}
          ${${_MOZFUNC_UN}GetLongPath} "$SMPROGRAMS\$R7" $R7
          ${Unless} "$R7" == ""
            StrCpy $R2 -1
            ${Do}
              IntOp $R2 $R2 + 1 ; Increment the counter
              ClearErrors
              ReadINIStr $R5 "$R6" "SMPROGRAMS" "Shortcut$R2"
              ${If} ${Errors}
                ${ExitDo}
              ${EndIf}

              ${If} ${FileExists} "$R7\$R5"
                ShellLink::GetShortCutTarget "$R7\$R5"
                Pop $R4
                ${GetLongPath} "$R4" $R4
                ${If} "$R4" == "$R9" ; link path == install path
                  ApplicationID::Set "$R7\$R5" "$R8" "true"
                  Pop $R4
                ${EndIf}
              ${EndIf}
            ${Loop}
          ${EndUnless}
        ${EndUnless}
      ${EndIf}

      StrCpy $R7 "$QUICKLAUNCH\User Pinned"
      StrCpy $R3 "false"

      ; $R9 = main application executable path
      ; $R8 = appid
      ; $R7 = user pinned path
      ; $R6 = find handle
      ; $R5 = found filename
      ; $R4 = GetShortCutTarget result

      ; TaskBar links
      FindFirst $R6 $R5 "$R7\TaskBar\*.lnk"
      ${Do}
        ${If} ${FileExists} "$R7\TaskBar\$R5"
          ShellLink::GetShortCutTarget "$R7\TaskBar\$R5"
          Pop $R4
          ${If} "$R4" == "$R9" ; link path == install path
            ApplicationID::Set "$R7\TaskBar\$R5" "$R8" "true"
            Pop $R4 ; pop Set result off the stack
            StrCpy $R3 "true"
          ${EndIf}
        ${EndIf}
        ClearErrors
        FindNext $R6 $R5
        ${If} ${Errors}
          ${ExitDo}
        ${EndIf}
      ${Loop}
      FindClose $R6

      ; Start menu links
      FindFirst $R6 $R5 "$R7\StartMenu\*.lnk"
      ${Do}
        ${If} ${FileExists} "$R7\StartMenu\$R5"
          ShellLink::GetShortCutTarget "$R7\StartMenu\$R5"
          Pop $R4
          ${If} "$R4" == "$R9" ; link path == install path
            ApplicationID::Set "$R7\StartMenu\$R5" "$R8" "true"
            Pop $R4 ; pop Set result off the stack
            StrCpy $R3 "true"
          ${EndIf}
        ${EndIf}
        ClearErrors
        FindNext $R6 $R5
        ${If} ${Errors}
          ${ExitDo}
        ${EndIf}
      ${Loop}
      FindClose $R6

      ClearErrors

      StrCpy $R9 $R3

      Pop $R2
      Pop $R3  ; stack: $R4, $R5, $R6, $R7, $R8, $R9
      Pop $R4  ; stack: $R5, $R6, $R7, $R8, $R9
      Pop $R5  ; stack: $R6, $R7, $R8, $R9
      Pop $R6  ; stack: $R7, $R8, $R9
      Pop $R7  ; stack: $R8, $R9
      Exch $R8 ; stack: appid, $R9 | $R8 = old $R8
      Exch 1   ; stack: $R9, appid
      Exch $R9 ; stack: path, appid | $R9 = old $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro UpdateShortcutAppModelIDsCall _EXE_PATH _APP_ID _RESULT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_APP_ID}"
  Push "${_EXE_PATH}"
  Call UpdateShortcutAppModelIDs
  Pop ${_RESULT}
  !verbose pop
!macroend

!macro IsUserAdmin
  ; Copied from: http://nsis.sourceforge.net/IsUserAdmin
  Function IsUserAdmin
    Push $R0
    Push $R1
    Push $R2

    ClearErrors
    UserInfo::GetName
    IfErrors Win9x
    Pop $R1
    UserInfo::GetAccountType
    Pop $R2

    StrCmp $R2 "Admin" 0 Continue
    StrCpy $R0 "true"
    Goto Done

    Continue:

    StrCmp $R2 "" Win9x
    StrCpy $R0 "false"
    Goto Done

    Win9x:
    StrCpy $R0 "true"

    Done:
    Pop $R2
    Pop $R1
    Exch $R0
  FunctionEnd
!macroend

/**
 * Retrieve if present or generate and store a 64 bit hash of an install path
 * using the City Hash algorithm.  On return the resulting id is saved in the
 * $AppUserModelID variable declared by inserting this macro. InitHashAppModelId
 * will attempt to load from HKLM/_REG_PATH first, then HKCU/_REG_PATH. If found
 * in either it will return the hash it finds. If not found it will generate a
 * new hash and attempt to store the hash in HKLM/_REG_PATH, then HKCU/_REG_PATH.
 * Subsequent calls will then retreive the stored hash value. On any failure,
 * $AppUserModelID will be set to an empty string.
 *
 * A second string that will be used for Private Browsing mode is also generated by
 * taking the regular $AppUserModelID and appending ";PrivateBrowsingAUMID" to it.
 * This is stored in $AppUserModelIDPrivate upon successful completion of this
 * function.
 *
 * Registry format: root/_REG_PATH/"_EXE_PATH" = "hash"
 *
 * @param   _EXE_PATH
 *          The main application executable path
 * @param   _REG_PATH
 *          The HKLM/HKCU agnostic registry path where the key hash should
 *          be stored. ex: "Software\Mozilla\Firefox\TaskBarIDs"
 * @result  (Var) $AppUserModelID and $AppUserModelIDPrivate contain the
 *          app model id and the private app model id, respectively.
 */
!macro InitHashAppModelId
  !ifndef ${_MOZFUNC_UN}InitHashAppModelId
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}GetLongPath
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !ifndef InitHashAppModelId
      Var AppUserModelID
      Var AppUserModelIDPrivate
    !endif

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}InitHashAppModelId "!insertmacro ${_MOZFUNC_UN}InitHashAppModelIdCall"

    Function ${_MOZFUNC_UN}InitHashAppModelId
      ; stack: apppath, regpath
      Exch $R9 ; stack: $R9, regpath | $R9 = apppath
      Exch 1   ; stack: regpath, $R9
      Exch $R8 ; stack: $R8, $R9   | $R8 = regpath
      Push $R7

      ${${_MOZFUNC_UN}GetLongPath} "$R9" $R9
      ; Always create a new AppUserModelID and overwrite the existing one
      ; for the current installation path.
      CityHash::GetCityHash64 "$R9"
      Pop $AppUserModelID
      ${If} $AppUserModelID == "error"
        GoTo end
      ${EndIf}
      ClearErrors
      WriteRegStr HKLM "$R8" "$R9" "$AppUserModelID"
      ${If} ${Errors}
        ClearErrors
        WriteRegStr HKCU "$R8" "$R9" "$AppUserModelID"
        ${If} ${Errors}
          StrCpy $AppUserModelID "error"
        ${EndIf}
      ${EndIf}

      end:
      ${If} "$AppUserModelID" == "error"
        StrCpy $AppUserModelID ""
        StrCpy $AppUserModelIDPrivate ""
      ${Else}
        StrCpy $AppUserModelIDPrivate "$AppUserModelID;PrivateBrowsingAUMID"
      ${EndIf}

      ClearErrors
      Pop $R7
      Exch $R8
      Exch 1
      Exch $R9
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro InitHashAppModelIdCall _EXE_PATH _REG_PATH
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_REG_PATH}"
  Push "${_EXE_PATH}"
  Call InitHashAppModelId
  !verbose pop
!macroend

!macro un.InitHashAppModelIdCall _EXE_PATH _REG_PATH
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_REG_PATH}"
  Push "${_EXE_PATH}"
  Call un.InitHashAppModelId
  !verbose pop
!macroend

!macro un.InitHashAppModelId
  !ifndef un.InitHashAppModelId
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro InitHashAppModelId

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Try to locate the default profile of this install from AppUserModelID
 * using installs.ini.
 * FIXME This could instead use the Install<AUMID> entries in profiles.ini?
 *
 * - `SetShellVarContext current` must be called before this macro so
 *   $APPDATA gets the current user's data.
 * - InitHashAppModelId must have been called before this macro to set
 *   $AppUserModelID
 *
 * @result: Path of the profile directory (not checked for existence),
 *          or "" if not found, left on top of the stack.
 */
!macro FindInstallSpecificProfileMaybeUn _MOZFUNC_UN
  Push $R0
  Push $0
  Push $1
  Push $2

  StrCpy $R0 ""
  ; Look for an install-specific profile, which might be listed as
  ; either a relative or an absolute path (installs.ini doesn't say which).
  ${If} ${FileExists} "$APPDATA\Mozilla\Firefox\installs.ini"
    ClearErrors
    ReadINIStr $1 "$APPDATA\Mozilla\Firefox\installs.ini" "$AppUserModelID" "Default"
    ${IfNot} ${Errors}
      ${${_MOZFUNC_UN}GetLongPath} "$APPDATA\Mozilla\Firefox\$1" $2
      ${If} ${FileExists} $2
        StrCpy $R0 $2
      ${Else}
        ${${_MOZFUNC_UN}GetLongPath} "$1" $2
        ${If} ${FileExists} $2
          StrCpy $R0 $2
        ${EndIf}
      ${EndIf}
    ${EndIf}
  ${EndIf}

  Pop $2
  Pop $1
  Pop $0
  Exch $R0
!macroend
!define FindInstallSpecificProfile "!insertmacro FindInstallSpecificProfileMaybeUn ''"
!define un.FindInstallSpecificProfile "!insertmacro FindInstallSpecificProfileMaybeUn un."

/**
 * Copy the post-signing data, which was left alongside the installer
 * by the self-extractor stub, into the global location for this data.
 *
 * If the post-signing data file doesn't exist, or is empty, "0" is
 * pushed on the stack, and nothing is copied.
 * Otherwise the first line of the post-signing data (including newline,
 * if any) is pushed on the stack.
 */
!macro CopyPostSigningData
  !ifndef CopyPostSigningData
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define CopyPostSigningData "Call CopyPostSigningData"

    Function CopyPostSigningData
      Push $0   ; Stack: old $0
      Push $1   ; Stack: $1, old $0

      ${LineRead} "$EXEDIR\postSigningData" "1" $0
      ${If} ${Errors}
        ClearErrors
        StrCpy $0 "0"
      ${Else}
        CopyFiles /SILENT "$EXEDIR\postSigningData" "$INSTDIR"
      ${Endif}

      Pop $1    ; Stack: old $0
      Exch $0   ; Stack: postSigningData
    FunctionEnd

    !verbose pop
  !endif
!macroend

################################################################################
# Helpers for taskbar progress

!ifndef CLSCTX_INPROC_SERVER
  !define CLSCTX_INPROC_SERVER  1
!endif

!define CLSID_ITaskbarList {56fdf344-fd6d-11d0-958a-006097c9a090}
!define IID_ITaskbarList3 {ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf}
!define ITaskbarList3->SetProgressValue $ITaskbarList3->9
!define ITaskbarList3->SetProgressState $ITaskbarList3->10

/**
 * Creates a single uninitialized object of the ITaskbarList class with a
 * reference to the ITaskbarList3 interface. This object can be used to set
 * progress and state on the installer's taskbar icon using the helper macros
 * in this section.
 */
!macro ITBL3Create

  !ifndef ${_MOZFUNC_UN}ITBL3Create
    Var ITaskbarList3

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}ITBL3Create "!insertmacro ${_MOZFUNC_UN}ITBL3CreateCall"

    Function ${_MOZFUNC_UN}ITBL3Create
      ; Setting to 0 allows the helper macros to detect when the object was not
      ; created.
      StrCpy $ITaskbarList3 0
      ; Don't create when running silently.
      ${Unless} ${Silent}
        System::Call "ole32::CoCreateInstance(g '${CLSID_ITaskbarList}', \
                                              i 0, \
                                              i ${CLSCTX_INPROC_SERVER}, \
                                              g '${IID_ITaskbarList3}', \
                                              *i .s)"
        Pop $ITaskbarList3
      ${EndUnless}
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro ITBL3CreateCall
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call ITBL3Create
  !verbose pop
!macroend

!macro un.ITBL3CreateCall _PATH_TO_IMAGE
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Call un.ITBL3Create
  !verbose pop
!macroend

!macro un.ITBL3Create
  !ifndef un.ITBL3Create
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro ITBL3Create

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Sets the percentage completed on the taskbar process icon progress indicator.
 *
 * @param   _COMPLETED
 *          The proportion of the operation that has been completed in relation
 *          to _TOTAL.
 * @param   _TOTAL
 *          The value _COMPLETED will have when the operation has completed.
 *
 * $R8 = _COMPLETED
 * $R9 = _TOTAL
 */
!macro ITBL3SetProgressValueCall _COMPLETED _TOTAL
  Push ${_COMPLETED}
  Push ${_TOTAL}
  ${CallArtificialFunction} ITBL3SetProgressValue_
!macroend

!define ITBL3SetProgressValue "!insertmacro ITBL3SetProgressValueCall"
!define un.ITBL3SetProgressValue "!insertmacro ITBL3SetProgressValueCall"

!macro ITBL3SetProgressValue_
  Exch $R9
  Exch 1
  Exch $R8
  ${If} $ITaskbarList3 <> 0
    System::Call "${ITaskbarList3->SetProgressValue}(i$HWNDPARENT, l$R8, l$R9)"
  ${EndIf}
  Exch $R8
  Exch 1
  Exch $R9
!macroend

; Normal state / no progress bar
!define TBPF_NOPROGRESS       0x00000000
; Marquee style progress bar
!define TBPF_INDETERMINATE    0x00000001
; Standard progress bar
!define TBPF_NORMAL           0x00000002
; Red taskbar button to indicate an error occurred
!define TBPF_ERROR            0x00000004
; Yellow taskbar button to indicate user attention (input) is required to
; resume progress
!define TBPF_PAUSED           0x00000008

/**
 * Sets the state on the taskbar process icon progress indicator.
 *
 * @param   _STATE
 *          The state to set on the taskbar icon progress indicator. Only one of
 *          the states defined above should be specified.
 *
 * $R9 = _STATE
 */
!macro ITBL3SetProgressStateCall _STATE
  Push ${_STATE}
  ${CallArtificialFunction} ITBL3SetProgressState_
!macroend

!define ITBL3SetProgressState "!insertmacro ITBL3SetProgressStateCall"
!define un.ITBL3SetProgressState "!insertmacro ITBL3SetProgressStateCall"

!macro ITBL3SetProgressState_
  Exch $R9
  ${If} $ITaskbarList3 <> 0
    System::Call "${ITaskbarList3->SetProgressState}(i$HWNDPARENT, i$R9)"
  ${EndIf}
  Exch $R9
!macroend

################################################################################
# Helpers for the new user interface

!ifndef MAXDWORD
  !define MAXDWORD 0xffffffff
!endif

!ifndef DT_WORDBREAK
  !define DT_WORDBREAK 0x0010
!endif
!ifndef DT_SINGLELINE
  !define DT_SINGLELINE 0x0020
!endif
!ifndef DT_NOCLIP
  !define DT_NOCLIP 0x0100
!endif
!ifndef DT_CALCRECT
  !define DT_CALCRECT 0x0400
!endif
!ifndef DT_EDITCONTROL
  !define DT_EDITCONTROL 0x2000
!endif
!ifndef DT_RTLREADING
  !define DT_RTLREADING 0x00020000
!endif
!ifndef DT_NOFULLWIDTHCHARBREAK
  !define DT_NOFULLWIDTHCHARBREAK 0x00080000
!endif

!define /ifndef GWL_STYLE -16
!define /ifndef GWL_EXSTYLE -20

!ifndef WS_EX_NOINHERITLAYOUT
  !define WS_EX_NOINHERITLAYOUT 0x00100000
!endif

!ifndef PBS_MARQUEE
  !define PBS_MARQUEE 0x08
!endif

!ifndef PBM_SETRANGE32
  !define PBM_SETRANGE32 0x406
!endif
!ifndef PBM_GETRANGE
  !define PBM_GETRANGE 0x407
!endif

!ifndef SHACF_FILESYSTEM
  !define SHACF_FILESYSTEM 1
!endif

!define MOZ_LOADTRANSPARENT ${LR_LOADFROMFILE}|${LR_LOADTRANSPARENT}|${LR_LOADMAP3DCOLORS}

; Extend nsDialogs.nsh to support creating centered labels if it is already
; included
!ifmacrodef __NSD_DefineControl
!insertmacro __NSD_DefineControl LabelCenter
!define __NSD_LabelCenter_CLASS STATIC
!define __NSD_LabelCenter_STYLE ${DEFAULT_STYLES}|${SS_NOTIFY}|${SS_CENTER}
!define __NSD_LabelCenter_EXSTYLE ${WS_EX_TRANSPARENT}
!endif

/**
 * Draws an image file (BMP, GIF, or JPG) onto a bitmap control, with scaling.
 * Adapted from https://stackoverflow.com/a/13405711/1508094
 *
 * @param CONTROL bitmap control created by NSD_CreateBitmap
 * @param IMAGE path to image file to draw to the bitmap
 * @param HANDLE output bitmap handle which must be freed via NSD_FreeImage
 *               after nsDialogs::Show has been called
 */
!macro __SetStretchedImageOLE CONTROL IMAGE HANDLE
  !ifndef IID_IPicture
    !define IID_IPicture {7BF80980-BF32-101A-8BBB-00AA00300CAB}
  !endif
  !ifndef SRCCOPY
    !define SRCCOPY 0xCC0020
  !endif
  !ifndef HALFTONE
    !define HALFTONE 4
  !endif
  !ifndef IMAGE_BITMAP
    !define IMAGE_BITMAP 0
  !endif

  Push $0 ; HANDLE
  Push $1 ; memory DC
  Push $2 ; IPicture created from IMAGE
  Push $3 ; HBITMAP obtained from $2
  Push $4 ; BITMAPINFO obtained from $3
  Push $5 ; width of IMAGE
  Push $6 ; height of IMAGE
  Push $7 ; width of CONTROL
  Push $8 ; height of CONTROL
  Push $R0 ; CONTROL

  StrCpy $R0 ${CONTROL} ; in case ${CONTROL} is $0
  StrCpy $7 ""
  StrCpy $8 ""

  System::Call '*(i, i, i, i) i.s'
  Pop $0

  ${If} $0 <> 0
    System::Call 'user32::GetClientRect(i R0, i r0)'
    System::Call '*$0(i, i, i .s, i .s)'
    System::Free $0
    Pop $7
    Pop $8
  ${EndIf}

  ${If} $7 > 0
  ${AndIf} $8 > 0
    System::Call 'oleaut32::OleLoadPicturePath(w"${IMAGE}",i0,i0,i0,g"${IID_IPicture}",*i.r2)i.r1'
    ${If} $1 = 0
      System::Call 'user32::GetDC(i0)i.s'
      System::Call 'gdi32::CreateCompatibleDC(iss)i.r1'
      System::Call 'gdi32::CreateCompatibleBitmap(iss,ir7,ir8)i.r0'
      System::Call 'user32::ReleaseDC(i0,is)'
      System::Call '$2->3(*i.r3)i.r4' ; IPicture->get_Handle
      ${If} $4 = 0
        System::Call 'gdi32::SetStretchBltMode(ir1,i${HALFTONE})'
        System::Call '*(&i40,&i1024)i.r4' ; BITMAP / BITMAPINFO
        System::Call 'gdi32::GetObject(ir3,i24,ir4)'
        System::Call 'gdi32::SelectObject(ir1,ir0)i.s'
        System::Call '*$4(i40,i.r5,i.r6,i0,i,i.s)' ; Grab size and bits-ptr AND init as BITMAPINFOHEADER
        System::Call 'gdi32::GetDIBits(ir1,ir3,i0,i0,i0,ir4,i0)' ; init BITMAPINFOHEADER
        System::Call 'gdi32::GetDIBits(ir1,ir3,i0,i0,i0,ir4,i0)' ; init BITMAPINFO
        System::Call 'gdi32::StretchDIBits(ir1,i0,i0,ir7,ir8,i0,i0,ir5,ir6,is,ir4,i0,i${SRCCOPY})'
        System::Call 'gdi32::SelectObject(ir1,is)'
        System::Free $4
        SendMessage $R0 ${STM_SETIMAGE} ${IMAGE_BITMAP} $0
      ${EndIf}
      System::Call 'gdi32::DeleteDC(ir1)'
      System::Call '$2->2()' ; IPicture->release()
    ${EndIf}
  ${EndIf}

  Pop $R0
  Pop $8
  Pop $7
  Pop $6
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Pop $1
  Exch $0
  Pop ${HANDLE}
!macroend
!define SetStretchedImageOLE "!insertmacro __SetStretchedImageOLE"

/**
 * Display a task dialog box with custom strings and button labels.
 *
 * The task dialog is highly customizable. The specific style being used here
 * is similar to a MessageBox except that the button text is customizable.
 * MessageBox-style buttons are used instead of command link buttons; this can
 * be made configurable if command buttons are needed.
 *
 * See https://msdn.microsoft.com/en-us/library/windows/desktop/bb760544.aspx
 * for the TaskDialogIndirect function's documentation, and links to definitions
 * of the TASKDIALOGCONFIG and TASKDIALOG_BUTTON structures it uses.
 *
 * @param INSTRUCTION  Dialog header string; use empty string if unneeded
 * @param CONTENT      Secondary message string; use empty string if unneeded
 * @param BUTTON1      Text for the first button, the one selected by default
 * @param BUTTON2      Text for the second button
 *
 * @return One of the following values will be left on the stack:
 *         1001 if the first button was clicked
 *         1002 if the second button was clicked
 *         2 (IDCANCEL) if the dialog was closed
 *         0 on error
 */
!macro _ShowTaskDialog INSTRUCTION CONTENT BUTTON1 BUTTON2
  !ifndef SIZEOF_TASKDIALOGCONFIG_32BIT
    !define SIZEOF_TASKDIALOGCONFIG_32BIT 96
  !endif
  !ifndef TDF_ALLOW_DIALOG_CANCELLATION
    !define TDF_ALLOW_DIALOG_CANCELLATION 0x0008
  !endif
  !ifndef TDF_RTL_LAYOUT
    !define TDF_RTL_LAYOUT 0x02000
  !endif
  !ifndef TD_WARNING_ICON
    !define TD_WARNING_ICON 0x0FFFF
  !endif

  Push $0 ; return value
  Push $1 ; TASKDIALOGCONFIG struct
  Push $2 ; TASKDIALOG_BUTTON array
  Push $3 ; dwFlags member of the TASKDIALOGCONFIG

  StrCpy $3 ${TDF_ALLOW_DIALOG_CANCELLATION}
  !ifdef ${AB_CD}_rtl
    IntOp $3 $3 | ${TDF_RTL_LAYOUT}
  !endif

  ; Build an array of two TASKDIALOG_BUTTON structs
  System::Call "*(i 1001, \
                  w '${BUTTON1}', \
                  i 1002, \
                  w '${BUTTON2}' \
                  ) p.r2"
  ; Build a TASKDIALOGCONFIG struct
  System::Call "*(i ${SIZEOF_TASKDIALOGCONFIG_32BIT}, \
                  p $HWNDPARENT, \
                  p 0, \
                  i $3, \
                  i 0, \
                  w '$(INSTALLER_WIN_CAPTION)', \
                  p ${TD_WARNING_ICON}, \
                  w '${INSTRUCTION}', \
                  w '${CONTENT}', \
                  i 2, \
                  p r2, \
                  i 1001, \
                  i 0, \
                  p 0, \
                  i 0, \
                  p 0, \
                  p 0, \
                  p 0, \
                  p 0, \
                  p 0, \
                  p 0, \
                  p 0, \
                  p 0, \
                  i 0 \
                  ) p.r1"
  System::Call "comctl32::TaskDialogIndirect(p r1, *i 0 r0, p 0, p 0)"
  System::Free $1
  System::Free $2

  Pop $3
  Pop $2
  Pop $1
  Exch $0
!macroend
!define ShowTaskDialog "!insertmacro _ShowTaskDialog"

/**
 * Removes a single style from a control.
 *
 * _HANDLE the handle of the control
 * _STYLE  the style to remove
 */
!macro _RemoveStyle _HANDLE _STYLE
  Push $0

  System::Call 'user32::GetWindowLongW(i ${_HANDLE}, i ${GWL_STYLE}) i .r0'
  IntOp $0 $0 | ${_STYLE}
  IntOp $0 $0 - ${_STYLE}
  System::Call 'user32::SetWindowLongW(i ${_HANDLE}, i ${GWL_STYLE}, i r0)'

  Pop $0
!macroend
!define RemoveStyle "!insertmacro _RemoveStyle"

/**
 * Adds a single extended style to a control.
 *
 * _HANDLE  the handle of the control
 * _EXSTYLE the extended style to add
 */
!macro _AddExStyle _HANDLE _EXSTYLE
  Push $0

  System::Call 'user32::GetWindowLongW(i ${_HANDLE}, i ${GWL_EXSTYLE}) i .r0'
  IntOp $0 $0 | ${_EXSTYLE}
  System::Call 'user32::SetWindowLongW(i ${_HANDLE}, i ${GWL_EXSTYLE}, i r0)'

  Pop $0
!macroend
!define AddExStyle "!insertmacro _AddExStyle"

/**
 * Removes a single extended style from a control.
 *
 * _HANDLE  the handle of the control
 * _EXSTYLE the extended style to remove
 */
!macro _RemoveExStyle _HANDLE _EXSTYLE
  Push $0

  System::Call 'user32::GetWindowLongW(i ${_HANDLE}, i ${GWL_EXSTYLE}) i .r0'
  IntOp $0 $0 | ${_EXSTYLE}
  IntOp $0 $0 - ${_EXSTYLE}
  System::Call 'user32::SetWindowLongW(i ${_HANDLE}, i ${GWL_EXSTYLE}, i r0)'

  Pop $0
!macroend
!define RemoveExStyle "!insertmacro _RemoveExStyle"

/**
 * Set the necessary styles to configure the given window as right-to-left
 *
 * _HANDLE the handle of the control to configure
 */
!macro _MakeWindowRTL _HANDLE
  !define /ifndef WS_EX_RIGHT 0x00001000
  !define /ifndef WS_EX_LEFT 0x00000000
  !define /ifndef WS_EX_RTLREADING 0x00002000
  !define /ifndef WS_EX_LTRREADING 0x00000000
  !define /ifndef WS_EX_LAYOUTRTL 0x00400000

  ${AddExStyle} ${_HANDLE} ${WS_EX_LAYOUTRTL}
  ${RemoveExStyle} ${_HANDLE} ${WS_EX_RTLREADING}
  ${RemoveExStyle} ${_HANDLE} ${WS_EX_RIGHT}
  ${AddExStyle} ${_HANDLE} ${WS_EX_LEFT}|${WS_EX_LTRREADING}
!macroend
!define MakeWindowRTL "!insertmacro _MakeWindowRTL"

/**
 * Gets the extent of the specified text in pixels for sizing a control.
 *
 * _TEXT       the text to get the text extent for
 * _FONT       the font to use when getting the text extent
 * _RES_WIDTH  return value - control width for the text
 * _RES_HEIGHT return value - control height for the text
 */
!macro GetTextExtentCall _TEXT _FONT _RES_WIDTH _RES_HEIGHT
  Push "${_TEXT}"
  Push "${_FONT}"
  ${CallArtificialFunction} GetTextExtent_
  Pop ${_RES_WIDTH}
  Pop ${_RES_HEIGHT}
!macroend

!define GetTextExtent "!insertmacro GetTextExtentCall"
!define un.GetTextExtent "!insertmacro GetTextExtentCall"

!macro GetTextExtent_
  Exch $0 ; font
  Exch 1
  Exch $1 ; text
  Push $2
  Push $3
  Push $4
  Push $5
  Push $6
  Push $7

  ; Reuse the existing NSIS control which is used for BrandingText instead of
  ; creating a new control.
  GetDlgItem $2 $HWNDPARENT 1028

  System::Call 'user32::GetDC(i r2) i .r3'
  System::Call 'gdi32::SelectObject(i r3, i r0)'

  StrLen $4 "$1"

  System::Call '*(i, i) i .r5'
  System::Call 'gdi32::GetTextExtentPoint32W(i r3, t$\"$1$\", i r4, i r5)'
  System::Call '*$5(i .r6, i .r7)'
  System::Free $5

  System::Call 'user32::ReleaseDC(i r2, i r3)'

  StrCpy $1 $7
  StrCpy $0 $6

  Pop $7
  Pop $6
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Exch $1 ; return height
  Exch 1
  Exch $0 ; return width
!macroend

/**
 * Gets the width and the height of a control in pixels.
 *
 * _HANDLE     the handle of the control
 * _RES_WIDTH  return value - control width for the text
 * _RES_HEIGHT return value - control height for the text
 */
!macro GetDlgItemWidthHeightCall _HANDLE _RES_WIDTH _RES_HEIGHT
  Push "${_HANDLE}"
  ${CallArtificialFunction} GetDlgItemWidthHeight_
  Pop ${_RES_WIDTH}
  Pop ${_RES_HEIGHT}
!macroend

!define GetDlgItemWidthHeight "!insertmacro GetDlgItemWidthHeightCall"
!define un.GetDlgItemWidthHeight "!insertmacro GetDlgItemWidthHeightCall"

!macro GetDlgItemWidthHeight_
  Exch $0 ; handle for the control
  Push $1
  Push $2

  System::Call '*(i, i, i, i) i .r2'
  ; The left and top values will always be 0 so the right and bottom values
  ; will be the width and height.
  System::Call 'user32::GetClientRect(i r0, i r2)'
  System::Call '*$2(i, i, i .r0, i .r1)'
  System::Free $2

  Pop $2
  Exch $1 ; return height
  Exch 1
  Exch $0 ; return width
!macroend

/**
 * Gets the number of pixels from the beginning of the dialog to the end of a
 * control in a RTL friendly manner.
 *
 * _HANDLE the handle of the control
 * _RES_PX return value - pixels from the beginning of the dialog to the end of
 *         the control
 */
!macro GetDlgItemEndPXCall _HANDLE _RES_PX
  Push "${_HANDLE}"
  ${CallArtificialFunction} GetDlgItemEndPX_
  Pop ${_RES_PX}
!macroend

!define GetDlgItemEndPX "!insertmacro GetDlgItemEndPXCall"
!define un.GetDlgItemEndPX "!insertmacro GetDlgItemEndPXCall"

!macro GetDlgItemEndPX_
  Exch $0 ; handle of the control
  Push $1
  Push $2

  ; #32770 is the dialog class
  FindWindow $1 "#32770" "" $HWNDPARENT
  System::Call '*(i, i, i, i) i .r2'
  System::Call 'user32::GetWindowRect(i r0, i r2)'
  System::Call 'user32::MapWindowPoints(i 0, i r1,i r2, i 2)'
  System::Call '*$2(i, i, i .r0, i)'
  System::Free $2

  Pop $2
  Pop $1
  Exch $0 ; pixels from the beginning of the dialog to the end of the control
!macroend

/**
 * Gets the number of pixels from the top of a dialog to the bottom of a control
 *
 * _CONTROL the handle of the control
 * _RES_PX  return value - pixels from the top of the dialog to the bottom
 *          of the control
 */
!macro GetDlgItemBottomPXCall _CONTROL _RES_PX
  Push "${_CONTROL}"
  ${CallArtificialFunction} GetDlgItemBottomPX_
  Pop ${_RES_PX}
!macroend

!define GetDlgItemBottomPX "!insertmacro GetDlgItemBottomPXCall"
!define un.GetDlgItemBottomPX "!insertmacro GetDlgItemBottomPXCall"

!macro GetDlgItemBottomPX_
  Exch $0 ; handle of the control
  Push $1
  Push $2

  ; #32770 is the dialog class
  FindWindow $1 "#32770" "" $HWNDPARENT
  System::Call '*(i, i, i, i) i .r2'
  System::Call 'user32::GetWindowRect(i r0, i r2)'
  System::Call 'user32::MapWindowPoints(i 0, i r1, i r2, i 2)'
  System::Call '*$2(i, i, i, i .r0)'
  System::Free $2

  Pop $2
  Pop $1
  Exch $0 ; pixels from the top of the dialog to the bottom of the control
!macroend

/**
 * Gets the width and height for sizing a control that has the specified text.
 * The control's height and width will be dynamically determined for the maximum
 * width specified.
 *
 * _TEXT       the text
 * _FONT       the font to use when getting the width and height
 * _MAX_WIDTH  the maximum width for the control in pixels
 * _RES_WIDTH  return value - control width for the text in pixels.
 *             This might be larger than _MAX_WIDTH if that constraint couldn't
 *             be satisfied, e.g. a single word that couldn't be broken up is
 *             longer than _MAX_WIDTH by itself.
 * _RES_HEIGHT return value - control height for the text in pixels
 */
!macro GetTextWidthHeight

  !ifndef ${_MOZFUNC_UN}GetTextWidthHeight
    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
    !insertmacro ${_MOZFUNC_UN_TMP}WordFind
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
    !undef _MOZFUNC_UN_TMP

    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !define ${_MOZFUNC_UN}GetTextWidthHeight "!insertmacro ${_MOZFUNC_UN}GetTextWidthHeightCall"

    Function ${_MOZFUNC_UN}GetTextWidthHeight
      ; Stack contents after each instruction (top of the stack on the left):
      ;          _MAX_WIDTH _FONT _TEXT
      Exch $0  ; $0 _FONT _TEXT
      Exch 1   ; _FONT $0 _TEXT
      Exch $1  ; $1 $0 _TEXT
      Exch 2   ; _TEXT $0 $1
      Exch $2  ; $2 $0 $1
      ; That's all the parameters, now save our scratch registers.
      Push $3  ; handle to a temporary control for drawing the text into
      Push $4  ; DC handle
      Push $5  ; string length of the text argument
      Push $6  ; RECT struct to call DrawText with
      Push $7  ; width returned from DrawText
      Push $8  ; height returned from DrawText
      Push $9  ; flags to pass to DrawText

      StrCpy $9 "${DT_NOCLIP}|${DT_CALCRECT}|${DT_WORDBREAK}"
      !ifdef ${AB_CD}_rtl
        StrCpy $9 "$9|${DT_RTLREADING}"
      !endif

      ; Reuse the existing NSIS control which is used for BrandingText instead
      ; of creating a new control.
      GetDlgItem $3 $HWNDPARENT 1028

      System::Call 'user32::GetDC(i r3) i .r4'
      System::Call 'gdi32::SelectObject(i r4, i r1)'

      StrLen $5 "$2" ; text length
      System::Call '*(i, i, i r0, i) i .r6'

      System::Call 'user32::DrawTextW(i r4, t $\"$2$\", i r5, i r6, i r9)'
      System::Call '*$6(i, i, i .r7, i .r8)'
      System::Free $6

      System::Call 'user32::ReleaseDC(i r3, i r4)'

      StrCpy $1 $8
      StrCpy $0 $7

      ; Restore the values that were in our scratch registers.
      Pop $9
      Pop $8
      Pop $7
      Pop $6
      Pop $5
      Pop $4
      Pop $3
      ; Restore our parameter registers and return our results.
      ; Stack contents after each instruction (top of the stack on the left):
      ;         $2 $0 $1
      Pop $2  ; $0 $1
      Exch 1  ; $1 $0
      Exch $1 ; _RES_HEIGHT $0
      Exch 1  ; $0 _RES_HEIGHT
      Exch $0 ; _RES_WIDTH _RES_HEIGHT
    FunctionEnd

    !verbose pop
  !endif
!macroend

!macro GetTextWidthHeightCall _TEXT _FONT _MAX_WIDTH _RES_WIDTH _RES_HEIGHT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_TEXT}"
  Push "${_FONT}"
  Push "${_MAX_WIDTH}"
  Call GetTextWidthHeight
  Pop ${_RES_WIDTH}
  Pop ${_RES_HEIGHT}
  !verbose pop
!macroend

!macro un.GetTextWidthHeightCall _TEXT _FONT _MAX_WIDTH _RES_WIDTH _RES_HEIGHT
  !verbose push
  !verbose ${_MOZFUNC_VERBOSE}
  Push "${_TEXT}"
  Push "${_FONT}"
  Push "${_MAX_WIDTH}"
  Call un.GetTextWidthHeight
  Pop ${_RES_WIDTH}
  Pop ${_RES_HEIGHT}
  !verbose pop
!macroend

!macro un.GetTextWidthHeight
  !ifndef un.GetTextWidthHeight
    !verbose push
    !verbose ${_MOZFUNC_VERBOSE}
    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN "un."

    !insertmacro GetTextWidthHeight

    !undef _MOZFUNC_UN
    !define _MOZFUNC_UN
    !verbose pop
  !endif
!macroend

/**
 * Convert a number of dialog units to a number of pixels.
 *
 * _DU    Number of dialog units to convert
 * _AXIS  Which axis you want to convert a value along, X or Y
 * _RV    Register or variable to return the number of pixels in
 */
!macro _DialogUnitsToPixels _DU _AXIS _RV
  Push $0
  Push $1

  ; The dialog units value might be a string ending with a 'u',
  ; so convert it to a number.
  IntOp $0 "${_DU}" + 0

  !if ${_AXIS} == 'Y'
    System::Call '*(i 0, i 0, i 0, i r0) i .r1'
    System::Call 'user32::MapDialogRect(p $HWNDPARENT, p r1)'
    System::Call '*$1(i, i, i, i . r0)'
  !else if ${_AXIS} == 'X'
    System::Call '*(i 0, i 0, i r0, i 0) i .r1'
    System::Call 'user32::MapDialogRect(p $HWNDPARENT, p r1)'
    System::Call '*$1(i, i, i . r0, i)'
  !else
    !error "Invalid axis ${_AXIS} passed to DialogUnitsToPixels; please use X or Y"
  !endif
  System::Free $1

  Pop $1
  Exch $0
  Pop ${_RV}
!macroend
!define DialogUnitsToPixels "!insertmacro _DialogUnitsToPixels"

/**
 * Convert a given left coordinate for a dialog control to flip the control to
 * the other side of the dialog if we're using an RTL locale.
 *
 * _LEFT_DU   Number of dialog units to convert
 * _WIDTH     Width of the control in either pixels or DU's
 *            If the string has a 'u' on the end, it will be interpreted as
 *            dialog units, otherwise it will be interpreted as pixels.
 * _RV        Register or variable to return converted coordinate, in pixels
 */
!macro _ConvertLeftCoordForRTLCall _LEFT_DU _WIDTH _RV
  Push "${_LEFT_DU}"
  Push "${_WIDTH}"
  ${CallArtificialFunction} _ConvertLeftCoordForRTL
  Pop ${_RV}
!macroend

!define ConvertLeftCoordForRTL "!insertmacro _ConvertLeftCoordForRTLCall"
!define un.ConvertLeftCoordForRTL "!insertmacro _ConvertLeftCoordForRTLCall"

!macro _ConvertLeftCoordForRTL
  ; Stack contents after each instruction (top of the stack on the left):
  ;         _WIDTH _LEFT_DU
  Exch $0 ; $0 _LEFT_DU
  Exch 1  ; _LEFT_DU $0
  Exch $1 ; $1 $0
  ; That's all the parameters, now save our scratch registers.
  Push $2 ; width of the entire dialog, in pixels
  Push $3 ; _LEFT_DU converted to pixels
  Push $4 ; temp string search result

  !ifndef ${AB_CD}_rtl
    StrCpy $0 "$1"
  !else
    ${GetDlgItemWidthHeight} $HWNDPARENT $2 $3
    ${DialogUnitsToPixels} $1 X $3

    ClearErrors
    ${${_MOZFUNC_UN}WordFind} "$0" "u" "E+1{" $4
    ${IfNot} ${Errors}
      ${DialogUnitsToPixels} $4 X $0
    ${EndIf}

    IntOp $1 $2 - $3
    IntOp $1 $1 - $0
    StrCpy $0 $1
  !endif

  ; Restore the values that were in our scratch registers.
  Pop $4
  Pop $3
  Pop $2
  ; Restore our parameter registers and return our result.
  ; Stack contents after each instruction (top of the stack on the left):
  ;         $1 $0
  Pop $1  ; $0
  Exch $0 ; _RV
!macroend

/**
 * Gets the elapsed time in seconds between two values in milliseconds stored as
 * an int64. The caller will typically get the millisecond values using
 * GetTickCount with a long return value as follows.
 * System::Call "kernel32::GetTickCount()l .s"
 * Pop $varname
 *
 * _START_TICK_COUNT
 * _FINISH_TICK_COUNT
 * _RES_ELAPSED_SECONDS return value - elapsed time between _START_TICK_COUNT
 *                      and _FINISH_TICK_COUNT in seconds.
 */
!macro GetSecondsElapsedCall _START_TICK_COUNT _FINISH_TICK_COUNT _RES_ELAPSED_SECONDS
  Push "${_START_TICK_COUNT}"
  Push "${_FINISH_TICK_COUNT}"
  ${CallArtificialFunction} GetSecondsElapsed_
  Pop ${_RES_ELAPSED_SECONDS}
!macroend

!define GetSecondsElapsed "!insertmacro GetSecondsElapsedCall"
!define un.GetSecondsElapsed "!insertmacro GetSecondsElapsedCall"

!macro GetSecondsElapsed_
  Exch $0 ; finish tick count
  Exch 1
  Exch $1 ; start tick count

  System::Int64Op $0 - $1
  Pop $0
  ; Discard the top bits of the int64 by bitmasking with MAXDWORD
  System::Int64Op $0 & ${MAXDWORD}
  Pop $0

  ; Convert from milliseconds to seconds
  System::Int64Op $0 / 1000
  Pop $0

  Pop $1
  Exch $0 ; return elapsed seconds
!macroend

/**
 * Create a process to execute a command line. If it is successfully created,
 * wait on it with WaitForInputIdle, to avoid exiting the current process too
 * early (exiting early can cause the created process's windows to be opened in
 * the background).
 *
 * CMDLINE Is the command line to execute, like the argument to Exec
 */
!define ExecAndWaitForInputIdle "!insertmacro ExecAndWaitForInputIdle_"
!define CREATE_DEFAULT_ERROR_MODE 0x04000000
!macro ExecAndWaitForInputIdle_ CMDLINE
  ; derived from https://stackoverflow.com/a/13960786/3444805 by Anders Kjersem
  Push $0
  Push $1
  Push $2

  ; Command line
  StrCpy $0 "${CMDLINE}"

  ; STARTUPINFO
  System::Alloc 68
  Pop $1
  ; fill in STARTUPINFO.cb (first field) with sizeof(STARTUPINFO)
  System::Call "*$1(i 68)"

  ; PROCESS_INFORMATION
  System::Call "*(i, i, i, i) i . r2"

  ; CREATE_DEFAULT_ERROR_MODE follows NSIS myCreateProcess used in Exec
  System::Call "kernel32::CreateProcessW(i 0, t r0, i 0, i 0, i 0, i ${CREATE_DEFAULT_ERROR_MODE}, i 0, i 0, i r1, i r2) i . r0"

  System::Free $1
  ${If} $0 <> 0
    System::Call "*$2(i . r0, i . r1)"
    ; $0: hProcess, $1: hThread
    System::Call "user32::WaitForInputIdle(i $0, i 10000)"
    System::Call "kernel32::CloseHandle(i $0)"
    System::Call "kernel32::CloseHandle(i $1)"
  ${EndIf}
  System::Free $2

  Pop $2
  Pop $1
  Pop $0
!macroend

Function WriteRegQWORD
          ; Stack contents:
          ; VALUE, VALUE_NAME, SUBKEY, ROOTKEY
  Exch $3 ; $3, VALUE_NAME, SUBKEY, ROOTKEY
  Exch 1  ; VALUE_NAME, $3, SUBKEY, ROOTKEY
  Exch $2 ; $2, $3, SUBKEY, ROOTKEY
  Exch 2  ; SUBKEY, $3, $2, ROOTKEY
  Exch $1 ; $1, $3, $2, ROOTKEY
  Exch 3  ; ROOTKEY, $3, $2, $1
  Exch $0 ; $0, $3, $2, $1
  System::Call "advapi32::RegSetKeyValueW(p r0, w r1, w r2, i 11, *l r3, i 8) i.r0"
  ${IfNot} $0 = 0
    SetErrors
  ${EndIf}
  Pop $0
  Pop $3
  Pop $2
  Pop $1
FunctionEnd
!macro WriteRegQWORD ROOTKEY SUBKEY VALUE_NAME VALUE
  ${If} "${ROOTKEY}" == "HKCR"
    Push 0x80000000
  ${ElseIf} "${ROOTKEY}" == "HKCU"
    Push 0x80000001
  ${ElseIf} "${ROOTKEY}" == "HKLM"
    Push 0x80000002
  ${Endif}
  Push "${SUBKEY}"
  Push "${VALUE_NAME}"
  System::Int64Op ${VALUE} + 0 ; The result is pushed on the stack
  Call WriteRegQWORD
!macroend
!define WriteRegQWORD "!insertmacro WriteRegQWORD"

Function ReadRegQWORD
          ; Stack contents:
          ; VALUE_NAME, SUBKEY, ROOTKEY
  Exch $2 ; $2, SUBKEY, ROOTKEY
  Exch 1  ; SUBKEY, $2, ROOTKEY
  Exch $1 ; $1, $2, ROOTKEY
  Exch 2  ; ROOTKEY, $2, $1
  Exch $0 ; $0, $2, $1
  System::Call "advapi32::RegGetValueW(p r0, w r1, w r2, i 0x48, p 0, *l s, *i 8) i.r0"
  ${IfNot} $0 = 0
    SetErrors
  ${EndIf}
          ; VALUE, $0, $2, $1
  Exch 3  ; $1, $0, $2, VALUE
  Pop $1  ; $0, $2, VALUE
  Pop $0  ; $2, VALUE
  Pop $2  ; VALUE
FunctionEnd
!macro ReadRegQWORD DEST ROOTKEY SUBKEY VALUE_NAME
  ${If} "${ROOTKEY}" == "HKCR"
    Push 0x80000000
  ${ElseIf} "${ROOTKEY}" == "HKCU"
    Push 0x80000001
  ${ElseIf} "${ROOTKEY}" == "HKLM"
    Push 0x80000002
  ${Endif}
  Push "${SUBKEY}"
  Push "${VALUE_NAME}"
  Call ReadRegQWORD
  Pop ${DEST}
!macroend
!define ReadRegQWORD "!insertmacro ReadRegQWORD"


[Dauer der Verarbeitung: 0.180 Sekunden, vorverarbeitet 2026-06-05]