Import Tycho's Add-on Manager

This commit is contained in:
NTD 2018-02-10 04:00:58 -05:00 • committed by Roy Tam
commit a122115666
875 changed files with 111377 additions and 0 deletions

View file

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

View file

@ -0,0 +1,17 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY blocklist.title "Add-ons may be causing problems">
<!ENTITY blocklist.style "width: 45em; height: 30em">
<!ENTITY blocklist.summary "&brandShortName; has determined that the following add-ons are known to cause stability or security problems:">
<!ENTITY blocklist.softblocked "For your protection, it is highly recommended that you restart with these add-ons disabled.">
<!ENTITY blocklist.hardblocked "These add-ons have a high risk of causing stability or security problems and have been blocked, but a restart is required to disable them completely.">
<!ENTITY blocklist.softandhard "The add-ons that have a high risk of causing stability or security problems have been blocked. The others are lower risk, but it is highly recommended that you restart with them disabled.">
<!ENTITY blocklist.moreinfo "More information">
<!ENTITY blocklist.accept.label "Restart &brandShortName;">
<!ENTITY blocklist.accept.accesskey "R">
<!ENTITY blocklist.blocked.label "Blocked">
<!ENTITY blocklist.checkbox.label "Disable">

View file

@ -0,0 +1,236 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY addons.windowTitle "Add-ons Manager">
<!ENTITY search.placeholder "Search all add-ons">
<!ENTITY search.buttonlabel "Search">
<!-- LOCALIZATION NOTE (search.commandKey):
The search command key should match findOnCmd.commandkey from browser.dtd -->
<!ENTITY search.commandkey "f">
<!ENTITY loading.label "Loading…">
<!ENTITY listEmpty.installed.label "You don't have any add-ons of this type installed">
<!ENTITY listEmpty.availableUpdates.label "No updates found">
<!ENTITY listEmpty.recentUpdates.label "You haven't recently updated any add-ons">
<!ENTITY listEmpty.findUpdates.label "Check For Updates">
<!ENTITY listEmpty.search.label "Could not find any matching add-ons">
<!ENTITY listEmpty.button.label "Learn more about add-ons">
<!ENTITY installAddonFromFile.label "Install Add-on From File…">
<!ENTITY installAddonFromFile.accesskey "I">
<!ENTITY toolsMenu.tooltip "Tools for all add-ons">
<!ENTITY cmd.back.tooltip "Go back one page">
<!ENTITY cmd.forward.tooltip "Go forward one page">
<!-- global warnings -->
<!ENTITY warning.safemode.label "All add-ons have been disabled by safe mode.">
<!ENTITY warning.checkcompatibility.label "Add-on compatibility checking is disabled. You may have incompatible add-ons.">
<!ENTITY warning.checkcompatibility.enable.label "Enable">
<!ENTITY warning.checkcompatibility.enable.tooltip "Enable add-on compatibility checking">
<!ENTITY warning.updatesecurity.label "Add-on update security checking is disabled. You may be compromised by updates.">
<!ENTITY warning.updatesecurity.enable.label "Enable">
<!ENTITY warning.updatesecurity.enable.tooltip "Enable add-on update security checking">
<!-- global informations -->
<!ENTITY info.plugincheck.label "Check to see if your plugins are up to date">
<!ENTITY info.plugincheck.tooltip "Check to see if your plugins are up to date">
<!-- categories / views -->
<!ENTITY view.search.label "Search">
<!ENTITY view.discover.label "Get Add-ons">
<!ENTITY view.recentUpdates.label "Recent Updates">
<!ENTITY view.availableUpdates.label "Available Updates">
<!-- addon updates -->
<!ENTITY updates.checkForUpdates.label "Check for Updates">
<!ENTITY updates.checkForUpdates.accesskey "C">
<!ENTITY updates.viewUpdates.label "View Recent Updates">
<!ENTITY updates.viewUpdates.accesskey "V">
<!-- LOCALIZATION NOTE (updates.updateAddonsAutomatically.label): This menu item
is a checkbox that toggles the default global behavior for add-on update
checking. -->
<!ENTITY updates.updateAddonsAutomatically.label "Update Add-ons Automatically">
<!ENTITY updates.updateAddonsAutomatically.accesskey "A">
<!-- LOCALIZATION NOTE (updates.resetUpdatesToAutomatic.label, updates.resetUpdatesToManual.label):
Specific addons can have custom update checking behaviors ("Manually",
"Automatically", "Use default global behavior"). These menu items reset the
update checking behavior for all add-ons to the default global behavior
(which itself is either "Automatically" or "Manually", controlled by the
updates.updateAddonsAutomatically.label menu item). -->
<!ENTITY updates.resetUpdatesToAutomatic.label "Reset All Add-ons to Update Automatically">
<!ENTITY updates.resetUpdatesToAutomatic.accesskey "R">
<!ENTITY updates.resetUpdatesToManual.label "Reset All Add-ons to Update Manually">
<!ENTITY updates.resetUpdatesToManual.accesskey "R">
<!ENTITY updates.updating.label "Updating add-ons">
<!ENTITY updates.installed.label "Your add-ons have been updated.">
<!ENTITY updates.downloaded.label "Your add-on updates have been downloaded.">
<!ENTITY updates.restart.label "Restart now to complete installation">
<!ENTITY updates.noneFound.label "No updates found">
<!ENTITY updates.manualUpdatesFound.label "View Available Updates">
<!ENTITY updates.updateSelected.label "Install Updates">
<!ENTITY updates.updateSelected.tooltip "Install available updates in this list">
<!-- addon actions -->
<!ENTITY cmd.showDetails.label "Show More Information">
<!ENTITY cmd.showDetails.accesskey "S">
<!ENTITY cmd.findUpdates.label "Find Updates">
<!ENTITY cmd.findUpdates.accesskey "F">
<!ENTITY cmd.preferencesWin.label "Options">
<!ENTITY cmd.preferencesWin.accesskey "O">
<!ENTITY cmd.preferencesUnix.label "Preferences">
<!ENTITY cmd.preferencesUnix.accesskey "P">
<!ENTITY cmd.about.label "About">
<!ENTITY cmd.about.accesskey "A">
<!ENTITY cmd.enableAddon.label "Enable">
<!ENTITY cmd.enableAddon.accesskey "E">
<!ENTITY cmd.disableAddon.label "Disable">
<!ENTITY cmd.disableAddon.accesskey "D">
<!ENTITY cmd.enableTheme.label "Wear Theme">
<!ENTITY cmd.enableTheme.accesskey "W">
<!ENTITY cmd.disableTheme.label "Stop Wearing Theme">
<!ENTITY cmd.disableTheme.accesskey "W">
<!ENTITY cmd.askToActivate.label "Ask to Activate">
<!ENTITY cmd.askToActivate.tooltip "Ask to use this add-on each time">
<!ENTITY cmd.alwaysActivate.label "Always Activate">
<!ENTITY cmd.alwaysActivate.tooltip "Always use this add-on">
<!ENTITY cmd.neverActivate.label "Never Activate">
<!ENTITY cmd.neverActivate.tooltip "Never use this add-on">
<!ENTITY cmd.stateMenu.tooltip "Change when this add-on runs">
<!ENTITY cmd.installAddon.label "Install">
<!ENTITY cmd.installAddon.accesskey "I">
<!ENTITY cmd.uninstallAddon.label "Remove">
<!ENTITY cmd.uninstallAddon.accesskey "R">
<!ENTITY cmd.debugAddon.label "Debug">
<!ENTITY cmd.showPreferencesWin.label "Options">
<!ENTITY cmd.showPreferencesWin.tooltip "Change this add-on's options">
<!ENTITY cmd.showPreferencesUnix.label "Preferences">
<!ENTITY cmd.showPreferencesUnix.tooltip "Change this add-on's preferences">
<!ENTITY cmd.contribute.label "Contribute">
<!ENTITY cmd.contribute.accesskey "C">
<!ENTITY cmd.contribute.tooltip "Contribute to the development of this add-on">
<!ENTITY cmd.showReleaseNotes.label "Show Release Notes">
<!ENTITY cmd.showReleaseNotes.tooltip "Show the release notes for this update">
<!ENTITY cmd.hideReleaseNotes.label "Hide Release Notes">
<!ENTITY cmd.hideReleaseNotes.tooltip "Hide the release notes for this update">
<!-- discovery view -->
<!-- LOCALIZATION NOTE (discover.title,discover.description,discover.footer):
Displayed in the center of the Get Add-ons view, see bug 601143 for mockups. -->
<!ENTITY discover.title "What are Add-ons?">
<!ENTITY discover.description2 "Add-ons are applications that let you personalize &brandShortName; with
extra functionality or style. Try a time-saving sidebar, a weather notifier, or a themed look to make &brandShortName;
your own.">
<!ENTITY discover.footer "When you're connected to the internet, this pane will feature
some of the best and most popular add-ons for you to try out.">
<!-- detail view -->
<!ENTITY detail.version.label "Version">
<!ENTITY detail.lastupdated.label "Last Updated">
<!ENTITY detail.creator.label "Developer">
<!ENTITY detail.homepage.label "Homepage">
<!ENTITY detail.numberOfDownloads.label "Downloads">
<!ENTITY detail.contributions.description "The developer of this add-on asks that you help support its continued development by making a small contribution.">
<!ENTITY detail.updateType "Automatic Updates">
<!ENTITY detail.updateDefault.label "Default">
<!ENTITY detail.updateDefault.tooltip "Automatically install updates only if that's the default">
<!ENTITY detail.updateAutomatic.label "On">
<!ENTITY detail.updateAutomatic.tooltip "Automatically install updates">
<!ENTITY detail.updateManual.label "Off">
<!ENTITY detail.updateManual.tooltip "Don't automatically install updates">
<!ENTITY detail.home "Homepage">
<!ENTITY detail.repository "Add-on Profile">
<!ENTITY detail.size "Size">
<!ENTITY detail.checkForUpdates.label "Check for Updates">
<!ENTITY detail.checkForUpdates.accesskey "F">
<!ENTITY detail.checkForUpdates.tooltip "Check for updates for this add-on">
<!ENTITY detail.showPreferencesWin.label "Options">
<!ENTITY detail.showPreferencesWin.accesskey "O">
<!ENTITY detail.showPreferencesWin.tooltip "Change this add-on's options">
<!ENTITY detail.showPreferencesUnix.label "Preferences">
<!ENTITY detail.showPreferencesUnix.accesskey "P">
<!ENTITY detail.showPreferencesUnix.tooltip "Change this add-on's preferences">
<!-- ratings -->
<!ENTITY rating2.label "Rating">
<!-- download/install progress -->
<!ENTITY progress.pause.tooltip "Pause">
<!ENTITY progress.cancel.tooltip "Cancel">
<!-- list sorting -->
<!ENTITY sort.name.label "Name">
<!ENTITY sort.name.tooltip "Sort by name">
<!ENTITY sort.dateUpdated.label "Last Updated">
<!ENTITY sort.dateUpdated.tooltip "Sort by date updated">
<!ENTITY sort.relevance.label "Best match">
<!ENTITY sort.relevance.tooltip "Sort by relevance">
<!ENTITY sort.price.label "Price">
<!ENTITY sort.price.tooltip "Sort by price">
<!ENTITY search.filter2.label "Search:">
<!ENTITY search.filter2.installed.label "My Add-ons">
<!ENTITY search.filter2.installed.tooltip "Show installed add-ons">
<!ENTITY search.filter2.available.label "Available Add-ons">
<!ENTITY search.filter2.available.tooltip "Show add-ons available to install">
<!ENTITY addon.homepage "Homepage">
<!ENTITY addon.details.label "More">
<!ENTITY addon.details.tooltip "Show more details about this add-on">
<!ENTITY addon.unknownDate "Unknown">
<!-- LOCALIZATION NOTE (addon.disabled.postfix): This is used in a normal list
to signify that an add-on is disabled, in the form
"<Addon name> <1.0> (disabled)" -->
<!ENTITY addon.disabled.postfix "(disabled)">
<!-- LOCALIZATION NOTE (addon.update.postfix): This is used in the available
updates list to signify that an item is an update, in the form
"<Addon name> <1.1> Update". It is fine to use constructs like brackets if
necessary -->
<!ENTITY addon.update.postfix "Update">
<!ENTITY addon.undoAction.label "Undo">
<!ENTITY addon.undoAction.tooltip "Undo this action">
<!ENTITY addon.undoRemove.label "Undo">
<!ENTITY addon.undoRemove.tooltip "Keep this add-on installed">
<!ENTITY addon.restartNow.label "Restart now">
<!ENTITY addon.install.label "Install">
<!ENTITY addon.install.tooltip "Install this add-on">
<!ENTITY addon.updateNow.label "Update Now">
<!ENTITY addon.updateNow.tooltip "Install the update for this add-on">
<!ENTITY addon.includeUpdate.label "Include in Update">
<!ENTITY addon.updateAvailable.label "An update is available">
<!ENTITY addon.checkingForUpdates.label "Checking for updates…">
<!ENTITY addon.releaseNotes.label "Release Notes:">
<!ENTITY addon.loadingReleaseNotes.label "Loading…">
<!ENTITY addon.errorLoadingReleaseNotes.label "Sorry, but there was an error loading the release notes.">
<!ENTITY addon.nativeAddon "This add-on directly targets Pale Moon">
<!ENTITY addon.compatAddon "This add-on targets Mozilla Firefox and runs in compatibility mode">
<!ENTITY addon.createdBy.label "By ">
<!ENTITY eula.title "End-User License Agreement">
<!ENTITY eula.width "560px">
<!ENTITY eula.height "400px">
<!ENTITY eula.accept "Accept and Install…">
<!ENTITY settings.path.button.label "Browse…">
<!-- LOCALIZATION NOTE (experiment.info.label): The strings related to
experiments are present on the "Experiments" tab of the add-ons manager.
This tab won't be displayed unless an Experiment add-on is installed.
Install https://people.mozilla.org/~gszorc/dummy-experiment-addon.xpi
to cause this tab to appear. -->
<!ENTITY experiment.info.label "What's this? Telemetry may install and run experiments from time to time.">
<!ENTITY experiment.info.learnmore "Learn More">
<!ENTITY experiment.info.learnmore.accesskey "L">
<!ENTITY experiment.info.changetelemetry "Telemetry Settings">
<!ENTITY experiment.info.changetelemetry.accesskey "T">
<!ENTITY setting.learnmore "Learn More…">

View file

@ -0,0 +1,177 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#LOCALIZATION NOTE (aboutWindowTitle) %S is the addon name
aboutWindowTitle=About %S
aboutWindowCloseButton=Close
#LOCALIZATION NOTE (aboutWindowVersionString) %S is the addon version
aboutWindowVersionString=version %S
#LOCALIZATION NOTE (aboutAddon) %S is the addon name
aboutAddon=About %S
#LOCALIZATION NOTE (uninstallNotice) %S is the add-on name
uninstallNotice=%S has been removed.
#LOCALIZATION NOTE (numReviews): Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of reviews
numReviews=#1 review;#1 reviews
#LOCALIZATION NOTE (dateUpdated) %S is the date the addon was last updated
dateUpdated=Updated %S
#LOCALIZATION NOTE (notification.incompatible) %1$S is the add-on name, %2$S is brand name, %3$S is application version
notification.incompatible=%1$S is incompatible with %2$S %3$S.
notification.jetsdk=This is a Jetpack/SDK extension which are not supported in %1$S %2$S.
#LOCALIZATION NOTE (notification.blocked) %1$S is the add-on name
notification.blocked=%1$S has been disabled due to security or stability issues.
notification.blocked.link=More Information
#LOCALIZATION NOTE (notification.softblocked) %1$S is the add-on name
notification.softblocked=%1$S is known to cause security or stability issues.
notification.softblocked.link=More Information
#LOCALIZATION NOTE (notification.outdated) %1$S is the add-on name
notification.outdated=An important update is available for %1$S.
notification.outdated.link=Update Now
#LOCALIZATION NOTE (notification.vulnerableUpdatable) %1$S is the add-on name
notification.vulnerableUpdatable=%1$S is known to be vulnerable and should be updated.
notification.vulnerableUpdatable.link=Update Now
#LOCALIZATION NOTE (notification.vulnerableNoUpdate) %1$S is the add-on name
notification.vulnerableNoUpdate=%1$S is known to be vulnerable. Use with caution.
notification.vulnerableNoUpdate.link=More Information
#LOCALIZATION NOTE (notification.enable) %1$S is the add-on name, %2$S is brand name
notification.enable=%1$S will be enabled after you restart %2$S.
#LOCALIZATION NOTE (notification.disable) %1$S is the add-on name, %2$S is brand name
notification.disable=%1$S will be disabled after you restart %2$S.
#LOCALIZATION NOTE (notification.install) %1$S is the add-on name, %2$S is brand name
notification.install=%1$S will be installed after you restart %2$S.
#LOCALIZATION NOTE (notification.uninstall) %1$S is the add-on name, %2$S is brand name
notification.uninstall=%1$S will be uninstalled after you restart %2$S.
#LOCALIZATION NOTE (notification.upgrade) %1$S is the add-on name, %2$S is brand name
notification.upgrade=%1$S will be updated after you restart %2$S.
#LOCALIZATION NOTE (notification.downloadError) %1$S is the add-on name.
notification.downloadError=There was an error downloading %1$S.
notification.downloadError.retry=Try again
notification.downloadError.retry.tooltip=Try downloading this add-on again
#LOCALIZATION NOTE (notification.installError) %1$S is the add-on name.
notification.installError=There was an error installing %1$S.
notification.installError.retry=Try again
notification.installError.retry.tooltip=Try downloading and installing this add-on again
#LOCALIZATION NOTE (notification.gmpPending) %1$S is the add-on name.
notification.gmpPending=%1$S will be installed shortly.
#LOCALIZATION NOTE (contributionAmount2) %S is the currency amount recommended for contributions
contributionAmount2=Suggested Contribution: %S
installDownloading=Downloading
installDownloaded=Downloaded
installDownloadFailed=Error downloading
installVerifying=Verifying
installInstalling=Installing
installEnablePending=Restart to enable
installDisablePending=Restart to disable
installFailed=Error installing
installCancelled=Install cancelled
#LOCALIZATION NOTE (details.notification.incompatible) %1$S is the add-on name, %2$S is brand name, %3$S is application version
details.notification.incompatible=%1$S is incompatible with %2$S %3$S.
#LOCALIZATION NOTE (details.notification.blocked) %1$S is the add-on name
details.notification.blocked=%1$S has been disabled due to security or stability issues.
details.notification.blocked.link=More Information
#LOCALIZATION NOTE (details.notification.softblocked) %1$S is the add-on name
details.notification.softblocked=%1$S is known to cause security or stability issues.
details.notification.softblocked.link=More Information
#LOCALIZATION NOTE (details.notification.outdated) %1$S is the add-on name
details.notification.outdated=An important update is available for %1$S.
details.notification.outdated.link=Update Now
#LOCALIZATION NOTE (details.notification.vulnerableUpdatable) %1$S is the add-on name
details.notification.vulnerableUpdatable=%1$S is known to be vulnerable and should be updated.
details.notification.vulnerableUpdatable.link=Update Now
#LOCALIZATION NOTE (details.notification.vulnerableNoUpdate) %1$S is the add-on name
details.notification.vulnerableNoUpdate=%1$S is known to be vulnerable. Use with caution.
details.notification.vulnerableNoUpdate.link=More Information
#LOCALIZATION NOTE (details.notification.enable) %1$S is the add-on name, %2$S is brand name
details.notification.enable=%1$S will be enabled after you restart %2$S.
#LOCALIZATION NOTE (details.notification.disable) %1$S is the add-on name, %2$S is brand name
details.notification.disable=%1$S will be disabled after you restart %2$S.
#LOCALIZATION NOTE (details.notification.install) %1$S is the add-on name, %2$S is brand name
details.notification.install=%1$S will be installed after you restart %2$S.
#LOCALIZATION NOTE (details.notification.uninstall) %1$S is the add-on name, %2$S is brand name
details.notification.uninstall=%1$S will be uninstalled after you restart %2$S.
#LOCALIZATION NOTE (details.notification.upgrade) %1$S is the add-on name, %2$S is brand name
details.notification.upgrade=%1$S will be updated after you restart %2$S.
#LOCALIZATION NOTE (details.notification.gmpPending) %1$S is the add-on name
details.notification.gmpPending=%1$S will be installed shortly.
# LOCALIZATION NOTE (details.experiment.time.daysRemaining):
# Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of days from now that the experiment will remain active (detail view).
details.experiment.time.daysRemaining=#1 day remaining;#1 days remaining
#LOCALIZATION NOTE (details.experiment.time.endsToday) The experiment will end in less than a day (detail view).
details.experiment.time.endsToday=Less than a day remaining
# LOCALIZATION NOTE (details.experiment.time.daysPassed):
# Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of days since the experiment ran (detail view).
details.experiment.time.daysPassed=#1 day ago;#1 days ago
#LOCALIZATION NOTE (details.experiment.time.endedToday) The experiment ended less than a day ago (detail view).
details.experiment.time.endedToday=Less than a day ago
#LOCALIZATION NOTE (details.experiment.state.active) This experiment is active (detail view).
details.experiment.state.active=Active
#LOCALIZATION NOTE (details.experiment.state.complete) This experiment is complete (it was previously active) (detail view).
details.experiment.state.complete=Complete
# LOCALIZATION NOTE (experiment.time.daysRemaining):
# Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of days from now that the experiment will remain active (list view item).
experiment.time.daysRemaining=#1 day remaining;#1 days remaining
#LOCALIZATION NOTE (experiment.time.endsToday) The experiment will end in less than a day (list view item).
experiment.time.endsToday=Less than a day remaining
# LOCALIZATION NOTE (experiment.time.daysPassed):
# Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of days since the experiment ran (list view item).
experiment.time.daysPassed=#1 day ago;#1 days ago
#LOCALIZATION NOTE (experiment.time.endedToday) The experiment ended less than a day ago (list view item).
experiment.time.endedToday=Less than a day ago
#LOCALIZATION NOTE (experiment.state.active) This experiment is active (list view item).
experiment.state.active=Active
#LOCALIZATION NOTE (experiment.state.complete) This experiment is complete (it was previously active) (list view item).
experiment.state.complete=Complete
installFromFile.dialogTitle=Select add-on to install
installFromFile.filterName=Add-ons
uninstallAddonTooltip=Uninstall this add-on
uninstallAddonRestartRequiredTooltip=Uninstall this add-on (restart required)
enableAddonTooltip=Enable this add-on
enableAddonRestartRequiredTooltip=Enable this add-on (restart required)
disableAddonTooltip=Disable this add-on
disableAddonRestartRequiredTooltip=Disable this add-on (restart required)
#LOCALIZATION NOTE (showAllSearchResults): Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the total number of search results
showAllSearchResults=See one result;See all #1 results
#LOCALIZATION NOTE (addon.purchase.label) displayed on a button in the list
# view, %S is the price of the add-on including currency symbol
addon.purchase.label=Purchase for %S…
addon.purchase.tooltip=Visit the add-ons gallery to purchase this add-on
#LOCALIZATION NOTE (cmd.purchaseAddon.label) displayed on a button in the detail
# view, %S is the price of the add-on including currency symbol
cmd.purchaseAddon.label=Purchase for %S…
cmd.purchaseAddon.accesskey=u
#LOCALIZATION NOTE (eulaHeader) %S is name of the add-on asking the user to agree to the EULA
eulaHeader=%S requires that you accept the following End User License Agreement before installation can proceed:
type.extension.name=Extensions
type.theme.name=Appearance
type.locale.name=Languages
type.plugin.name=Plugins
type.dictionary.name=Dictionaries
type.service.name=Services
type.experiment.name=Experiments

View file

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

View file

@ -0,0 +1,10 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#LOCALIZATION NOTE (name) %1$S is the add-on name, %2$S is the add-on version
name=%1$S %2$S
#LOCALIZATION NOTE (author) %S is the author of the add-on
author=By %S
#LOCALIZATION NOTE (location) %S is the path the add-on is installed in
location=Location: %S

View file

@ -0,0 +1,49 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY upgrade.style "width: 93ch; height: 448px;">
<!ENTITY checking.heading "Checking Your Add-ons">
<!ENTITY checking.progress.label "Checking your add-ons for compatibility with this version of &brandShortName;.">
<!ENTITY select.heading "Select Your Add-ons">
<!-- LOCALIZATION NOTE (select.description): The term used for "third parties"
here should match the string source.other in selectAddons.properties. -->
<!ENTITY select.description "Make &brandShortName; even faster by disabling add-ons you no longer use. Add-ons already installed by third parties will be disabled automatically unless you select them below.">
<!ENTITY select.keep "Keep">
<!-- LOCALIZATION NOTE (select.keep.style): Should be a width wide enough for
the string in select.keep above. -->
<!ENTITY select.keep.style "width: 6ch;">
<!ENTITY select.action "Action">
<!-- LOCALIZATION NOTE (select.action.style): Should be a width wide enough for
the action strings in selectAddons.properties or brandShortName. -->
<!ENTITY select.action.style "width: 35ch;">
<!ENTITY select.source "Installed By">
<!ENTITY select.name "Name">
<!-- LOCALIZATION NOTE (select.name.style): Should be a width small enough so
the source column still has enough room for the source strings in
selectAddons.properties. -->
<!ENTITY select.name.style "width: 33ch;">
<!ENTITY confirm.heading "Select Your Add-ons">
<!-- LOCALIZATION NOTE (confirm.description): The term used for "third parties"
here should match the string source.other in selectAddons.properties. -->
<!ENTITY confirm.description "Make &brandShortName; even faster by disabling add-ons you no longer use. Add-ons already installed by third parties will be disabled automatically unless you select them below.">
<!ENTITY action.disable.heading "The following add-ons will be disabled:">
<!ENTITY action.incompatible.heading "The following add-ons are disabled, but will be enabled as soon as they are compatible:">
<!ENTITY action.update.heading "The following add-ons will be updated:">
<!ENTITY action.enable.heading "The following add-ons will be enabled:">
<!ENTITY update.heading "Updating Your Add-ons">
<!ENTITY update.progress.label "Downloading and installing updates for your selected add-ons.">
<!ENTITY errors.heading "&brandShortName; could not update some of your add-ons.">
<!ENTITY errors.description "Installing updates for some of your add-ons failed. &brandShortName; will automatically try to update them again later.">
<!ENTITY footer.label "You can always change your add-ons by going to the Add-ons Manager.">
<!ENTITY cancel.label "Cancel">
<!ENTITY back.label "Back">
<!ENTITY next.label "Next">
<!ENTITY done.label "Done">

View file

@ -0,0 +1,21 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#LOCALIZATION NOTE (source.profile) add-ons installed by the user, this may be
# translated as "You" or "User" depending on the locale
source.profile=You
#LOCALIZATION NOTE (source.bundled) add-ons shipped with the application, and thus
# treated as installed by the user. This may be
# translated as "You" or "User" depending on the locale
source.bundled=You (Bundled)
#LOCALIZATION NOTE (source.other) add-ons installed by other applications
# installed on the computer
source.other=Third Party
action.enabled=Will be enabled
action.disabled=Will be disabled
action.autoupdate=Will be updated to be compatible
action.incompatible=Will be enabled when compatible
action.neededupdate=Update to make compatible
action.unneededupdate=Optional update

View file

@ -0,0 +1,65 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY updateWizard.title "&brandShortName; Update">
<!ENTITY offline.title "&brandShortName; is working offline">
<!ENTITY offline.description "&brandShortName; needs to go online in order to see if updates
are available for your add-ons to make them compatible with this
version.">
<!ENTITY offline.toggleOffline.label "Go online now.">
<!ENTITY offline.toggleOffline.accesskey "G">
<!ENTITY mismatch.win.title "Incompatible Add-ons">
<!ENTITY mismatch.top.label "The following add-ons are not compatible with this version of
&brandShortName; and have been disabled:">
<!ENTITY mismatch.bottom.label "&brandShortName; can check if there are compatible versions
of these add-ons available.">
<!ENTITY checking.wizard.title "Checking for Compatible Add-ons">
<!ENTITY checking.top.label "Checking your incompatible add-ons for updates…">
<!ENTITY checking.status "This may take a few minutes…">
<!ENTITY found.wizard.title "Found Compatible Add-ons">
<!ENTITY found.top.label "Select the add-ons you would like to install:">
<!ENTITY found.disabledXPinstall.label "These updates can't be installed because software installation is currently
disabled. You can change this setting below.">
<!ENTITY found.enableXPInstall.label "Allow websites to install software">
<!ENTITY found.enableXPInstall.accesskey "A">
<!ENTITY installing.wizard.title "Installing Compatible Add-ons">
<!ENTITY installing.top.label "Downloading and installing updates to your add-ons…">
<!ENTITY noupdates.wizard.title "No Compatible Add-ons Found">
<!ENTITY noupdates.intro.desc "&brandShortName; was unable to find updates to your
incompatible add-ons.">
<!ENTITY noupdates.error.desc "Some problems were encountered when trying to find updates.">
<!ENTITY noupdates.checkEnabled.desc "&brandShortName; will check periodically and inform you
when compatible updates for these add-ons are found.">
<!ENTITY finished.wizard.title "Compatible Add-ons Installed">
<!ENTITY finished.top.label "&brandShortName; has installed the updates to your add-ons.">
<!ENTITY finished.checkDisabled.desc "&brandShortName; can check periodically and inform you
when updates for add-ons are found.">
<!ENTITY finished.checkEnabled.desc "&brandShortName; will check periodically and inform you
when updates for add-ons are found.">
<!ENTITY adminDisabled.wizard.title "Unable to Check for Updates">
<!ENTITY adminDisabled.warning.label "It is not possible to check for updates to incompatible add-ons
because software installation for &brandShortName; has been disabled.
Please contact your System Administrator for assistance.">
<!ENTITY versioninfo.wizard.title "Checking Compatibility of Add-ons">
<!ENTITY versioninfo.top.label "Checking your add-ons for compatibility with this
version of &brandShortName;.">
<!ENTITY versioninfo.waiting "This may take a few minutes…">
<!ENTITY installerrors.wizard.title "Problems Installing Updates">
<!ENTITY installerrors.intro.label "&brandShortName; encountered problems when updating
some of your add-ons.">
<!-- general strings used by several of the finish pages -->
<!ENTITY clickFinish.label "Click Finish to continue starting &brandShortName;.">
<!ENTITY clickFinish.labelMac "Click Done to continue starting &brandShortName;.">
<!ENTITY enableChecking.label "Allow &brandShortName; to check for updates.">

View file

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

View file

@ -0,0 +1,13 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- extracted from institems.xul -->
<!ENTITY dialog.title "Software Installation">
<!ENTITY dialog.style "width: 45em">
<!ENTITY warningPrimary.label "Install add-ons only from authors whom you trust.">
<!ENTITY warningSecondary.label "Malicious software can damage your computer or violate your privacy.">
<!ENTITY from.label "from:">

View file

@ -0,0 +1,16 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
unverified=(Author not verified)
signed=(%S)
itemWarnIntroMultiple=You have asked to install the following %S items:
itemWarnIntroSingle=You have asked to install the following item:
installButtonDisabledLabel=Install (%S)
installButtonLabel=Install Now
installComplete=Software Installation is complete. You will have to restart %S for changes to take effect.
installCompleteTitle=Installation Complete
error-203=Error Installing Item

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,223 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AddonPathService.h"
#include "amIAddonManager.h"
#include "nsIURI.h"
#include "nsXULAppAPI.h"
#include "jsapi.h"
#include "nsServiceManagerUtils.h"
#include "nsLiteralString.h"
#include "nsThreadUtils.h"
#include "nsIIOService.h"
#include "nsNetUtil.h"
#include "nsIResProtocolHandler.h"
#include "nsIChromeRegistry.h"
#include "nsIJARURI.h"
#include "nsJSUtils.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/AddonPathService.h"
#include "mozilla/Omnijar.h"
#include <algorithm>
namespace mozilla {
struct PathEntryComparator
{
typedef AddonPathService::PathEntry PathEntry;
bool Equals(const PathEntry& entry1, const PathEntry& entry2) const
{
return entry1.mPath == entry2.mPath;
}
bool LessThan(const PathEntry& entry1, const PathEntry& entry2) const
{
return entry1.mPath < entry2.mPath;
}
};
AddonPathService::AddonPathService()
{
}
AddonPathService::~AddonPathService()
{
sInstance = nullptr;
}
NS_IMPL_ISUPPORTS(AddonPathService, amIAddonPathService)
AddonPathService *AddonPathService::sInstance;
/* static */ AddonPathService*
AddonPathService::GetInstance()
{
if (!sInstance) {
sInstance = new AddonPathService();
}
NS_ADDREF(sInstance);
return sInstance;
}
JSAddonId*
AddonPathService::Find(const nsAString& path)
{
// Use binary search to find the nearest entry that is <= |path|.
PathEntryComparator comparator;
unsigned index = mPaths.IndexOfFirstElementGt(PathEntry(path, nullptr), comparator);
if (index == 0) {
return nullptr;
}
const PathEntry& entry = mPaths[index - 1];
// Return the entry's addon if its path is a prefix of |path|.
if (StringBeginsWith(path, entry.mPath)) {
return entry.mAddonId;
}
return nullptr;
}
NS_IMETHODIMP
AddonPathService::FindAddonId(const nsAString& path, nsAString& addonIdString)
{
if (JSAddonId* id = Find(path)) {
JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id));
AssignJSFlatString(addonIdString, flat);
}
return NS_OK;
}
/* static */ JSAddonId*
AddonPathService::FindAddonId(const nsAString& path)
{
// If no service has been created, then we're not going to find anything.
if (!sInstance) {
return nullptr;
}
return sInstance->Find(path);
}
NS_IMETHODIMP
AddonPathService::InsertPath(const nsAString& path, const nsAString& addonIdString)
{
AutoSafeJSContext cx;
JS::RootedString str(cx, JS_NewUCStringCopyN(cx,
addonIdString.BeginReading(),
addonIdString.Length()));
JSAddonId* addonId = JS::NewAddonId(cx, str);
// Add the new path in sorted order.
PathEntryComparator comparator;
mPaths.InsertElementSorted(PathEntry(path, addonId), comparator);
return NS_OK;
}
static nsresult
ResolveURI(nsIURI* aURI, nsAString& out)
{
bool equals;
nsresult rv;
nsCOMPtr<nsIURI> uri;
nsAutoCString spec;
// Resolve resource:// URIs. At the end of this if/else block, we
// have both spec and uri variables identifying the same URI.
if (NS_SUCCEEDED(aURI->SchemeIs("resource", &equals)) && equals) {
nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
if (NS_WARN_IF(NS_FAILED(rv)))
return rv;
nsCOMPtr<nsIProtocolHandler> ph;
rv = ioService->GetProtocolHandler("resource", getter_AddRefs(ph));
if (NS_WARN_IF(NS_FAILED(rv)))
return rv;
nsCOMPtr<nsIResProtocolHandler> irph(do_QueryInterface(ph, &rv));
if (NS_WARN_IF(NS_FAILED(rv)))
return rv;
rv = irph->ResolveURI(aURI, spec);
if (NS_WARN_IF(NS_FAILED(rv)))
return rv;
rv = ioService->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
if (NS_WARN_IF(NS_FAILED(rv)))
return rv;
} else if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &equals)) && equals) {
nsCOMPtr<nsIChromeRegistry> chromeReg =
mozilla::services::GetChromeRegistryService();
if (NS_WARN_IF(!chromeReg))
return NS_ERROR_UNEXPECTED;
rv = chromeReg->ConvertChromeURL(aURI, getter_AddRefs(uri));
if (NS_WARN_IF(NS_FAILED(rv)))
return rv;
} else {
uri = aURI;
}
if (NS_SUCCEEDED(uri->SchemeIs("jar", &equals)) && equals) {
nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
if (NS_WARN_IF(NS_FAILED(rv)))
return rv;
nsCOMPtr<nsIURI> jarFileURI;
rv = jarURI->GetJARFile(getter_AddRefs(jarFileURI));
if (NS_WARN_IF(NS_FAILED(rv)))
return rv;
return ResolveURI(jarFileURI, out);
}
if (NS_SUCCEEDED(uri->SchemeIs("file", &equals)) && equals) {
nsCOMPtr<nsIFileURL> baseFileURL = do_QueryInterface(uri, &rv);
if (NS_WARN_IF(NS_FAILED(rv)))
return rv;
nsCOMPtr<nsIFile> file;
rv = baseFileURL->GetFile(getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv)))
return rv;
return file->GetPath(out);
}
return NS_ERROR_FAILURE;
}
JSAddonId*
MapURIToAddonID(nsIURI* aURI)
{
if (!NS_IsMainThread() || XRE_GetProcessType() != GoannaProcessType_Default) {
return nullptr;
}
nsAutoString filePath;
nsresult rv = ResolveURI(aURI, filePath);
if (NS_FAILED(rv))
return nullptr;
nsCOMPtr<nsIFile> greJar = Omnijar::GetPath(Omnijar::GRE);
nsCOMPtr<nsIFile> appJar = Omnijar::GetPath(Omnijar::APP);
if (greJar && appJar) {
nsAutoString greJarString, appJarString;
if (NS_FAILED(greJar->GetPath(greJarString)) || NS_FAILED(appJar->GetPath(appJarString)))
return nullptr;
// If |aURI| is part of either Omnijar, then it can't be part of an
// add-on. This catches pretty much all URLs for Firefox content.
if (filePath.Equals(greJarString) || filePath.Equals(appJarString))
return nullptr;
}
// If it's not part of Firefox, we resort to binary searching through the
// add-on paths.
return AddonPathService::FindAddonId(filePath);
}
}

View file

@ -0,0 +1,55 @@
//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef AddonPathService_h
#define AddonPathService_h
#include "amIAddonPathService.h"
#include "nsString.h"
#include "nsTArray.h"
class nsIURI;
class JSAddonId;
namespace mozilla {
JSAddonId*
MapURIToAddonID(nsIURI* aURI);
class AddonPathService final : public amIAddonPathService
{
public:
AddonPathService();
static AddonPathService* GetInstance();
JSAddonId* Find(const nsAString& path);
static JSAddonId* FindAddonId(const nsAString& path);
NS_DECL_ISUPPORTS
NS_DECL_AMIADDONPATHSERVICE
struct PathEntry
{
nsString mPath;
JSAddonId* mAddonId;
PathEntry(const nsAString& aPath, JSAddonId* aAddonId)
: mPath(aPath), mAddonId(aAddonId)
{}
};
private:
virtual ~AddonPathService();
// Paths are stored sorted in order of their mPath.
nsTArray<PathEntry> mPaths;
static AddonPathService* sInstance;
};
} // namespace mozilla
#endif

View file

@ -0,0 +1,159 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["ChromeManifestParser"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
const MSG_JAR_FLUSH = "AddonJarFlush";
/**
* Sends local and remote notifications to flush a JAR file cache entry
*
* @param aJarFile
* The ZIP/XPI/JAR file as a nsIFile
*/
function flushJarCache(aJarFile) {
Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null);
Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageBroadcaster)
.broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path);
}
/**
* Parses chrome manifest files.
*/
this.ChromeManifestParser = {
/**
* Reads and parses a chrome manifest file located at a specified URI, and all
* secondary manifests it references.
*
* @param aURI
* A nsIURI pointing to a chrome manifest.
* Typically a file: or jar: URI.
* @return Array of objects describing each manifest instruction, in the form:
* { type: instruction-type, baseURI: string-uri, args: [arguments] }
**/
parseSync: function CMP_parseSync(aURI) {
function parseLine(aLine) {
let line = aLine.trim();
if (line.length == 0 || line.charAt(0) == '#')
return;
let tokens = line.split(/\s+/);
let type = tokens.shift();
if (type == "manifest") {
let uri = NetUtil.newURI(tokens.shift(), null, aURI);
data = data.concat(this.parseSync(uri));
} else {
data.push({type: type, baseURI: baseURI, args: tokens});
}
}
let contents = "";
try {
if (aURI.scheme == "jar")
contents = this._readFromJar(aURI);
else
contents = this._readFromFile(aURI);
} catch (e) {
// Silently fail.
}
if (!contents)
return [];
let baseURI = NetUtil.newURI(".", null, aURI).spec;
let data = [];
let lines = contents.split("\n");
lines.forEach(parseLine.bind(this));
return data;
},
_readFromJar: function CMP_readFromJar(aURI) {
let data = "";
let entries = [];
let readers = [];
try {
// Deconstrict URI, which can be nested jar: URIs.
let uri = aURI.clone();
while (uri instanceof Ci.nsIJARURI) {
entries.push(uri.JAREntry);
uri = uri.JARFile;
}
// Open the base jar.
let reader = Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(Ci.nsIZipReader);
reader.open(uri.QueryInterface(Ci.nsIFileURL).file);
readers.push(reader);
// Open the nested jars.
for (let i = entries.length - 1; i > 0; i--) {
let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(Ci.nsIZipReader);
innerReader.openInner(reader, entries[i]);
readers.push(innerReader);
reader = innerReader;
}
// First entry is the actual file we want to read.
let zis = reader.getInputStream(entries[0]);
data = NetUtil.readInputStreamToString(zis, zis.available());
}
finally {
// Close readers in reverse order.
for (let i = readers.length - 1; i >= 0; i--) {
readers[i].close();
flushJarCache(readers[i].file);
}
}
return data;
},
_readFromFile: function CMP_readFromFile(aURI) {
let file = aURI.QueryInterface(Ci.nsIFileURL).file;
if (!file.exists() || !file.isFile())
return "";
let data = "";
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
try {
fis.init(file, -1, -1, false);
data = NetUtil.readInputStreamToString(fis, fis.available());
} finally {
fis.close();
}
return data;
},
/**
* Detects if there were any instructions of a specified type in a given
* chrome manifest.
*
* @param aManifest
* Manifest data, as returned by ChromeManifestParser.parseSync().
* @param aType
* Instruction type to filter by.
* @return True if any matching instructions were found in the manifest.
*/
hasType: function CMP_hasType(aManifest, aType) {
return aManifest.some(function hasType_matchEntryType(aEntry) {
return aEntry.type == aType;
});
}
};

View file

@ -0,0 +1,274 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
// Make it possible to mock out timers for testing
let MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.EXPORTED_SYMBOLS = ["DeferredSave"];
// If delay parameter is not provided, default is 50 milliseconds.
const DEFAULT_SAVE_DELAY_MS = 50;
Cu.import("resource://gre/modules/Log.jsm");
//Configure a logger at the parent 'DeferredSave' level to format
//messages for all the modules under DeferredSave.*
const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave";
let parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID);
parentLogger.level = Log.Level.Warn;
let formatter = new Log.BasicFormatter();
//Set parent logger (and its children) to append to
//the Javascript section of the Browser Console
parentLogger.addAppender(new Log.ConsoleAppender(formatter));
//Set parent logger (and its children) to
//also append to standard out
parentLogger.addAppender(new Log.DumpAppender(formatter));
//Provide the ability to enable/disable logging
//messages at runtime.
//If the "extensions.logging.enabled" preference is
//missing or 'false', messages at the WARNING and higher
//severity should be logged to the JS console and standard error.
//If "extensions.logging.enabled" is set to 'true', messages
//at DEBUG and higher should go to JS console and standard error.
Cu.import("resource://gre/modules/Services.jsm");
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
/**
* Preference listener which listens for a change in the
* "extensions.logging.enabled" preference and changes the logging level of the
* parent 'addons' level logger accordingly.
*/
var PrefObserver = {
init: function PrefObserver_init() {
Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
Services.obs.addObserver(this, "xpcom-shutdown", false);
this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
},
observe: function PrefObserver_observe(aSubject, aTopic, aData) {
if (aTopic == "xpcom-shutdown") {
Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
Services.obs.removeObserver(this, "xpcom-shutdown");
}
else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
let debugLogEnabled = false;
try {
debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED);
}
catch (e) {
}
if (debugLogEnabled) {
parentLogger.level = Log.Level.Debug;
}
else {
parentLogger.level = Log.Level.Warn;
}
}
}
};
PrefObserver.init();
/**
* A module to manage deferred, asynchronous writing of data files
* to disk. Writing is deferred by waiting for a specified delay after
* a request to save the data, before beginning to write. If more than
* one save request is received during the delay, all requests are
* fulfilled by a single write.
*
* @constructor
* @param aPath
* String representing the full path of the file where the data
* is to be written.
* @param aDataProvider
* Callback function that takes no argument and returns the data to
* be written. If aDataProvider returns an ArrayBufferView, the
* bytes it contains are written to the file as is.
* If aDataProvider returns a String the data are UTF-8 encoded
* and then written to the file.
* @param [optional] aDelay
* The delay in milliseconds between the first saveChanges() call
* that marks the data as needing to be saved, and when the DeferredSave
* begins writing the data to disk. Default 50 milliseconds.
*/
this.DeferredSave = function (aPath, aDataProvider, aDelay) {
// Create a new logger (child of 'DeferredSave' logger)
// for use by this particular instance of DeferredSave object
let leafName = OS.Path.basename(aPath);
let logger_id = DEFERREDSAVE_PARENT_LOGGER_ID + "." + leafName;
this.logger = Log.repository.getLogger(logger_id);
// @type {Deferred|null}, null when no data needs to be written
// @resolves with the result of OS.File.writeAtomic when all writes complete
// @rejects with the error from OS.File.writeAtomic if the write fails,
// or with the error from aDataProvider() if that throws.
this._pending = null;
// @type {Promise}, completes when the in-progress write (if any) completes,
// kept as a resolved promise at other times to simplify logic.
// Because _deferredSave() always uses _writing.then() to execute
// its next action, we don't need a special case for whether a write
// is in progress - if the previous write is complete (and the _writing
// promise is already resolved/rejected), _writing.then() starts
// the next action immediately.
//
// @resolves with the result of OS.File.writeAtomic
// @rejects with the error from OS.File.writeAtomic
this._writing = Promise.resolve(0);
// Are we currently waiting for a write to complete
this.writeInProgress = false;
this._path = aPath;
this._dataProvider = aDataProvider;
this._timer = null;
// Some counters for telemetry
// The total number of times the file was written
this.totalSaves = 0;
// The number of times the data became dirty while
// another save was in progress
this.overlappedSaves = 0;
// Error returned by the most recent write (if any)
this._lastError = null;
if (aDelay && (aDelay > 0))
this._delay = aDelay;
else
this._delay = DEFAULT_SAVE_DELAY_MS;
}
this.DeferredSave.prototype = {
get dirty() {
return this._pending || this.writeInProgress;
},
get lastError() {
return this._lastError;
},
// Start the pending timer if data is dirty
_startTimer: function() {
if (!this._pending) {
return;
}
this.logger.debug("Starting timer");
if (!this._timer)
this._timer = MakeTimer();
this._timer.initWithCallback(() => this._deferredSave(),
this._delay, Ci.nsITimer.TYPE_ONE_SHOT);
},
/**
* Mark the current stored data dirty, and schedule a flush to disk
* @return A Promise<integer> that will be resolved after the data is written to disk;
* the promise is resolved with the number of bytes written.
*/
saveChanges: function() {
this.logger.debug("Save changes");
if (!this._pending) {
if (this.writeInProgress) {
this.logger.debug("Data changed while write in progress");
this.overlappedSaves++;
}
this._pending = Promise.defer();
// Wait until the most recent write completes or fails (if it hasn't already)
// and then restart our timer
this._writing.then(count => this._startTimer(), error => this._startTimer());
}
return this._pending.promise;
},
_deferredSave: function() {
let pending = this._pending;
this._pending = null;
let writing = this._writing;
this._writing = pending.promise;
// In either the success or the exception handling case, we don't need to handle
// the error from _writing here; it's already being handled in another then()
let toSave = null;
try {
toSave = this._dataProvider();
}
catch(e) {
this.logger.error("Deferred save dataProvider failed", e);
writing.then(null, error => {})
.then(count => {
pending.reject(e);
});
return;
}
writing.then(null, error => {return 0;})
.then(count => {
this.logger.debug("Starting write");
this.totalSaves++;
this.writeInProgress = true;
OS.File.writeAtomic(this._path, toSave, {tmpPath: this._path + ".tmp"})
.then(
result => {
this._lastError = null;
this.writeInProgress = false;
this.logger.debug("Write succeeded");
pending.resolve(result);
},
error => {
this._lastError = error;
this.writeInProgress = false;
this.logger.warn("Write failed", error);
pending.reject(error);
});
});
},
/**
* Immediately save the dirty data to disk, skipping
* the delay of normal operation. Note that the write
* still happens asynchronously in the worker
* thread from OS.File.
*
* There are four possible situations:
* 1) Nothing to flush
* 2) Data is not currently being written, in-memory copy is dirty
* 3) Data is currently being written, in-memory copy is clean
* 4) Data is being written and in-memory copy is dirty
*
* @return Promise<integer> that will resolve when all in-memory data
* has finished being flushed, returning the number of bytes
* written. If all in-memory data is clean, completes with the
* result of the most recent write.
*/
flush: function() {
// If we have pending changes, cancel our timer and set up the write
// immediately (_deferredSave queues the write for after the most
// recent write completes, if it hasn't already)
if (this._pending) {
this.logger.debug("Flush called while data is dirty");
if (this._timer) {
this._timer.cancel();
this._timer = null;
}
this._deferredSave();
}
return this._writing;
}
};

View file

@ -0,0 +1,804 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["LightweightThemeManager"];
const Cc = Components.classes;
const Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/AddonManager.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
const ID_SUFFIX = "@personas.mozilla.org";
const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect";
const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
const PREF_EM_DSS_ENABLED = "extensions.dss.enabled";
const ADDON_TYPE = "theme";
const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME = "type.%ID%.name";
const DEFAULT_MAX_USED_THEMES_COUNT = 30;
const MAX_PREVIEW_SECONDS = 30;
const MANDATORY = ["id", "name", "headerURL"];
const OPTIONAL = ["footerURL", "textcolor", "accentcolor", "iconURL",
"previewURL", "author", "description", "homepageURL",
"updateURL", "version"];
const PERSIST_ENABLED = true;
const PERSIST_BYPASS_CACHE = false;
const PERSIST_FILES = {
headerURL: "lightweighttheme-header",
footerURL: "lightweighttheme-footer"
};
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer",
"resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");
this.__defineGetter__("_prefs", function prefsGetter() {
delete this._prefs;
return this._prefs = Services.prefs.getBranch("lightweightThemes.");
});
this.__defineGetter__("_maxUsedThemes", function maxUsedThemesGetter() {
delete this._maxUsedThemes;
try {
this._maxUsedThemes = _prefs.getIntPref("maxUsedThemes");
}
catch (e) {
this._maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT;
}
return this._maxUsedThemes;
});
this.__defineSetter__("_maxUsedThemes", function maxUsedThemesSetter(aVal) {
delete this._maxUsedThemes;
return this._maxUsedThemes = aVal;
});
// Holds the ID of the theme being enabled or disabled while sending out the
// events so cached AddonWrapper instances can return correct values for
// permissions and pendingOperations
var _themeIDBeingEnabled = null;
var _themeIDBeingDisabled = null;
this.LightweightThemeManager = {
get name() "LightweightThemeManager",
get usedThemes () {
try {
return JSON.parse(_prefs.getComplexValue("usedThemes",
Ci.nsISupportsString).data);
} catch (e) {
return [];
}
},
get currentTheme () {
try {
if (_prefs.getBoolPref("isThemeSelected"))
var data = this.usedThemes[0];
} catch (e) {}
return data || null;
},
get currentThemeForDisplay () {
var data = this.currentTheme;
if (data && PERSIST_ENABLED) {
for (let key in PERSIST_FILES) {
try {
if (data[key] && _prefs.getBoolPref("persisted." + key))
data[key] = _getLocalImageURI(PERSIST_FILES[key]).spec
+ "?" + data.id + ";" + _version(data);
} catch (e) {}
}
}
return data;
},
set currentTheme (aData) {
return _setCurrentTheme(aData, false);
},
setLocalTheme: function LightweightThemeManager_setLocalTheme(aData) {
_setCurrentTheme(aData, true);
},
getUsedTheme: function LightweightThemeManager_getUsedTheme(aId) {
var usedThemes = this.usedThemes;
for (let usedTheme of usedThemes) {
if (usedTheme.id == aId)
return usedTheme;
}
return null;
},
forgetUsedTheme: function LightweightThemeManager_forgetUsedTheme(aId) {
let theme = this.getUsedTheme(aId);
if (!theme)
return;
let wrapper = new AddonWrapper(theme);
AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
var currentTheme = this.currentTheme;
if (currentTheme && currentTheme.id == aId) {
this.themeChanged(null);
AddonManagerPrivate.notifyAddonChanged(null, ADDON_TYPE, false);
}
_updateUsedThemes(_usedThemesExceptId(aId));
AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
},
previewTheme: function LightweightThemeManager_previewTheme(aData) {
let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
cancel.data = false;
Services.obs.notifyObservers(cancel, "lightweight-theme-preview-requested",
JSON.stringify(aData));
if (cancel.data)
return;
if (_previewTimer)
_previewTimer.cancel();
else
_previewTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
_previewTimer.initWithCallback(_previewTimerCallback,
MAX_PREVIEW_SECONDS * 1000,
_previewTimer.TYPE_ONE_SHOT);
_notifyWindows(aData);
},
resetPreview: function LightweightThemeManager_resetPreview() {
if (_previewTimer) {
_previewTimer.cancel();
_previewTimer = null;
_notifyWindows(this.currentThemeForDisplay);
}
},
parseTheme: function LightweightThemeManager_parseTheme(aString, aBaseURI) {
try {
return _sanitizeTheme(JSON.parse(aString), aBaseURI, false);
} catch (e) {
return null;
}
},
updateCurrentTheme: function LightweightThemeManager_updateCurrentTheme() {
try {
if (!_prefs.getBoolPref("update.enabled"))
return;
} catch (e) {
return;
}
var theme = this.currentTheme;
if (!theme || !theme.updateURL)
return;
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
req.mozBackgroundRequest = true;
req.overrideMimeType("text/plain");
req.open("GET", theme.updateURL, true);
// Prevent the request from reading from the cache.
req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
// Prevent the request from writing to the cache.
req.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
var self = this;
req.addEventListener("load", function loadEventListener() {
if (req.status != 200)
return;
let newData = self.parseTheme(req.responseText, theme.updateURL);
if (!newData ||
newData.id != theme.id ||
_version(newData) == _version(theme))
return;
var currentTheme = self.currentTheme;
if (currentTheme && currentTheme.id == theme.id)
self.currentTheme = newData;
}, false);
req.send(null);
},
/**
* Switches to a new lightweight theme.
*
* @param aData
* The lightweight theme to switch to
*/
themeChanged: function LightweightThemeManager_themeChanged(aData) {
if (_previewTimer) {
_previewTimer.cancel();
_previewTimer = null;
}
if (aData) {
let usedThemes = _usedThemesExceptId(aData.id);
usedThemes.unshift(aData);
_updateUsedThemes(usedThemes);
if (PERSIST_ENABLED) {
LightweightThemeImageOptimizer.purge();
_persistImages(aData, function themeChanged_persistImages() {
_notifyWindows(this.currentThemeForDisplay);
}.bind(this));
}
}
_prefs.setBoolPref("isThemeSelected", aData != null);
_notifyWindows(aData);
Services.obs.notifyObservers(null, "lightweight-theme-changed", null);
},
/**
* Starts the Addons provider and enables the new lightweight theme if
* necessary.
*/
startup: function LightweightThemeManager_startup() {
if (Services.prefs.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) {
let id = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
if (id)
this.themeChanged(this.getUsedTheme(id));
else
this.themeChanged(null);
Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT);
}
_prefs.addObserver("", _prefObserver, false);
},
/**
* Shuts down the provider.
*/
shutdown: function LightweightThemeManager_shutdown() {
_prefs.removeObserver("", _prefObserver);
},
/**
* Called when a new add-on has been enabled when only one add-on of that type
* can be enabled.
*
* @param aId
* The ID of the newly enabled add-on
* @param aType
* The type of the newly enabled add-on
* @param aPendingRestart
* true if the newly enabled add-on will only become enabled after a
* restart
*/
addonChanged: function LightweightThemeManager_addonChanged(aId, aType, aPendingRestart) {
if (aType != ADDON_TYPE)
return;
let id = _getInternalID(aId);
let current = this.currentTheme;
try {
let next = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
if (id == next && aPendingRestart)
return;
Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT);
if (next) {
AddonManagerPrivate.callAddonListeners("onOperationCancelled",
new AddonWrapper(this.getUsedTheme(next)));
}
else {
if (id == current.id) {
AddonManagerPrivate.callAddonListeners("onOperationCancelled",
new AddonWrapper(current));
return;
}
}
}
catch (e) {
}
if (current) {
if (current.id == id)
return;
_themeIDBeingDisabled = current.id;
let wrapper = new AddonWrapper(current);
if (aPendingRestart) {
Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, "");
AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, true);
}
else {
AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false);
this.themeChanged(null);
AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
}
_themeIDBeingDisabled = null;
}
if (id) {
let theme = this.getUsedTheme(id);
_themeIDBeingEnabled = id;
let wrapper = new AddonWrapper(theme);
if (aPendingRestart) {
AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, true);
Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, id);
// Flush the preferences to disk so they survive any crash
Services.prefs.savePrefFile(null);
}
else {
AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false);
this.themeChanged(theme);
AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
}
_themeIDBeingEnabled = null;
}
},
/**
* Called to get an Addon with a particular ID.
*
* @param aId
* The ID of the add-on to retrieve
* @param aCallback
* A callback to pass the Addon to
*/
getAddonByID: function LightweightThemeManager_getAddonByID(aId, aCallback) {
let id = _getInternalID(aId);
if (!id) {
aCallback(null);
return;
}
let theme = this.getUsedTheme(id);
if (!theme) {
aCallback(null);
return;
}
aCallback(new AddonWrapper(theme));
},
/**
* Called to get Addons of a particular type.
*
* @param aTypes
* An array of types to fetch. Can be null to get all types.
* @param aCallback
* A callback to pass an array of Addons to
*/
getAddonsByTypes: function LightweightThemeManager_getAddonsByTypes(aTypes, aCallback) {
if (aTypes && aTypes.indexOf(ADDON_TYPE) == -1) {
aCallback([]);
return;
}
aCallback([new AddonWrapper(a) for each (a in this.usedThemes)]);
},
};
/**
* The AddonWrapper wraps lightweight theme to provide the data visible to
* consumers of the AddonManager API.
*/
function AddonWrapper(aTheme) {
this.__defineGetter__("id", function AddonWrapper_idGetter() aTheme.id + ID_SUFFIX);
this.__defineGetter__("type", function AddonWrapper_typeGetter() ADDON_TYPE);
this.__defineGetter__("isActive", function AddonWrapper_isActiveGetter() {
let current = LightweightThemeManager.currentTheme;
if (current)
return aTheme.id == current.id;
return false;
});
this.__defineGetter__("name", function AddonWrapper_nameGetter() aTheme.name);
this.__defineGetter__("version", function AddonWrapper_versionGetter() {
return "version" in aTheme ? aTheme.version : "";
});
["description", "homepageURL", "iconURL"].forEach(function(prop) {
this.__defineGetter__(prop, function AddonWrapper_optionalPropGetter() {
return prop in aTheme ? aTheme[prop] : null;
});
}, this);
["installDate", "updateDate"].forEach(function(prop) {
this.__defineGetter__(prop, function AddonWrapper_datePropGetter() {
return prop in aTheme ? new Date(aTheme[prop]) : null;
});
}, this);
this.__defineGetter__("creator", function AddonWrapper_creatorGetter() {
return new AddonManagerPrivate.AddonAuthor(aTheme.author);
});
this.__defineGetter__("screenshots", function AddonWrapper_screenshotsGetter() {
let url = aTheme.previewURL;
return [new AddonManagerPrivate.AddonScreenshot(url)];
});
this.__defineGetter__("pendingOperations",
function AddonWrapper_pendingOperationsGetter() {
let pending = AddonManager.PENDING_NONE;
if (this.isActive == this.userDisabled)
pending |= this.isActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE;
return pending;
});
this.__defineGetter__("operationsRequiringRestart",
function AddonWrapper_operationsRequiringRestartGetter() {
// If a non-default theme is in use then a restart will be required to
// enable lightweight themes unless dynamic theme switching is enabled
if (Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) {
try {
if (Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED))
return AddonManager.OP_NEEDS_RESTART_NONE;
}
catch (e) {
}
return AddonManager.OP_NEEDS_RESTART_ENABLE;
}
return AddonManager.OP_NEEDS_RESTART_NONE;
});
this.__defineGetter__("size", function AddonWrapper_sizeGetter() {
// The size changes depending on whether the theme is in use or not, this is
// probably not worth exposing.
return null;
});
this.__defineGetter__("permissions", function AddonWrapper_permissionsGetter() {
let permissions = AddonManager.PERM_CAN_UNINSTALL;
if (this.userDisabled)
permissions |= AddonManager.PERM_CAN_ENABLE;
else
permissions |= AddonManager.PERM_CAN_DISABLE;
return permissions;
});
this.__defineGetter__("userDisabled", function AddonWrapper_userDisabledGetter() {
if (_themeIDBeingEnabled == aTheme.id)
return false;
if (_themeIDBeingDisabled == aTheme.id)
return true;
try {
let toSelect = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
return aTheme.id != toSelect;
}
catch (e) {
let current = LightweightThemeManager.currentTheme;
return !current || current.id != aTheme.id;
}
});
this.__defineSetter__("userDisabled", function AddonWrapper_userDisabledSetter(val) {
if (val == this.userDisabled)
return val;
if (val)
LightweightThemeManager.currentTheme = null;
else
LightweightThemeManager.currentTheme = aTheme;
return val;
});
this.uninstall = function AddonWrapper_uninstall() {
LightweightThemeManager.forgetUsedTheme(aTheme.id);
};
this.cancelUninstall = function AddonWrapper_cancelUninstall() {
throw new Error("Theme is not marked to be uninstalled");
};
this.findUpdates = function AddonWrapper_findUpdates(listener, reason, appVersion, platformVersion) {
AddonManagerPrivate.callNoUpdateListeners(this, listener, reason, appVersion, platformVersion);
};
}
AddonWrapper.prototype = {
// Lightweight themes are never disabled by the application
get appDisabled() {
return false;
},
// Lightweight themes are always compatible
get isCompatible() {
return true;
},
get isPlatformCompatible() {
return true;
},
get scope() {
return AddonManager.SCOPE_PROFILE;
},
get foreignInstall() {
return false;
},
// Lightweight themes are always compatible
isCompatibleWith: function AddonWrapper_isCompatibleWith(appVersion, platformVersion) {
return true;
},
// Lightweight themes are always securely updated
get providesUpdatesSecurely() {
return true;
},
// Lightweight themes are never blocklisted
get blocklistState() {
return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
}
};
/**
* Converts the ID used by the public AddonManager API to an lightweight theme
* ID.
*
* @param id
* The ID to be converted
*
* @return the lightweight theme ID or null if the ID was not for a lightweight
* theme.
*/
function _getInternalID(id) {
if (!id)
return null;
let len = id.length - ID_SUFFIX.length;
if (len > 0 && id.substring(len) == ID_SUFFIX)
return id.substring(0, len);
return null;
}
function _setCurrentTheme(aData, aLocal) {
aData = _sanitizeTheme(aData, null, aLocal);
let needsRestart = (ADDON_TYPE == "theme") &&
Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN);
let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
cancel.data = false;
Services.obs.notifyObservers(cancel, "lightweight-theme-change-requested",
JSON.stringify(aData));
if (aData) {
let theme = LightweightThemeManager.getUsedTheme(aData.id);
let isInstall = !theme || theme.version != aData.version;
if (isInstall) {
aData.updateDate = Date.now();
if (theme && "installDate" in theme)
aData.installDate = theme.installDate;
else
aData.installDate = aData.updateDate;
var oldWrapper = theme ? new AddonWrapper(theme) : null;
var wrapper = new AddonWrapper(aData);
AddonManagerPrivate.callInstallListeners("onExternalInstall", null,
wrapper, oldWrapper, false);
AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false);
}
let current = LightweightThemeManager.currentTheme;
let usedThemes = _usedThemesExceptId(aData.id);
if (current && current.id != aData.id)
usedThemes.splice(1, 0, aData);
else
usedThemes.unshift(aData);
_updateUsedThemes(usedThemes);
if (isInstall)
AddonManagerPrivate.callAddonListeners("onInstalled", wrapper);
}
if (cancel.data)
return null;
AddonManagerPrivate.notifyAddonChanged(aData ? aData.id + ID_SUFFIX : null,
ADDON_TYPE, needsRestart);
return LightweightThemeManager.currentTheme;
}
function _sanitizeTheme(aData, aBaseURI, aLocal) {
if (!aData || typeof aData != "object")
return null;
var resourceProtocols = ["http", "https", "resource"];
if (aLocal)
resourceProtocols.push("file");
var resourceProtocolExp = new RegExp("^(" + resourceProtocols.join("|") + "):");
function sanitizeProperty(prop) {
if (!(prop in aData))
return null;
if (typeof aData[prop] != "string")
return null;
let val = aData[prop].trim();
if (!val)
return null;
if (!/URL$/.test(prop))
return val;
try {
val = _makeURI(val, aBaseURI ? _makeURI(aBaseURI) : null).spec;
if ((prop == "updateURL" ? /^https:/ : resourceProtocolExp).test(val))
return val;
return null;
}
catch (e) {
return null;
}
}
let result = {};
for (let mandatoryProperty of MANDATORY) {
let val = sanitizeProperty(mandatoryProperty);
if (!val)
throw Components.results.NS_ERROR_INVALID_ARG;
result[mandatoryProperty] = val;
}
for (let optionalProperty of OPTIONAL) {
let val = sanitizeProperty(optionalProperty);
if (!val)
continue;
result[optionalProperty] = val;
}
return result;
}
function _usedThemesExceptId(aId)
LightweightThemeManager.usedThemes.filter(
function usedThemesExceptId_filterID(t) "id" in t && t.id != aId);
function _version(aThemeData)
aThemeData.version || "";
function _makeURI(aURL, aBaseURI)
Services.io.newURI(aURL, null, aBaseURI);
function _updateUsedThemes(aList) {
// Send uninstall events for all themes that need to be removed.
while (aList.length > _maxUsedThemes) {
let wrapper = new AddonWrapper(aList[aList.length - 1]);
AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
aList.pop();
AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
}
var str = Cc["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
str.data = JSON.stringify(aList);
_prefs.setComplexValue("usedThemes", Ci.nsISupportsString, str);
Services.obs.notifyObservers(null, "lightweight-theme-list-changed", null);
}
function _notifyWindows(aThemeData) {
Services.obs.notifyObservers(null, "lightweight-theme-styling-update",
JSON.stringify(aThemeData));
}
var _previewTimer;
var _previewTimerCallback = {
notify: function _previewTimerCallback_notify() {
LightweightThemeManager.resetPreview();
}
};
/**
* Called when any of the lightweightThemes preferences are changed.
*/
function _prefObserver(aSubject, aTopic, aData) {
switch (aData) {
case "maxUsedThemes":
try {
_maxUsedThemes = _prefs.getIntPref(aData);
}
catch (e) {
_maxUsedThemes = DEFAULT_MAX_USED_THEMES_COUNT;
}
// Update the theme list to remove any themes over the number we keep
_updateUsedThemes(LightweightThemeManager.usedThemes);
break;
}
}
function _persistImages(aData, aCallback) {
function onSuccess(key) function () {
let current = LightweightThemeManager.currentTheme;
if (current && current.id == aData.id) {
_prefs.setBoolPref("persisted." + key, true);
}
if (--numFilesToPersist == 0 && aCallback) {
aCallback();
}
};
let numFilesToPersist = 0;
for (let key in PERSIST_FILES) {
_prefs.setBoolPref("persisted." + key, false);
if (aData[key]) {
numFilesToPersist++;
_persistImage(aData[key], PERSIST_FILES[key], onSuccess(key));
}
}
}
function _getLocalImageURI(localFileName) {
var localFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
localFile.append(localFileName);
return Services.io.newFileURI(localFile);
}
function _persistImage(sourceURL, localFileName, successCallback) {
if (/^(file|resource):/.test(sourceURL))
return;
var targetURI = _getLocalImageURI(localFileName);
var sourceURI = _makeURI(sourceURL);
var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
.createInstance(Ci.nsIWebBrowserPersist);
persist.persistFlags =
Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION |
(PERSIST_BYPASS_CACHE ?
Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE :
Ci.nsIWebBrowserPersist.PERSIST_FLAGS_FROM_CACHE);
persist.progressListener = new _persistProgressListener(successCallback);
persist.saveURI(sourceURI, null,
null, Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE,
null, null, targetURI, null);
}
function _persistProgressListener(successCallback) {
this.onLocationChange = function persistProgressListener_onLocationChange() {};
this.onProgressChange = function persistProgressListener_onProgressChange() {};
this.onStatusChange = function persistProgressListener_onStatusChange() {};
this.onSecurityChange = function persistProgressListener_onSecurityChange() {};
this.onStateChange = function persistProgressListener_onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
if (aRequest &&
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
try {
if (aRequest.QueryInterface(Ci.nsIHttpChannel).requestSucceeded) {
// success
successCallback();
return;
}
} catch (e) { }
// failure
}
};
}
AddonManagerPrivate.registerProvider(LightweightThemeManager, [
new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 5000)
]);

View file

@ -0,0 +1,204 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This component serves as integration between the platform and AddonManager.
* It is responsible for initializing and shutting down the AddonManager as well
* as passing new installs from webpages to the AddonManager.
*/
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
const PREF_EM_UPDATE_INTERVAL = "extensions.update.interval";
// The old XPInstall error codes
const EXECUTION_ERROR = -203;
const CANT_READ_ARCHIVE = -207;
const USER_CANCELLED = -210;
const DOWNLOAD_ERROR = -228;
const UNSUPPORTED_TYPE = -244;
const SUCCESS = 0;
const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled";
const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
const CHILD_SCRIPT = "resource://gre/modules/addons/Content.js";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let gSingleton = null;
let gParentMM = null;
function amManager() {
Cu.import("resource://gre/modules/AddonManager.jsm");
let globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
globalMM.loadFrameScript(CHILD_SCRIPT, true);
globalMM.addMessageListener(MSG_INSTALL_ADDONS, this);
gParentMM = Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
gParentMM.addMessageListener(MSG_INSTALL_ENABLED, this);
// Needed so receiveMessage can be called directly by JS callers
this.wrappedJSObject = this;
}
amManager.prototype = {
observe: function AMC_observe(aSubject, aTopic, aData) {
if (aTopic == "addons-startup")
AddonManagerPrivate.startup();
},
/**
* @see amIAddonManager.idl
*/
mapURIToAddonID: function AMC_mapURIToAddonID(uri, id) {
id.value = AddonManager.mapURIToAddonID(uri);
return !!id.value;
},
/**
* @see amIWebInstaller.idl
*/
isInstallEnabled: function AMC_isInstallEnabled(aMimetype, aReferer) {
return AddonManager.isInstallEnabled(aMimetype);
},
/**
* @see amIWebInstaller.idl
*/
installAddonsFromWebpage: function AMC_installAddonsFromWebpage(aMimetype,
aBrowser,
aInstallingPrincipal,
aUris, aHashes,
aNames, aIcons,
aCallback) {
if (aUris.length == 0)
return false;
let retval = true;
if (!AddonManager.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
aCallback = null;
retval = false;
}
let installs = [];
function buildNextInstall() {
if (aUris.length == 0) {
AddonManager.installAddonsFromWebpage(aMimetype, aBrowser, aInstallingPrincipal, installs);
return;
}
let uri = aUris.shift();
AddonManager.getInstallForURL(uri, function buildNextInstall_getInstallForURL(aInstall) {
function callCallback(aUri, aStatus) {
try {
aCallback.onInstallEnded(aUri, aStatus);
}
catch (e) {
Components.utils.reportError(e);
}
}
if (aInstall) {
installs.push(aInstall);
if (aCallback) {
aInstall.addListener({
onDownloadCancelled: function buildNextInstall_onDownloadCancelled(aInstall) {
callCallback(uri, USER_CANCELLED);
},
onDownloadFailed: function buildNextInstall_onDownloadFailed(aInstall) {
if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE)
callCallback(uri, CANT_READ_ARCHIVE);
else
callCallback(uri, DOWNLOAD_ERROR);
},
onInstallFailed: function buildNextInstall_onInstallFailed(aInstall) {
callCallback(uri, EXECUTION_ERROR);
},
onInstallEnded: function buildNextInstall_onInstallEnded(aInstall, aStatus) {
callCallback(uri, SUCCESS);
}
});
}
}
else if (aCallback) {
aCallback.onInstallEnded(uri, UNSUPPORTED_TYPE);
}
buildNextInstall();
}, aMimetype, aHashes.shift(), aNames.shift(), aIcons.shift(), null, aBrowser);
}
buildNextInstall();
return retval;
},
notify: function AMC_notify(aTimer) {
AddonManagerPrivate.backgroundUpdateTimerHandler();
},
/**
* messageManager callback function.
*
* Listens to requests from child processes for InstallTrigger
* activity, and sends back callbacks.
*/
receiveMessage: function AMC_receiveMessage(aMessage) {
let payload = aMessage.data;
switch (aMessage.name) {
case MSG_INSTALL_ENABLED:
return AddonManager.isInstallEnabled(payload.mimetype);
case MSG_INSTALL_ADDONS: {
let callback = null;
if (payload.callbackID != -1) {
callback = {
onInstallEnded: function ITP_callback(url, status) {
gParentMM.broadcastAsyncMessage(MSG_INSTALL_CALLBACK, {
callbackID: payload.callbackID,
url: url,
status: status
});
},
};
}
return this.installAddonsFromWebpage(payload.mimetype,
aMessage.target, payload.triggeringPrincipal, payload.uris,
payload.hashes, payload.names, payload.icons, callback);
}
}
},
classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"),
_xpcom_factory: {
createInstance: function AMC_createInstance(aOuter, aIid) {
if (aOuter != null)
throw Components.Exception("Component does not support aggregation",
Cr.NS_ERROR_NO_AGGREGATION);
if (!gSingleton)
gSingleton = new amManager();
return gSingleton.QueryInterface(aIid);
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.amIAddonManager,
Ci.amIWebInstaller,
Ci.nsITimerCallback,
Ci.nsIObserver,
Ci.nsIMessageListener])
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amManager]);

View file

@ -0,0 +1,100 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const XPI_CONTENT_TYPE = "application/x-xpinstall";
const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
function amContentHandler() {
}
amContentHandler.prototype = {
/**
* Handles a new request for an application/x-xpinstall file.
*
* @param aMimetype
* The mimetype of the file
* @param aContext
* The context passed to nsIChannel.asyncOpen
* @param aRequest
* The nsIRequest dealing with the content
*/
handleContent: function XCH_handleContent(aMimetype, aContext, aRequest) {
if (aMimetype != XPI_CONTENT_TYPE)
throw Cr.NS_ERROR_WONT_HANDLE_CONTENT;
if (!(aRequest instanceof Ci.nsIChannel))
throw Cr.NS_ERROR_WONT_HANDLE_CONTENT;
let uri = aRequest.URI;
let window = null;
let callbacks = aRequest.notificationCallbacks ?
aRequest.notificationCallbacks :
aRequest.loadGroup.notificationCallbacks;
if (callbacks)
window = callbacks.getInterface(Ci.nsIDOMWindow);
aRequest.cancel(Cr.NS_BINDING_ABORTED);
let installs = {
uris: [uri.spec],
hashes: [null],
names: [null],
icons: [null],
mimetype: XPI_CONTENT_TYPE,
triggeringPrincipal: aRequest.loadInfo.triggeringPrincipal,
callbackID: -1
};
if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
// When running in the main process this might be a frame inside an
// in-content UI page, walk up to find the first frame element in a chrome
// privileged document
let element = window.frameElement;
let ssm = Services.scriptSecurityManager;
while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal))
element = element.ownerDocument.defaultView.frameElement;
if (element) {
let listener = Cc["@mozilla.org/addons/integration;1"].
getService(Ci.nsIMessageListener);
listener.wrappedJSObject.receiveMessage({
name: MSG_INSTALL_ADDONS,
target: element,
data: installs,
});
return;
}
}
// Fall back to sending through the message manager
let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
messageManager.sendAsyncMessage(MSG_INSTALL_ADDONS, installs);
},
classID: Components.ID("{7beb3ba8-6ec3-41b4-b67c-da89b8518922}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler]),
log : function XCH_log(aMsg) {
let msg = "amContentHandler.js: " + (aMsg.join ? aMsg.join("") : aMsg);
Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
logStringMessage(msg);
dump(msg + "\n");
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amContentHandler]);

View file

@ -0,0 +1,29 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIURI;
/**
* A service to make some AddonManager functionality available to C++ callers.
* Javascript callers should still use AddonManager.jsm directly.
*/
[scriptable, function, uuid(7b45d82d-7ad5-48d7-9b05-f32eb9818cd4)]
interface amIAddonManager : nsISupports
{
/**
* Synchronously map a URI to the corresponding Addon ID.
*
* Mappable URIs are limited to in-application resources belonging to the
* add-on, such as Javascript compartments, XUL windows, XBL bindings, etc.
* but do not include URIs from meta data, such as the add-on homepage.
*
* @param aURI
* The nsIURI to map
* @return
* true if the URI has been mapped successfully to an Addon ID
*/
boolean mapURIToAddonID(in nsIURI aURI, out AUTF8String aID);
};

View file

@ -0,0 +1,29 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
/**
* This service maps file system paths where add-ons reside to the ID
* of the add-on. Paths are added by the add-on manager. They can
* looked up by anyone.
*/
[scriptable, uuid(fcd9e270-dfb1-11e3-8b68-0800200c9a66)]
interface amIAddonPathService : nsISupports
{
/**
* Given a path to a file, return the ID of the add-on that the file belongs
* to. Returns an empty string if there is no add-on there. Note that if an
* add-on is located at /a/b/c, then looking up the path /a/b/c/d will return
* that add-on.
*/
AString findAddonId(in AString path);
/**
* Call this function to inform the service that the given file system path is
* associated with the given add-on ID.
*/
void insertPath(in AString path, in AString addonId);
};

View file

@ -0,0 +1,134 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIDOMElement;
interface nsIURI;
interface nsIVariant;
/**
* amIWebInstallInfo is used by the default implementation of
* amIWebInstallListener to communicate with the running application and allow
* it to warn the user about blocked installs and start the installs running.
*/
[scriptable, uuid(fa0b47a3-f819-47ac-bc66-4bd1d7f67b1d)]
interface amIWebInstallInfo : nsISupports
{
readonly attribute nsIDOMElement browser;
readonly attribute nsIURI originatingURI;
readonly attribute nsIVariant installs;
/**
* Starts all installs.
*/
void install();
};
/**
* The registered amIWebInstallListener is used to notify about new installs
* triggered by websites. The default implementation displays a confirmation
* dialog when add-ons are ready to install and uses the observer service to
* notify when installations are blocked.
*/
[scriptable, uuid(d9240d4b-6b3a-4cad-b402-de6c93337e0c)]
interface amIWebInstallListener : nsISupports
{
/**
* Called when installation by websites is currently disabled.
*
* @param aBrowser
* The browser that triggered the installs
* @param aUri
* The URI of the site that triggered the installs
* @param aInstalls
* The AddonInstalls that were blocked
* @param aCount
* The number of AddonInstalls
*/
void onWebInstallDisabled(in nsIDOMElement aBrowser, in nsIURI aUri,
[array, size_is(aCount)] in nsIVariant aInstalls,
[optional] in uint32_t aCount);
/**
* Called when the website is not allowed to directly prompt the user to
* install add-ons.
*
* @param aBrowser
* The browser that triggered the installs
* @param aUri
* The URI of the site that triggered the installs
* @param aInstalls
* The AddonInstalls that were blocked
* @param aCount
* The number of AddonInstalls
* @return true if the caller should start the installs
*/
boolean onWebInstallBlocked(in nsIDOMElement aBrowser, in nsIURI aUri,
[array, size_is(aCount)] in nsIVariant aInstalls,
[optional] in uint32_t aCount);
/**
* Called when a website wants to ask the user to install add-ons.
*
* @param aBrowser
* The browser that triggered the installs
* @param aUri
* The URI of the site that triggered the installs
* @param aInstalls
* The AddonInstalls that were requested
* @param aCount
* The number of AddonInstalls
* @return true if the caller should start the installs
*/
boolean onWebInstallRequested(in nsIDOMElement aBrowser, in nsIURI aUri,
[array, size_is(aCount)] in nsIVariant aInstalls,
[optional] in uint32_t aCount);
};
[scriptable, uuid(a80b89ad-bb1a-4c43-9cb7-3ae656556f78)]
interface amIWebInstallListener2 : nsISupports
{
/**
* Called when a non-same-origin resource attempted to initiate an install.
* Installs will have already been cancelled and cannot be restarted.
*
* @param aBrowser
* The browser that triggered the installs
* @param aUri
* The URI of the site that triggered the installs
* @param aInstalls
* The AddonInstalls that were blocked
* @param aCount
* The number of AddonInstalls
*/
boolean onWebInstallOriginBlocked(in nsIDOMElement aBrowser, in nsIURI aUri,
[array, size_is(aCount)] in nsIVariant aInstalls,
[optional] in uint32_t aCount);
};
/**
* amIWebInstallPrompt is used, if available, by the default implementation of
* amIWebInstallInfo to display a confirmation UI to the user before running
* installs.
*/
[scriptable, uuid(386906f1-4d18-45bf-bc81-5dcd68e42c3b)]
interface amIWebInstallPrompt : nsISupports
{
/**
* Get a confirmation that the user wants to start the installs.
*
* @param aBrowser
* The browser that triggered the installs
* @param aUri
* The URI of the site that triggered the installs
* @param aInstalls
* The AddonInstalls that were requested
* @param aCount
* The number of AddonInstalls
*/
void confirm(in nsIDOMElement aBrowser, in nsIURI aUri,
[array, size_is(aCount)] in nsIVariant aInstalls,
[optional] in uint32_t aCount);
};

View file

@ -0,0 +1,82 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIDOMElement;
interface nsIVariant;
interface nsIURI;
/**
* A callback function used to notify webpages when a requested install has
* ended.
*
* NOTE: This is *not* the same as InstallListener.
*/
[scriptable, function, uuid(bb22f5c0-3ca1-48f6-873c-54e87987700f)]
interface amIInstallCallback : nsISupports
{
/**
* Called when an install completes or fails.
*
* @param aUrl
* The url of the add-on being installed
* @param aStatus
* 0 if the install was successful or negative if not
*/
void onInstallEnded(in AString aUrl, in int32_t aStatus);
};
/**
* This interface is used to allow webpages to start installing add-ons.
*/
[scriptable, uuid(658d6c09-15e0-4688-bee8-8551030472a9)]
interface amIWebInstaller : nsISupports
{
/**
* Checks if installation is enabled for a webpage.
*
* @param aMimetype
* The mimetype for the add-on to be installed
* @param referer
* The URL of the webpage trying to install an add-on
* @return true if installation is enabled
*/
boolean isInstallEnabled(in AString aMimetype, in nsIURI aReferer);
/**
* Installs an array of add-ons at the request of a webpage
*
* @param aMimetype
* The mimetype for the add-ons
* @param aBrowser
* The browser installing the add-ons.
* @param aReferer
* The URI for the webpage installing the add-ons
* @param aUris
* The URIs of add-ons to be installed
* @param aHashes
* The hashes for the add-ons to be installed
* @param aNames
* The names for the add-ons to be installed
* @param aIcons
* The icons for the add-ons to be installed
* @param aCallback
* An optional callback to notify about installation success and
* failure
* @param aInstallCount
* An optional argument including the number of add-ons to install
* @return true if the installation was successfully started
*/
boolean installAddonsFromWebpage(in AString aMimetype,
in nsIDOMElement aBrowser,
in nsIURI aReferer,
[array, size_is(aInstallCount)] in wstring aUris,
[array, size_is(aInstallCount)] in wstring aHashes,
[array, size_is(aInstallCount)] in wstring aNames,
[array, size_is(aInstallCount)] in wstring aIcons,
[optional] in amIInstallCallback aCallback,
[optional] in uint32_t aInstallCount);
};

View file

@ -0,0 +1,230 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Log.jsm");
const XPINSTALL_MIMETYPE = "application/x-xpinstall";
const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled";
const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
let log = Log.repository.getLogger("AddonManager.InstallTrigger");
log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"];
function CallbackObject(id, callback, urls, mediator) {
this.id = id;
this.callback = callback;
this.urls = new Set(urls);
this.callCallback = function(url, status) {
try {
this.callback(url, status);
}
catch (e) {
log.warn("InstallTrigger callback threw an exception: " + e);
}
this.urls.delete(url);
if (this.urls.size == 0)
mediator._callbacks.delete(id);
};
}
function RemoteMediator(windowID) {
this._windowID = windowID;
this._lastCallbackID = 0;
this._callbacks = new Map();
this.mm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender);
this.mm.addWeakMessageListener(MSG_INSTALL_CALLBACK, this);
}
RemoteMediator.prototype = {
receiveMessage: function(message) {
if (message.name == MSG_INSTALL_CALLBACK) {
let payload = message.data;
let callbackHandler = this._callbacks.get(payload.callbackID);
if (callbackHandler) {
callbackHandler.callCallback(payload.url, payload.status);
}
}
},
enabled: function(url) {
let params = {
mimetype: XPINSTALL_MIMETYPE
};
return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0];
},
install: function(installs, principal, callback, window) {
let callbackID = this._addCallback(callback, installs.uris);
installs.mimetype = XPINSTALL_MIMETYPE;
installs.triggeringPrincipal = principal;
installs.callbackID = callbackID;
if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
// When running in the main process this might be a frame inside an
// in-content UI page, walk up to find the first frame element in a chrome
// privileged document
let element = window.frameElement;
let ssm = Services.scriptSecurityManager;
while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal))
element = element.ownerDocument.defaultView.frameElement;
if (element) {
let listener = Cc["@mozilla.org/addons/integration;1"].
getService(Ci.nsIMessageListener);
return listener.wrappedJSObject.receiveMessage({
name: MSG_INSTALL_ADDONS,
target: element,
data: installs,
});
}
}
// Fall back to sending through the message manager
let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
return messageManager.sendSyncMessage(MSG_INSTALL_ADDONS, installs)[0];
},
_addCallback: function(callback, urls) {
if (!callback || typeof callback != "function")
return -1;
let callbackID = this._windowID + "-" + ++this._lastCallbackID;
let callbackObject = new CallbackObject(callbackID, callback, urls, this);
this._callbacks.set(callbackID, callbackObject);
return callbackID;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference])
};
function InstallTrigger() {
}
InstallTrigger.prototype = {
// Here be magic. We've declared ourselves as providing the
// nsIDOMGlobalPropertyInitializer interface, and are registered in the
// "JavaScript-global-property" category in the XPCOM category manager. This
// means that for newly created windows, XPCOM will createinstance this
// object, and then call init, passing in the window for which we need to
// provide an instance. We then initialize ourselves and return the webidl
// version of this object using the webidl-provided _create method, which
// XPCOM will then duly expose as a property value on the window. All this
// indirection is necessary because webidl does not (yet) support statics
// (bug 863952). See bug 926712 for more details about this implementation.
init: function(window) {
this._window = window;
this._principal = window.document.nodePrincipal;
this._url = window.document.documentURIObject;
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
let utils = window.getInterface(Components.interfaces.nsIDOMWindowUtils);
this._mediator = new RemoteMediator(utils.currentInnerWindowID);
return window.InstallTriggerImpl._create(window, this);
},
enabled: function() {
return this._mediator.enabled(this._url.spec);
},
updateEnabled: function() {
return this.enabled();
},
install: function(installs, callback) {
let installData = {
uris: [],
hashes: [],
names: [],
icons: [],
};
for (let name of Object.keys(installs)) {
let item = installs[name];
if (typeof item === "string") {
item = { URL: item };
}
if (!item.URL) {
throw new this._window.DOMError("Error", "Missing URL property for '" + name + "'");
}
let url = this._resolveURL(item.URL);
if (!this._checkLoadURIFromScript(url)) {
throw new this._window.DOMError("SecurityError", "Insufficient permissions to install: " + url.spec);
}
let iconUrl = null;
if (item.IconURL) {
iconUrl = this._resolveURL(item.IconURL);
if (!this._checkLoadURIFromScript(iconUrl)) {
iconUrl = null; // If page can't load the icon, just ignore it
}
}
installData.uris.push(url.spec);
installData.hashes.push(item.Hash || null);
installData.names.push(name);
installData.icons.push(iconUrl ? iconUrl.spec : null);
}
return this._mediator.install(installData, this._principal, callback, this._window);
},
startSoftwareUpdate: function(url, flags) {
let filename = Services.io.newURI(url, null, null)
.QueryInterface(Ci.nsIURL)
.filename;
let args = {};
args[filename] = { "URL": url };
return this.install(args);
},
installChrome: function(type, url, skin) {
return this.startSoftwareUpdate(url);
},
_resolveURL: function (url) {
return Services.io.newURI(url, null, this._url);
},
_checkLoadURIFromScript: function(uri) {
let secman = Services.scriptSecurityManager;
try {
secman.checkLoadURIWithPrincipal(this._principal,
uri,
secman.DISALLOW_INHERIT_PRINCIPAL);
return true;
}
catch(e) {
return false;
}
},
classID: Components.ID("{9df8ef2b-94da-45c9-ab9f-132eb55fddf1}"),
contractID: "@mozilla.org/addons/installtrigger;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer])
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InstallTrigger]);

View file

@ -0,0 +1,342 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This is a default implementation of amIWebInstallListener that should work
* for most applications but can be overriden. It notifies the observer service
* about blocked installs. For normal installs it pops up an install
* confirmation when all the add-ons have been downloaded.
*/
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromptUtils", "resource://gre/modules/SharedPromptUtils.jsm");
const URI_XPINSTALL_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
// Installation can begin from any of these states
const READY_STATES = [
AddonManager.STATE_AVAILABLE,
AddonManager.STATE_DOWNLOAD_FAILED,
AddonManager.STATE_INSTALL_FAILED,
AddonManager.STATE_CANCELLED
];
Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.weblistener";
// Create a new logger for use by the Addons Web Listener
// (Requires AddonManager.jsm)
let logger = Log.repository.getLogger(LOGGER_ID);
function notifyObservers(aTopic, aBrowser, aUri, aInstalls) {
let info = {
browser: aBrowser,
originatingURI: aUri,
installs: aInstalls,
QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
};
Services.obs.notifyObservers(info, aTopic, null);
}
/**
* Creates a new installer to monitor downloads and prompt to install when
* ready
*
* @param aBrowser
* The browser that started the installations
* @param aUrl
* The URL that started the installations
* @param aInstalls
* An array of AddonInstalls
*/
function Installer(aBrowser, aUrl, aInstalls) {
this.browser = aBrowser;
this.url = aUrl;
this.downloads = aInstalls;
this.installed = [];
notifyObservers("addon-install-started", aBrowser, aUrl, aInstalls);
aInstalls.forEach(function(aInstall) {
aInstall.addListener(this);
// Start downloading if it hasn't already begun
if (READY_STATES.indexOf(aInstall.state) != -1)
aInstall.install();
}, this);
this.checkAllDownloaded();
}
Installer.prototype = {
browser: null,
downloads: null,
installed: null,
isDownloading: true,
/**
* Checks if all downloads are now complete and if so prompts to install.
*/
checkAllDownloaded: function Installer_checkAllDownloaded() {
// Prevent re-entrancy caused by the confirmation dialog cancelling unwanted
// installs.
if (!this.isDownloading)
return;
var failed = [];
var installs = [];
for (let install of this.downloads) {
switch (install.state) {
case AddonManager.STATE_AVAILABLE:
case AddonManager.STATE_DOWNLOADING:
// Exit early if any add-ons haven't started downloading yet or are
// still downloading
return;
case AddonManager.STATE_DOWNLOAD_FAILED:
failed.push(install);
break;
case AddonManager.STATE_DOWNLOADED:
// App disabled items are not compatible and so fail to install
if (install.addon.appDisabled)
failed.push(install);
else
installs.push(install);
if (install.linkedInstalls) {
install.linkedInstalls.forEach(function(aInstall) {
aInstall.addListener(this);
// App disabled items are not compatible and so fail to install
if (aInstall.addon.appDisabled)
failed.push(aInstall);
else
installs.push(aInstall);
}, this);
}
break;
case AddonManager.STATE_CANCELLED:
// Just ignore cancelled downloads
break;
default:
logger.warn("Download of " + install.sourceURI.spec + " in unexpected state " +
install.state);
}
}
this.isDownloading = false;
this.downloads = installs;
if (failed.length > 0) {
// Stop listening and cancel any installs that are failed because of
// compatibility reasons.
failed.forEach(function(aInstall) {
if (aInstall.state == AddonManager.STATE_DOWNLOADED) {
aInstall.removeListener(this);
aInstall.cancel();
}
}, this);
notifyObservers("addon-install-failed", this.browser, this.url, failed);
}
// If none of the downloads were successful then exit early
if (this.downloads.length == 0)
return;
// Check for a custom installation prompt that may be provided by the
// applicaton
if ("@mozilla.org/addons/web-install-prompt;1" in Cc) {
try {
let prompt = Cc["@mozilla.org/addons/web-install-prompt;1"].
getService(Ci.amIWebInstallPrompt);
prompt.confirm(this.browser, this.url, this.downloads, this.downloads.length);
return;
}
catch (e) {}
}
let args = {};
args.url = this.url;
args.installs = this.downloads;
args.wrappedJSObject = args;
try {
Cc["@mozilla.org/base/telemetry;1"].
getService(Ci.nsITelemetry).
getHistogramById("SECURITY_UI").
add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
let parentWindow = null;
if (this.browser) {
parentWindow = this.browser.ownerDocument.defaultView;
PromptUtils.fireDialogEvent(parentWindow, "DOMWillOpenModalDialog", this.browser);
}
Services.ww.openWindow(parentWindow, URI_XPINSTALL_DIALOG,
null, "chrome,modal,centerscreen", args);
} catch (e) {
logger.warn("Exception showing install confirmation dialog", e);
this.downloads.forEach(function(aInstall) {
aInstall.removeListener(this);
// Cancel the installs, as currently there is no way to make them fail
// from here.
aInstall.cancel();
}, this);
notifyObservers("addon-install-cancelled", this.browser, this.url,
this.downloads);
}
},
/**
* Checks if all installs are now complete and if so notifies observers.
*/
checkAllInstalled: function Installer_checkAllInstalled() {
var failed = [];
for (let install of this.downloads) {
switch(install.state) {
case AddonManager.STATE_DOWNLOADED:
case AddonManager.STATE_INSTALLING:
// Exit early if any add-ons haven't started installing yet or are
// still installing
return;
case AddonManager.STATE_INSTALL_FAILED:
failed.push(install);
break;
}
}
this.downloads = null;
if (failed.length > 0)
notifyObservers("addon-install-failed", this.browser, this.url, failed);
if (this.installed.length > 0)
notifyObservers("addon-install-complete", this.browser, this.url, this.installed);
this.installed = null;
},
onDownloadCancelled: function Installer_onDownloadCancelled(aInstall) {
aInstall.removeListener(this);
this.checkAllDownloaded();
},
onDownloadFailed: function Installer_onDownloadFailed(aInstall) {
aInstall.removeListener(this);
this.checkAllDownloaded();
},
onDownloadEnded: function Installer_onDownloadEnded(aInstall) {
this.checkAllDownloaded();
return false;
},
onInstallCancelled: function Installer_onInstallCancelled(aInstall) {
aInstall.removeListener(this);
this.checkAllInstalled();
},
onInstallFailed: function Installer_onInstallFailed(aInstall) {
aInstall.removeListener(this);
this.checkAllInstalled();
},
onInstallEnded: function Installer_onInstallEnded(aInstall) {
aInstall.removeListener(this);
this.installed.push(aInstall);
// If installing a theme that is disabled and can be enabled then enable it
if (aInstall.addon.type == "theme" &&
aInstall.addon.userDisabled == true &&
aInstall.addon.appDisabled == false) {
aInstall.addon.userDisabled = false;
}
this.checkAllInstalled();
}
};
function extWebInstallListener() {
}
extWebInstallListener.prototype = {
/**
* @see amIWebInstallListener.idl
*/
onWebInstallDisabled: function extWebInstallListener_onWebInstallDisabled(aBrowser, aUri, aInstalls) {
let info = {
browser: aBrowser,
originatingURI: aUri,
installs: aInstalls,
QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
};
Services.obs.notifyObservers(info, "addon-install-disabled", null);
},
/**
* @see amIWebInstallListener.idl
*/
onWebInstallOriginBlocked: function extWebInstallListener_onWebInstallOriginBlocked(aBrowser, aUri, aInstalls) {
let info = {
browser: aBrowser,
originatingURI: aUri,
installs: aInstalls,
install: function onWebInstallBlocked_install() {
},
QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
};
Services.obs.notifyObservers(info, "addon-install-origin-blocked", null);
return false;
},
/**
* @see amIWebInstallListener.idl
*/
onWebInstallBlocked: function extWebInstallListener_onWebInstallBlocked(aBrowser, aUri, aInstalls) {
let info = {
browser: aBrowser,
originatingURI: aUri,
installs: aInstalls,
install: function onWebInstallBlocked_install() {
new Installer(this.browser, this.originatingURI, this.installs);
},
QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
};
Services.obs.notifyObservers(info, "addon-install-blocked", null);
return false;
},
/**
* @see amIWebInstallListener.idl
*/
onWebInstallRequested: function extWebInstallListener_onWebInstallRequested(aBrowser, aUri, aInstalls) {
new Installer(aBrowser, aUri, aInstalls);
// We start the installs ourself
return false;
},
classDescription: "XPI Install Handler",
contractID: "@mozilla.org/addons/web-install-listener;1",
classID: Components.ID("{0f38e086-89a3-40a5-8ffc-9b694de1d04a}"),
QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallListener,
Ci.amIWebInstallListener2])
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([extWebInstallListener]);

View file

@ -0,0 +1,59 @@
-------------------------------------------------------
About The Cisco-Provided Binary of OpenH264 Video Codec
-------------------------------------------------------
Cisco provides this program under the terms of the BSD license.
Additionally, this binary is licensed under Cisco’s AVC/H.264 Patent Portfolio License from MPEG LA, at no cost to you, provided that the requirements and conditions shown below in the AVC/H.264 Patent Portfolio sections are met.
As with all AVC/H.264 codecs, you may also obtain your own patent license from MPEG LA or from the individual patent owners, or proceed at your own risk. Your rights from Cisco under the BSD license are not affected by this choice.
For more information on the OpenH264 binary licensing, please see the OpenH264 FAQ found at http://www.openh264.org/faq.html#binary
A corresponding source code to this binary program is available under the same BSD terms, which can be found at http://www.openh264.org
-----------
BSD License
-----------
Copyright © 2014 Cisco Systems, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-----------------------------------------
AVC/H.264 Patent Portfolio License Notice
-----------------------------------------
The binary form of this Software is distributed by Cisco under the AVC/H.264 Patent Portfolio License from MPEG LA, and is subject to the following requirements, which may or may not be applicable to your use of this software:
THIS PRODUCT IS LICENSED UNDER THE AVC PATENT PORTFOLIO LICENSE FOR THE PERSONAL USE OF A CONSUMER OR OTHER USES IN WHICH IT DOES NOT RECEIVE REMUNERATION TO (i) ENCODE VIDEO IN COMPLIANCE WITH THE AVC STANDARD (“AVC VIDEO”) AND/OR (ii) DECODE AVC VIDEO THAT WAS ENCODED BY A CONSUMER ENGAGED IN A PERSONAL ACTIVITY AND/OR WAS OBTAINED FROM A VIDEO PROVIDER LICENSED TO PROVIDE AVC VIDEO. NO LICENSE IS GRANTED OR SHALL BE IMPLIED FOR ANY OTHER USE. ADDITIONAL INFORMATION MAY BE OBTAINED FROM MPEG LA, L.L.C. SEE HTTP://WWW.MPEGLA.COM
Accordingly, please be advised that content providers and broadcasters using AVC/H.264 in their service may be required to obtain a separate use license from MPEG LA, referred to as "(b) sublicenses" in the SUMMARY OF AVC/H.264 LICENSE TERMS from MPEG LA found at http://www.openh264.org/mpegla
---------------------------------------------
AVC/H.264 Patent Portfolio License Conditions
---------------------------------------------
In addition, the Cisco-provided binary of this Software is licensed under Cisco's license from MPEG LA only if the following conditions are met:
1. The Cisco-provided binary is separately downloaded to an end user’s device, and not integrated into or combined with third party software prior to being downloaded to the end user’s device;
2. The end user must have the ability to control (e.g., to enable, disable, or re-enable) the use of the Cisco-provided binary;
3. Third party software, in the location where end users can control the use of the Cisco-provided binary, must display the following text:
"OpenH264 Video Codec provided by Cisco Systems, Inc."
4. Any third-party software that makes use of the Cisco-provided binary must reproduce all of the above text, as well as this last condition, in the EULA and/or in another location where licensing information is to be presented to the end user.
v1.0

View file

@ -0,0 +1,97 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
function init() {
var addon = window.arguments[0];
var extensionsStrings = document.getElementById("extensionsStrings");
document.documentElement.setAttribute("addontype", addon.type);
if (addon.iconURL) {
var extensionIcon = document.getElementById("extensionIcon");
extensionIcon.src = addon.iconURL;
}
document.title = extensionsStrings.getFormattedString("aboutWindowTitle", [addon.name]);
var extensionName = document.getElementById("extensionName");
extensionName.textContent = addon.name;
var extensionVersion = document.getElementById("extensionVersion");
if (addon.version)
extensionVersion.setAttribute("value", extensionsStrings.getFormattedString("aboutWindowVersionString", [addon.version]));
else
extensionVersion.hidden = true;
var extensionDescription = document.getElementById("extensionDescription");
if (addon.description)
extensionDescription.textContent = addon.description;
else
extensionDescription.hidden = true;
var numDetails = 0;
var extensionCreator = document.getElementById("extensionCreator");
if (addon.creator) {
extensionCreator.setAttribute("value", addon.creator);
numDetails++;
} else {
extensionCreator.hidden = true;
var extensionCreatorLabel = document.getElementById("extensionCreatorLabel");
extensionCreatorLabel.hidden = true;
}
var extensionHomepage = document.getElementById("extensionHomepage");
var homepageURL = addon.homepageURL;
if (homepageURL) {
extensionHomepage.setAttribute("homepageURL", homepageURL);
extensionHomepage.setAttribute("tooltiptext", homepageURL);
numDetails++;
} else {
extensionHomepage.hidden = true;
}
numDetails += appendToList("extensionDevelopers", "developersBox", addon.developers);
numDetails += appendToList("extensionTranslators", "translatorsBox", addon.translators);
numDetails += appendToList("extensionContributors", "contributorsBox", addon.contributors);
if (numDetails == 0) {
var groove = document.getElementById("groove");
groove.hidden = true;
var extensionDetailsBox = document.getElementById("extensionDetailsBox");
extensionDetailsBox.hidden = true;
}
var acceptButton = document.documentElement.getButton("accept");
acceptButton.label = extensionsStrings.getString("aboutWindowCloseButton");
setTimeout(sizeToContent, 0);
}
function appendToList(aHeaderId, aNodeId, aItems) {
var header = document.getElementById(aHeaderId);
var node = document.getElementById(aNodeId);
if (!aItems || aItems.length == 0) {
header.hidden = true;
return 0;
}
for (let currentItem of aItems) {
var label = document.createElement("label");
label.textContent = currentItem;
label.setAttribute("class", "contributor");
node.appendChild(label);
}
return aItems.length;
}
function loadHomepage(aEvent) {
window.close();
openURL(aEvent.target.getAttribute("homepageURL"));
}

View file

@ -0,0 +1,57 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/about.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://mozapps/locale/extensions/about.dtd">
<dialog id="genericAbout"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="init();"
buttons="accept"
buttoniconaccept="close"
onaccept="close();">
<script type="application/javascript" src="chrome://mozapps/content/extensions/about.js"/>
<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
<stringbundleset id="aboutSet">
<stringbundle id="extensionsStrings" src="chrome://mozapps/locale/extensions/extensions.properties"/>
</stringbundleset>
<vbox id="clientBox" flex="1">
<hbox class="basic-info">
<vbox pack="center">
<image id="extensionIcon"/>
</vbox>
<vbox flex="1">
<label id="extensionName"/>
<label id="extensionVersion" crop="end"/>
</vbox>
</hbox>
<description id="extensionDescription" class="boxIndent"/>
<separator id="groove" class="groove"/>
<vbox id="extensionDetailsBox" flex="1">
<label id="extensionCreatorLabel" class="sectionTitle">&creator.label;</label>
<hbox id="creatorBox" class="boxIndent">
<label id="extensionCreator" flex="1" crop="end"/>
<label id="extensionHomepage" onclick="if (event.button == 0) { loadHomepage(event); }"
class="text-link" value="&homepage.label;"/>
</hbox>
<label id="extensionDevelopers" class="sectionTitle">&developers.label;</label>
<vbox flex="1" id="developersBox" class="boxIndent"/>
<label id="extensionTranslators" class="sectionTitle">&translators.label;</label>
<vbox flex="1" id="translatorsBox" class="boxIndent"/>
<label id="extensionContributors" class="sectionTitle">&contributors.label;</label>
<vbox flex="1" id="contributorsBox" class="boxIndent"/>
</vbox>
</vbox>
</dialog>

View file

@ -0,0 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.hardBlockedAddon {
-moz-binding: url("chrome://mozapps/content/extensions/blocklist.xml#hardblockedaddon");
}
.softBlockedAddon {
-moz-binding: url("chrome://mozapps/content/extensions/blocklist.xml#softblockedaddon");
}

View file

@ -0,0 +1,72 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
Components.utils.import("resource://gre/modules/Services.jsm");
var gArgs;
function init() {
var hasHardBlocks = false;
var hasSoftBlocks = false;
gArgs = window.arguments[0].wrappedJSObject;
// NOTE: We use strings from the "updates.properties" bundleset to change the
// text on the "Cancel" button to "Restart Later". (bug 523784)
let bundle = Services.strings.
createBundle("chrome://mozapps/locale/update/updates.properties");
let cancelButton = document.documentElement.getButton("cancel");
cancelButton.setAttribute("label", bundle.GetStringFromName("restartLaterButton"));
cancelButton.setAttribute("accesskey",
bundle.GetStringFromName("restartLaterButton.accesskey"));
var richlist = document.getElementById("addonList");
var list = gArgs.list;
list.sort(function listSort(a, b) { return String.localeCompare(a.name, b.name); });
for (let listItem of list) {
let item = document.createElement("richlistitem");
item.setAttribute("name", listItem.name);
item.setAttribute("version", listItem.version);
item.setAttribute("icon", listItem.icon);
if (listItem.blocked) {
item.setAttribute("class", "hardBlockedAddon");
hasHardBlocks = true;
}
else {
item.setAttribute("class", "softBlockedAddon");
hasSoftBlocks = true;
}
richlist.appendChild(item);
}
if (hasHardBlocks && hasSoftBlocks)
document.getElementById("bothMessage").hidden = false;
else if (hasHardBlocks)
document.getElementById("hardBlockMessage").hidden = false;
else
document.getElementById("softBlockMessage").hidden = false;
var link = document.getElementById("moreInfo");
if (list.length == 1 && list[0].url) {
link.setAttribute("href", list[0].url);
}
else {
var url = Services.urlFormatter.formatURLPref("extensions.blocklist.detailsURL");
link.setAttribute("href", url);
}
}
function finish(shouldRestartNow) {
gArgs.restart = shouldRestartNow;
var list = gArgs.list;
var items = document.getElementById("addonList").childNodes;
for (let i = 0; i < list.length; i++) {
if (!list[i].blocked)
list[i].disable = items[i].checked;
}
return true;
}

View file

@ -0,0 +1,58 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE bindings [
<!ENTITY % blocklistDTD SYSTEM "chrome://mozapps/locale/extensions/blocklist.dtd" >
%blocklistDTD;
]>
<bindings id="blocklistBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="hardblockedaddon">
<content align="start">
<xul:image xbl:inherits="src=icon"/>
<xul:vbox flex="1">
<xul:hbox class="addon-name-version">
<xul:label class="addonName" crop="end" xbl:inherits="value=name"/>
<xul:label class="addonVersion" xbl:inherits="value=version"/>
</xul:hbox>
<xul:hbox>
<xul:spacer flex="1"/>
<xul:label class="blockedLabel" value="&blocklist.blocked.label;"/>
</xul:hbox>
</xul:vbox>
</content>
</binding>
<binding id="softblockedaddon">
<content align="start">
<xul:image xbl:inherits="src=icon"/>
<xul:vbox flex="1">
<xul:hbox class="addon-name-version">
<xul:label class="addonName" crop="end" xbl:inherits="value=name"/>
<xul:label class="addonVersion" xbl:inherits="value=version"/>
</xul:hbox>
<xul:hbox>
<xul:spacer flex="1"/>
<xul:checkbox class="disableCheckbox" checked="true" label="&blocklist.checkbox.label;"/>
</xul:hbox>
</xul:vbox>
</content>
<implementation>
<field name="_checkbox">
document.getAnonymousElementByAttribute(this, "class", "disableCheckbox")
</field>
<property name="checked" readonly="true">
<getter>
return this._checkbox.checked;
</getter>
</property>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,46 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/blocklist.css"?>
<?xml-stylesheet href="chrome://mozapps/content/extensions/blocklist.css"?>
<!DOCTYPE dialog [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/blocklist.dtd">
%extensionsDTD;
]>
<dialog windowtype="Addons:Blocklist" title="&blocklist.title;" align="stretch"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="init();" ondialogaccept="return finish(true)"
ondialogcancel="return finish(false)"
buttons="accept,cancel" style="&blocklist.style;"
buttonlabelaccept="&blocklist.accept.label;"
buttonaccesskeyaccept="&blocklist.accept.accesskey;">
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript" src="chrome://mozapps/content/extensions/blocklist.js"/>
<hbox align="stretch" flex="1">
<vbox pack="start">
<image class="error-icon"/>
</vbox>
<vbox flex="1">
<label>&blocklist.summary;</label>
<separator class="thin"/>
<richlistbox id="addonList" flex="1"/>
<separator class="thin"/>
<description id="bothMessage" hidden="true" class="bold">&blocklist.softandhard;</description>
<description id="hardBlockMessage" hidden="true" class="bold">&blocklist.hardblocked;</description>
<description id="softBlockMessage" hidden="true" class="bold">&blocklist.softblocked;</description>
<hbox pack="start">
<label id="moreInfo" class="text-link" value="&blocklist.moreinfo;"/>
</hbox>
</vbox>
</hbox>
</dialog>

View file

@ -0,0 +1,21 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
function Startup() {
var bundle = document.getElementById("extensionsStrings");
var addon = window.arguments[0].addon;
document.documentElement.setAttribute("addontype", addon.type);
if (addon.iconURL)
document.getElementById("icon").src = addon.iconURL;
var label = document.createTextNode(bundle.getFormattedString("eulaHeader", [addon.name]));
document.getElementById("heading").appendChild(label);
document.getElementById("eula").value = addon.eula;
}

View file

@ -0,0 +1,35 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/eula.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
%extensionsDTD;
]>
<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&eula.title;" width="&eula.width;" height="&eula.height;"
buttons="accept,cancel" buttonlabelaccept="&eula.accept;"
ondialogaccept="window.arguments[0].accepted = true"
onload="Startup();">
<script type="application/javascript" src="chrome://mozapps/content/extensions/eula.js"/>
<stringbundleset id="extensionsSet">
<stringbundle id="extensionsStrings" src="chrome://mozapps/locale/extensions/extensions.properties"/>
</stringbundleset>
<hbox id="heading-container">
<image id="icon"/>
<label id="heading" flex="1"/>
</hbox>
<textbox id="eula" multiline="true" readonly="true" flex="1"/>
</dialog>

View file

@ -0,0 +1,288 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@namespace xhtml "http://www.w3.org/1999/xhtml";
/* HTML link elements do weird things to the layout if they are not hidden */
xhtml|link {
display: none;
}
#categories {
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#categories-list");
}
.category {
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#category");
}
.sort-controls {
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#sorters");
}
.addon[status="installed"] {
-moz-box-orient: vertical;
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-generic");
}
.addon[status="installing"] {
-moz-box-orient: vertical;
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-installing");
}
.addon[pending="uninstall"] {
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-uninstalled");
}
.creator {
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#creator-link");
}
.translators {
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#translators-list");
}
.meta-rating {
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#rating");
}
.download-progress, .download-progress[mode="undetermined"] {
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#download-progress");
}
.install-status {
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#install-status");
}
.detail-row {
-moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#detail-row");
}
.text-list {
white-space: pre-line;
-moz-user-select: element;
}
setting, row[unsupported="true"] {
display: none;
}
setting[type="bool"] {
display: -moz-grid-line;
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-bool");
}
setting[type="bool"][localized="true"] {
display: -moz-grid-line;
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-localized-bool");
}
setting[type="bool"]:not([learnmore]) .preferences-learnmore {
visibility: collapse;
}
setting[type="boolint"] {
display: -moz-grid-line;
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-boolint");
}
setting[type="integer"] {
display: -moz-grid-line;
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-integer");
}
setting[type="integer"]:not([size]) textbox {
-moz-box-flex: 1;
}
setting[type="control"] {
display: -moz-grid-line;
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-control");
}
setting[type="string"] {
display: -moz-grid-line;
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-string");
}
setting[type="color"] {
display: -moz-grid-line;
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-color");
}
setting[type="file"],
setting[type="directory"] {
display: -moz-grid-line;
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-path");
}
setting[type="radio"],
setting[type="menulist"] {
display: -moz-grid-line;
-moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-multi");
}
#addonitem-popup > menuitem[disabled="true"] {
display: none;
}
#addonitem-popup[addontype="theme"] > #menuitem_enableItem,
#addonitem-popup[addontype="theme"] > #menuitem_disableItem,
#addonitem-popup:not([addontype="theme"]) > #menuitem_enableTheme,
#addonitem-popup:not([addontype="theme"]) > #menuitem_disableTheme {
display: none;
}
#header-searching:not([active]) {
visibility: hidden;
}
#search-list[local="false"] > .addon[remote="false"],
#search-list[remote="false"] > .addon[remote="true"] {
visibility: collapse;
}
#detail-view {
overflow: auto;
}
.addon:not([notification="warning"]) .warning,
.addon:not([notification="error"]) .error,
.addon:not([notification="info"]) .info,
.addon:not([pending]) .pending,
.addon:not([upgrade="true"]) .update-postfix,
.addon:not([native="true"]) .nativeAddon,
.addon:not([native="false"]) .compatAddon,
.addon[active="true"] .disabled-postfix,
.addon[pending="install"] .update-postfix,
.addon[pending="install"] .disabled-postfix,
#detail-view:not([notification="warning"]) .warning,
#detail-view:not([notification="error"]) .error,
#detail-view:not([notification="info"]) .info,
#detail-view:not([pending]) .pending,
#detail-view:not([upgrade="true"]) .update-postfix,
#detail-view[active="true"] .disabled-postfix,
#detail-view[loading] .detail-view-container,
#detail-view:not([loading]) .alert-container,
.detail-row:not([value]),
#search-list[remote="false"] #search-allresults-link {
display: none;
}
#addons-page:not([warning]) #list-view > .global-warning-container {
display: none;
}
#addon-list .date-updated {
display: none;
}
.view-pane:not(#updates-view) .addon .relnotes-toggle,
.view-pane:not(#updates-view) .addon .include-update,
#updates-view:not([updatetype="available"]) .addon .include-update,
#updates-view[updatetype="available"] .addon .update-available-notice {
display: none;
}
#addons-page:not([warning]) .global-warning,
#addons-page:not([warning="safemode"]) .global-warning-safemode,
#addons-page:not([warning="checkcompatibility"]) .global-warning-checkcompatibility,
#addons-page:not([warning="updatesecurity"]) .global-warning-updatesecurity {
display: none;
}
/* Plugins aren't yet disabled by safemode (bug 342333),
so don't show that warning when viewing plugins. */
#addons-page[warning="safemode"] .view-pane[type="plugin"] .global-warning-container,
#addons-page[warning="safemode"] #detail-view[loading="true"] .global-warning {
display: none;
}
#addons-page .view-pane:not([type="plugin"]) .plugin-info-container {
display: none;
}
#addons-page .view-pane:not([type="experiment"]) .experiment-info-container {
display: none;
}
.addon .relnotes {
-moz-user-select: text;
}
#detail-name, #detail-desc, #detail-fulldesc {
-moz-user-select: text;
}
/* Make sure we're not animating hidden images. See bug 623739. */
#view-port:not([selectedIndex="0"]) #discover-view .loading,
#discover-view:not([selectedIndex="0"]) .loading {
display: none;
}
/* Elements in unselected richlistitems cannot be focused */
richlistitem:not([selected]) * {
-moz-user-focus: ignore;
}
#header-search {
width: 22em;
}
#header-utils-btn {
-moz-user-focus: normal;
}
.discover-button[disabled="true"] {
display: none;
}
#experiments-learn-more[disabled="true"] {
display: none;
}
#experiments-change-telemetry[disabled="true"] {
display: none;
}
.view-pane[type="experiment"] .error,
.view-pane[type="experiment"] .warning,
.view-pane[type="experiment"] .addon:not([pending="uninstall"]) .pending,
.view-pane[type="experiment"] .disabled-postfix,
.view-pane[type="experiment"] .update-postfix,
.view-pane[type="experiment"] .version,
#detail-view[type="experiment"] .alert-container,
#detail-view[type="experiment"] #detail-version,
#detail-view[type="experiment"] #detail-creator {
display: none;
}
.view-pane:not([type="experiment"]) .experiment-container,
.view-pane:not([type="experiment"]) #detail-experiment-container {
display: none;
}
.addon[type="experiment"][status="installing"] .experiment-time,
.addon[type="experiment"][status="installing"] .experiment-state {
display: none;
}
/* Indicator style for extension target application */
.addon[native] .nativeIndicator {
margin-left: 5pt;
padding-bottom: 1pt;
}
.addon[native][active="false"] .nativeIndicator {
opacity: 0.4;
}
.addon[native] .nativeAddon {
color: #3366FF;
}
.addon[native] .compatAddon {
color: #FF6600;
}
/* Translators for Language Pack details */
.translators > label {
-moz-margin-start: 0px;
-moz-margin-end: 0px;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,689 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/content/extensions/extensions.css"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/extensions.css"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/about.css"?>
<!DOCTYPE page [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
%brandDTD;
<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
%extensionsDTD;
]>
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
id="addons-page" title="&addons.windowTitle;"
role="application" windowtype="Addons:Manager"
disablefastfind="true">
<xhtml:link rel="shortcut icon"
href="chrome://mozapps/skin/extensions/extensionGeneric-16.png"/>
<script type="application/javascript"
src="chrome://mozapps/content/extensions/extensions.js"/>
<script type="application/javascript"
src="chrome://global/content/contentAreaUtils.js"/>
<popupset>
<!-- menu for an addon item -->
<menupopup id="addonitem-popup">
<menuitem id="menuitem_showDetails" command="cmd_showItemDetails"
default="true" label="&cmd.showDetails.label;"
accesskey="&cmd.showDetails.accesskey;"/>
<menuitem id="menuitem_enableItem" command="cmd_enableItem"
label="&cmd.enableAddon.label;"
accesskey="&cmd.enableAddon.accesskey;"/>
<menuitem id="menuitem_disableItem" command="cmd_disableItem"
label="&cmd.disableAddon.label;"
accesskey="&cmd.disableAddon.accesskey;"/>
<menuitem id="menuitem_enableTheme" command="cmd_enableItem"
label="&cmd.enableTheme.label;"
accesskey="&cmd.enableTheme.accesskey;"/>
<menuitem id="menuitem_disableTheme" command="cmd_disableItem"
label="&cmd.disableTheme.label;"
accesskey="&cmd.disableTheme.accesskey;"/>
<menuitem id="menuitem_installItem" command="cmd_installItem"
label="&cmd.installAddon.label;"
accesskey="&cmd.installAddon.accesskey;"/>
<menuitem id="menuitem_uninstallItem" command="cmd_uninstallItem"
label="&cmd.uninstallAddon.label;"
accesskey="&cmd.uninstallAddon.accesskey;"/>
<menuitem id="menuitem_debugItem" command="cmd_debugItem"
label="&cmd.debugAddon.label;"/>
<menuseparator id="addonitem-menuseparator" />
<menuitem id="menuitem_preferences" command="cmd_showItemPreferences"
#ifdef XP_WIN
label="&cmd.preferencesWin.label;"
accesskey="&cmd.preferencesWin.accesskey;"/>
#else
label="&cmd.preferencesUnix.label;"
accesskey="&cmd.preferencesUnix.accesskey;"/>
#endif
<menuitem id="menuitem_findUpdates" command="cmd_findItemUpdates"
label="&cmd.findUpdates.label;"
accesskey="&cmd.findUpdates.accesskey;"/>
<menuitem id="menuitem_about" command="cmd_showItemAbout"
label="&cmd.about.label;"
accesskey="&cmd.about.accesskey;"/>
</menupopup>
</popupset>
<!-- global commands - these act on all addons, or affect the addons manager
in some other way -->
<commandset id="globalCommandSet">
<command id="cmd_focusSearch"/>
<command id="cmd_findAllUpdates"/>
<command id="cmd_restartApp"/>
<command id="cmd_goToDiscoverPane"/>
<command id="cmd_goToRecentUpdates"/>
<command id="cmd_goToAvailableUpdates"/>
<command id="cmd_installFromFile"/>
<command id="cmd_back"/>
<command id="cmd_forward"/>
<command id="cmd_enableCheckCompatibility"/>
<!-- <command id="cmd_pluginCheck"/> -->
<command id="cmd_enableUpdateSecurity"/>
<command id="cmd_toggleAutoUpdateDefault"/>
<command id="cmd_resetAddonAutoUpdate"/>
<command id="cmd_experimentsLearnMore"/>
<command id="cmd_experimentsOpenTelemetryPreferences"/>
</commandset>
<!-- view commands - these act on the selected addon -->
<commandset id="viewCommandSet"
events="richlistbox-select" commandupdater="true">
<command id="cmd_showItemDetails"/>
<command id="cmd_findItemUpdates"/>
<command id="cmd_showItemPreferences"/>
<command id="cmd_showItemAbout"/>
<command id="cmd_debugItem"/>
<command id="cmd_enableItem"/>
<command id="cmd_disableItem"/>
<command id="cmd_installItem"/>
<command id="cmd_purchaseItem"/>
<command id="cmd_uninstallItem"/>
<command id="cmd_cancelUninstallItem"/>
<command id="cmd_cancelOperation"/>
<command id="cmd_contribute"/>
<command id="cmd_askToActivateItem"/>
<command id="cmd_alwaysActivateItem"/>
<command id="cmd_neverActivateItem"/>
</commandset>
<keyset>
<!-- XXXunf Disabled until bug 371900 is fixed. -->
<key id="focusSearch" key="&search.commandkey;" modifiers="accel"
disabled="true"/>
</keyset>
<!-- main header -->
<hbox id="header" align="center">
<toolbarbutton id="back-btn" class="nav-button header-button" command="cmd_back"
tooltiptext="&cmd.back.tooltip;" hidden="true" disabled="true"/>
<toolbarbutton id="forward-btn" class="nav-button header-button" command="cmd_forward"
tooltiptext="&cmd.forward.tooltip;" hidden="true" disabled="true"/>
<spacer flex="1"/>
<hbox id="updates-container" align="center">
<image class="spinner"/>
<label id="updates-noneFound" hidden="true"
value="&updates.noneFound.label;"/>
<button id="updates-manualUpdatesFound-btn" class="button-link"
hidden="true" label="&updates.manualUpdatesFound.label;"
command="cmd_goToAvailableUpdates"/>
<label id="updates-progress" hidden="true"
value="&updates.updating.label;"/>
<label id="updates-installed" hidden="true"
value="&updates.installed.label;"/>
<label id="updates-downloaded" hidden="true"
value="&updates.downloaded.label;"/>
<button id="updates-restart-btn" class="button-link" hidden="true"
label="&updates.restart.label;"
command="cmd_restartApp"/>
</hbox>
<toolbarbutton id="header-utils-btn" class="header-button" type="menu"
tooltiptext="&toolsMenu.tooltip;">
<menupopup id="utils-menu">
<menuitem id="utils-updateNow"
label="&updates.checkForUpdates.label;"
accesskey="&updates.checkForUpdates.accesskey;"
command="cmd_findAllUpdates"/>
<menuitem id="utils-viewUpdates"
label="&updates.viewUpdates.label;"
accesskey="&updates.viewUpdates.accesskey;"
command="cmd_goToRecentUpdates"/>
<menuseparator id="utils-installFromFile-separator"/>
<menuitem id="utils-installFromFile"
label="&installAddonFromFile.label;"
accesskey="&installAddonFromFile.accesskey;"
command="cmd_installFromFile"/>
<menuseparator/>
<menuitem id="utils-autoUpdateDefault"
label="&updates.updateAddonsAutomatically.label;"
accesskey="&updates.updateAddonsAutomatically.accesskey;"
type="checkbox" autocheck="false"
command="cmd_toggleAutoUpdateDefault"/>
<menuitem id="utils-resetAddonUpdatesToAutomatic"
label="&updates.resetUpdatesToAutomatic.label;"
accesskey="&updates.resetUpdatesToAutomatic.accesskey;"
command="cmd_resetAddonAutoUpdate"/>
<menuitem id="utils-resetAddonUpdatesToManual"
label="&updates.resetUpdatesToManual.label;"
accesskey="&updates.resetUpdatesToManual.accesskey;"
command="cmd_resetAddonAutoUpdate"/>
</menupopup>
</toolbarbutton>
<textbox id="header-search" type="search" searchbutton="true"
searchbuttonlabel="&search.buttonlabel;"
placeholder="&search.placeholder;"/>
</hbox>
<hbox flex="1">
<!-- category list -->
<richlistbox id="categories">
<richlistitem id="category-search" value="addons://search/"
class="category"
name="&view.search.label;" priority="0"
tooltiptext="&view.search.label;" disabled="true"/>
<richlistitem id="category-discover" value="addons://discover/"
class="category"
name="&view.discover.label;" priority="1000"
tooltiptext="&view.discover.label;"/>
<richlistitem id="category-availableUpdates" value="addons://updates/available"
class="category"
name="&view.availableUpdates.label;" priority="100000"
tooltiptext="&view.availableUpdates.label;"
disabled="true"/>
<richlistitem id="category-recentUpdates" value="addons://updates/recent"
class="category"
name="&view.recentUpdates.label;" priority="101000"
tooltiptext="&view.recentUpdates.label;" disabled="true"/>
</richlistbox>
<box id="view-port-container" class="main-content" flex="1">
<!-- view port -->
<deck id="view-port" flex="1" selectedIndex="0">
<!-- discover view -->
<deck id="discover-view" flex="1" class="view-pane" selectedIndex="0" tabindex="0">
<vbox id="discover-loading" align="center" pack="stretch" flex="1" class="alert-container">
<spacer class="alert-spacer-before"/>
<hbox class="alert loading" align="center">
<image/>
<label value="&loading.label;"/>
</hbox>
<spacer class="alert-spacer-after"/>
</vbox>
<vbox id="discover-error" align="center" pack="stretch" flex="1" class="alert-container">
<spacer class="alert-spacer-before"/>
<hbox>
<spacer class="discover-spacer-before"/>
<hbox class="alert" align="center">
<image class="discover-logo"/>
<vbox flex="1" align="stretch">
<label class="discover-title">&discover.title;</label>
<description class="discover-description">&discover.description2;</description>
<description class="discover-footer">&discover.footer;</description>
</vbox>
</hbox>
<spacer class="discover-spacer-after"/>
</hbox>
<spacer class="alert-spacer-after"/>
</vbox>
<browser id="discover-browser" type="content" flex="1"
disablehistory="true" homepage="about:blank"/>
</deck>
<!-- search view -->
<vbox id="search-view" flex="1" class="view-pane" tabindex="0">
<hbox class="view-header global-warning-container" align="center">
<!-- global warnings -->
<hbox class="global-warning" flex="1">
<hbox class="global-warning-safemode" flex="1" align="center"
tooltiptext="&warning.safemode.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.safemode.label;"/>
</hbox>
<hbox class="global-warning-checkcompatibility" flex="1" align="center"
tooltiptext="&warning.checkcompatibility.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.checkcompatibility.label;"/>
</hbox>
<button class="button-link global-warning-checkcompatibility"
label="&warning.checkcompatibility.enable.label;"
tooltiptext="&warning.checkcompatibility.enable.tooltip;"
command="cmd_enableCheckCompatibility"/>
<hbox class="global-warning-updatesecurity" flex="1" align="center"
tooltiptext="&warning.updatesecurity.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.updatesecurity.label;"/>
</hbox>
<button class="button-link global-warning-updatesecurity"
label="&warning.updatesecurity.enable.label;"
tooltiptext="&warning.updatesecurity.enable.tooltip;"
command="cmd_enableUpdateSecurity"/>
<spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
</hbox>
<spacer flex="1"/>
<hbox id="search-sorters" class="sort-controls"
showrelevance="true" sortby="relevancescore" ascending="false"/>
</hbox>
<hbox id="search-filter" align="center">
<label id="search-filter-label" value="&search.filter2.label;"/>
<radiogroup id="search-filter-radiogroup" orient="horizontal"
align="center" persist="value" value="remote">
<radio id="search-filter-local" class="search-filter-radio"
label="&search.filter2.installed.label;" value="local"
tooltiptext="&search.filter2.installed.tooltip;"/>
<radio id="search-filter-remote" class="search-filter-radio"
label="&search.filter2.available.label;" value="remote"
tooltiptext="&search.filter2.available.tooltip;"/>
</radiogroup>
</hbox>
<vbox id="search-loading" class="alert-container"
flex="1" hidden="true">
<spacer class="alert-spacer-before"/>
<hbox class="alert loading" align="center">
<image/>
<label value="&loading.label;"/>
</hbox>
<spacer class="alert-spacer-after"/>
</vbox>
<vbox id="search-list-empty" class="alert-container"
flex="1" hidden="true">
<spacer class="alert-spacer-before"/>
<vbox class="alert">
<label value="&listEmpty.search.label;"/>
<button class="discover-button"
id="discover-button-search"
label="&listEmpty.button.label;"
command="cmd_goToDiscoverPane"/>
</vbox>
<spacer class="alert-spacer-after"/>
</vbox>
<richlistbox id="search-list" class="list" flex="1">
<hbox pack="center">
<label id="search-allresults-link" class="text-link"/>
</hbox>
</richlistbox>
</vbox>
<!-- list view -->
<vbox id="list-view" flex="1" class="view-pane" align="stretch" tabindex="0">
<hbox class="view-header global-warning-container">
<!-- global warnings -->
<hbox class="global-warning" flex="1">
<hbox class="global-warning-safemode" flex="1" align="center"
tooltiptext="&warning.safemode.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.safemode.label;"/>
</hbox>
<hbox class="global-warning-checkcompatibility" flex="1" align="center"
tooltiptext="&warning.checkcompatibility.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.checkcompatibility.label;"/>
</hbox>
<button class="button-link global-warning-checkcompatibility"
label="&warning.checkcompatibility.enable.label;"
tooltiptext="&warning.checkcompatibility.enable.tooltip;"
command="cmd_enableCheckCompatibility"/>
<hbox class="global-warning-updatesecurity" flex="1" align="center"
tooltiptext="&warning.updatesecurity.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.updatesecurity.label;"/>
</hbox>
<button class="button-link global-warning-updatesecurity"
label="&warning.updatesecurity.enable.label;"
tooltiptext="&warning.updatesecurity.enable.tooltip;"
command="cmd_enableUpdateSecurity"/>
<spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
</hbox>
</hbox>
<!-- <hbox class="view-header global-info-container plugin-info-container">
<hbox class="global-info" flex="1" align="center">
<button class="button-link global-info-plugincheck"
label="&info.plugincheck.label;"
tooltiptext="&info.plugincheck.tooltip;"
command="cmd_pluginCheck"/>
<spacer flex="5000"/>
</hbox>
</hbox> -->
<hbox class="view-header global-info-container experiment-info-container">
<hbox class="global-info" flex="1" align="center">
<label value="&experiment.info.label;"/>
<button id="experiments-learn-more"
label="&experiment.info.learnmore;"
tooltiptext="&experiment.info.learnmore;"
accesskey="&experiment.info.learnmore.accesskey;"
command="cmd_experimentsLearnMore"/>
<button id="experiments-change-telemetry"
label="&experiment.info.changetelemetry;"
tooltiptext="&experiment.info.changetelemetry;"
accesskey="&experiment.info.changetelemetry.accesskey;"
command="cmd_experimentsOpenTelemetryPreferences"/>
<spacer flex="5000"/> <!-- Necessary to allow the message to wrap. -->
</hbox>
</hbox>
<vbox id="addon-list-empty" class="alert-container"
flex="1" hidden="true">
<spacer class="alert-spacer-before"/>
<vbox class="alert">
<label value="&listEmpty.installed.label;"/>
<button class="discover-button"
id="discover-button-install"
label="&listEmpty.button.label;"
command="cmd_goToDiscoverPane"/>
</vbox>
<spacer class="alert-spacer-after"/>
</vbox>
<richlistbox id="addon-list" class="list" flex="1"/>
</vbox>
<!-- updates view -->
<vbox id="updates-view" flex="1" class="view-pane" tabindex="0">
<hbox class="view-header global-warning-container" align="center">
<!-- global warnings -->
<hbox class="global-warning" flex="1">
<hbox class="global-warning-safemode" flex="1" align="center"
tooltiptext="&warning.safemode.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.safemode.label;"/>
</hbox>
<hbox class="global-warning-checkcompatibility" flex="1" align="center"
tooltiptext="&warning.checkcompatibility.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.checkcompatibility.label;"/>
</hbox>
<button class="button-link global-warning-checkcompatibility"
label="&warning.checkcompatibility.enable.label;"
tooltiptext="&warning.checkcompatibility.enable.tooltip;"
command="cmd_enableCheckCompatibility"/>
<hbox class="global-warning-updatesecurity" flex="1" align="center"
tooltiptext="&warning.updatesecurity.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.updatesecurity.label;"/>
</hbox>
<button class="button-link global-warning-updatesecurity"
label="&warning.updatesecurity.enable.label;"
tooltiptext="&warning.updatesecurity.enable.tooltip;"
command="cmd_enableUpdateSecurity"/>
<spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
</hbox>
<spacer flex="1"/>
<hbox id="updates-sorters" class="sort-controls" sortby="updateDate"
ascending="false"/>
</hbox>
<vbox id="updates-list-empty" class="alert-container"
flex="1" hidden="true">
<spacer class="alert-spacer-before"/>
<vbox class="alert">
<label id="empty-availableUpdates-msg" value="&listEmpty.availableUpdates.label;"/>
<label id="empty-recentUpdates-msg" value="&listEmpty.recentUpdates.label;"/>
<button label="&listEmpty.findUpdates.label;"
command="cmd_findAllUpdates"/>
</vbox>
<spacer class="alert-spacer-after"/>
</vbox>
<hbox id="update-actions" pack="center">
<button id="update-selected-btn" hidden="true"
label="&updates.updateSelected.label;"
tooltiptext="&updates.updateSelected.tooltip;"/>
</hbox>
<richlistbox id="updates-list" class="list" flex="1"/>
</vbox>
<!-- detail view -->
<scrollbox id="detail-view" flex="1" class="view-pane addon-view" orient="vertical" tabindex="0"
role="document">
<!-- global warnings -->
<hbox class="global-warning-container global-warning">
<hbox class="global-warning-safemode" flex="1" align="center"
tooltiptext="&warning.safemode.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.safemode.label;"/>
</hbox>
<hbox class="global-warning-checkcompatibility" flex="1" align="center"
tooltiptext="&warning.checkcompatibility.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.checkcompatibility.label;"/>
</hbox>
<button class="button-link global-warning-checkcompatibility"
label="&warning.checkcompatibility.enable.label;"
tooltiptext="&warning.checkcompatibility.enable.tooltip;"
command="cmd_enableCheckCompatibility"/>
<hbox class="global-warning-updatesecurity" flex="1" align="center"
tooltiptext="&warning.updatesecurity.label;">
<image class="warning-icon"/>
<label class="global-warning-text" flex="1" crop="end"
value="&warning.updatesecurity.label;"/>
</hbox>
<button class="button-link global-warning-updatesecurity"
label="&warning.updatesecurity.enable.label;"
tooltiptext="&warning.updatesecurity.enable.tooltip;"
command="cmd_enableUpdateSecurity"/>
<spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
</hbox>
<hbox flex="1">
<spacer flex="1"/>
<!-- "loading" splash screen -->
<vbox class="alert-container">
<spacer class="alert-spacer-before"/>
<hbox class="alert loading">
<image/>
<label value="&loading.label;"/>
</hbox>
<spacer class="alert-spacer-after"/>
</vbox>
<!-- actual detail view -->
<vbox class="detail-view-container" flex="3" contextmenu="addonitem-popup">
<vbox id="detail-notifications">
<hbox id="warning-container" align="center" class="warning">
<image class="warning-icon"/>
<label id="detail-warning" flex="1"/>
<label id="detail-warning-link" class="text-link"/>
<spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
</hbox>
<hbox id="error-container" align="center" class="error">
<image class="error-icon"/>
<label id="detail-error" flex="1"/>
<label id="detail-error-link" class="text-link"/>
<spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
</hbox>
<hbox id="pending-container" align="center" class="pending">
<image class="pending-icon"/>
<label id="detail-pending" flex="1"/>
<button id="detail-restart-btn" class="button-link"
label="&addon.restartNow.label;"
command="cmd_restartApp"/>
<button id="detail-undo-btn" class="button-link"
label="&addon.undoAction.label;"
tooltipText="&addon.undoAction.tooltip;"
command="cmd_cancelOperation"/>
<spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
</hbox>
</vbox>
<hbox align="start">
<vbox id="detail-icon-container" align="end">
<image id="detail-icon" class="icon"/>
</vbox>
<vbox flex="1">
<vbox id="detail-summary">
<hbox id="detail-name-container" class="name-container"
align="start">
<label id="detail-name" flex="1"/>
<label id="detail-version"/>
<label class="disabled-postfix" value="&addon.disabled.postfix;"/>
<label class="update-postfix" value="&addon.update.postfix;"/>
<spacer flex="5000"/> <!-- Necessary to allow the name to wrap -->
</hbox>
<label id="detail-creator" class="creator"/>
<label id="detail-translators" class="translators"/>
</vbox>
<hbox id="detail-experiment-container">
<svg width="8" height="8" viewBox="0 0 8 8" version="1.1"
xmlns="http://www.w3.org/2000/svg"
id="detail-experiment-bullet-container">
<circle cx="4" cy="4" r="4" id="detail-experiment-bullet"/>
</svg>
<label id="detail-experiment-state"/>
<label id="detail-experiment-time"/>
</hbox>
<hbox id="detail-desc-container" align="start">
<vbox pack="center"> <!-- Necessary to work around bug 394738 -->
<image id="detail-screenshot" hidden="true"/>
</vbox>
<vbox flex="1">
<description id="detail-desc"/>
<description id="detail-fulldesc"/>
</vbox>
</hbox>
<vbox id="detail-contributions">
<description id="detail-contrib-description">
&detail.contributions.description;
</description>
<hbox align="center">
<label id="detail-contrib-suggested"/>
<spacer flex="1"/>
<button id="detail-contrib-btn"
label="&cmd.contribute.label;"
accesskey="&cmd.contribute.accesskey;"
tooltiptext="&cmd.contribute.tooltip;"
command="cmd_contribute"/>
</hbox>
</vbox>
<grid id="detail-grid">
<columns>
<column flex="1"/>
<column flex="2"/>
</columns>
<rows id="detail-rows">
<row class="detail-row-complex" id="detail-updates-row">
<label class="detail-row-label" value="&detail.updateType;"/>
<hbox align="center">
<radiogroup id="detail-autoUpdate" orient="horizontal">
<!-- The values here need to match the values of
AddonManager.AUTOUPDATE_* -->
<radio label="&detail.updateDefault.label;"
tooltiptext="&detail.updateDefault.tooltip;"
value="1"/>
<radio label="&detail.updateAutomatic.label;"
tooltiptext="&detail.updateAutomatic.tooltip;"
value="2"/>
<radio label="&detail.updateManual.label;"
tooltiptext="&detail.updateManual.tooltip;"
value="0"/>
</radiogroup>
<button id="detail-findUpdates-btn" class="button-link"
label="&detail.checkForUpdates.label;"
accesskey="&detail.checkForUpdates.accesskey;"
tooltiptext="&detail.checkForUpdates.tooltip;"
command="cmd_findItemUpdates"/>
</hbox>
</row>
<row class="detail-row" id="detail-dateUpdated" label="&detail.lastupdated.label;"/>
<row class="detail-row-complex" id="detail-homepage-row" label="&detail.home;">
<label class="detail-row-label" value="&detail.home;"/>
<label id="detail-homepage" class="detail-row-value text-link" crop="end"/>
</row>
<row class="detail-row-complex" id="detail-repository-row" label="&detail.repository;">
<label class="detail-row-label" value="&detail.repository;"/>
<label id="detail-repository" class="detail-row-value text-link"/>
</row>
<row class="detail-row" id="detail-size" label="&detail.size;"/>
<row class="detail-row-complex" id="detail-rating-row">
<label class="detail-row-label" value="&rating2.label;"/>
<hbox>
<label id="detail-rating" class="meta-value meta-rating"
showrating="average"/>
<label id="detail-reviews" class="text-link"/>
</hbox>
</row>
<row class="detail-row" id="detail-downloads" label="&detail.numberOfDownloads.label;"/>
</rows>
</grid>
<hbox id="detail-controls">
<button id="detail-prefs-btn" class="addon-control preferences"
#ifdef XP_WIN
label="&detail.showPreferencesWin.label;"
accesskey="&detail.showPreferencesWin.accesskey;"
tooltiptext="&detail.showPreferencesWin.tooltip;"
#else
label="&detail.showPreferencesUnix.label;"
accesskey="&detail.showPreferencesUnix.accesskey;"
tooltiptext="&detail.showPreferencesUnix.tooltip;"
#endif
command="cmd_showItemPreferences"/>
<spacer flex="1"/>
<button id="detail-debug-btn" class="addon-control debug"
label="Debug"
command="cmd_debugItem" />
<button id="detail-enable-btn" class="addon-control enable"
label="&cmd.enableAddon.label;"
accesskey="&cmd.enableAddon.accesskey;"
command="cmd_enableItem"/>
<button id="detail-disable-btn" class="addon-control disable"
label="&cmd.disableAddon.label;"
accesskey="&cmd.disableAddon.accesskey;"
command="cmd_disableItem"/>
<button id="detail-uninstall-btn" class="addon-control remove"
label="&cmd.uninstallAddon.label;"
accesskey="&cmd.uninstallAddon.accesskey;"
command="cmd_uninstallItem"/>
<button id="detail-purchase-btn" class="addon-control purchase"
command="cmd_purchaseItem"/>
<button id="detail-install-btn" class="addon-control install"
label="&cmd.installAddon.label;"
accesskey="&cmd.installAddon.accesskey;"
command="cmd_installItem"/>
<menulist id="detail-state-menulist"
crop="none" sizetopopup="always"
tooltiptext="&cmd.stateMenu.tooltip;">
<menupopup>
<menuitem id="detail-ask-to-activate-menuitem"
class="addon-control"
label="&cmd.askToActivate.label;"
tooltiptext="&cmd.askToActivate.tooltip;"
command="cmd_askToActivateItem"/>
<menuitem id="detail-always-activate-menuitem"
class="addon-control"
label="&cmd.alwaysActivate.label;"
tooltiptext="&cmd.alwaysActivate.tooltip;"
command="cmd_alwaysActivateItem"/>
<menuitem id="detail-never-activate-menuitem"
class="addon-control"
label="&cmd.neverActivate.label;"
tooltiptext="&cmd.neverActivate.tooltip;"
command="cmd_neverActivateItem"/>
</menupopup>
</menulist>
</hbox>
</vbox>
</hbox>
</vbox>
<spacer flex="1"/>
</hbox>
</scrollbox>
</deck>
</box>
</hbox>
</page>

View file

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- This is intentionally empty and a dummy to let the GMPProvider
have a preferences button in the list view. -->

View file

@ -0,0 +1,165 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const kDialog = "dialog";
/**
* This dialog can be initialized from parameters supplied via window.arguments
* or it can be used to display blocklist notification and blocklist blocked
* installs via nsIDialogParamBlock as is done by nsIExtensionManager.
*
* When using this dialog with window.arguments it must be opened modally, the
* caller can inspect the user action after the dialog closes by inspecting the
* value of the |result| parameter on this object which is set to the dlgtype
* of the button used to close the dialog.
*
* window.arguments[0] is an array of strings to display in the tree. If the
* array is empty the tree will not be displayed.
* window.arguments[1] a JS Object with the following properties:
*
* title: A title string, to be displayed in the title bar of the dialog.
* message1: A message string, displayed above the addon list
* message2: A message string, displayed below the addon list
* message3: A bolded message string, displayed below the addon list
* moreInfoURL: An url for displaying more information
* iconClass : Can be one of the following values (default is alert-icon)
* alert-icon, error-icon, or question-icon
*
* If no value is supplied for message1, message2, message3, or moreInfoURL,
* the element is not displayed.
*
* buttons: {
* accept: { label: "A Label for the Accept button",
* focused: true },
* cancel: { label: "A Label for the Cancel button" },
* ...
* },
*
* result: The dlgtype of button that was used to dismiss the dialog.
*/
"use strict";
var gButtons = { };
function init() {
var de = document.documentElement;
var items = [];
if (window.arguments[0] instanceof Components.interfaces.nsIDialogParamBlock) {
// This is a warning about a blocklisted item the user is trying to install
var args = window.arguments[0];
var softblocked = args.GetInt(0) == 1 ? true : false;
var extensionsBundle = document.getElementById("extensionsBundle");
try {
var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
.getService(Components.interfaces.nsIURLFormatter);
var url = formatter.formatURLPref("extensions.blocklist.detailsURL");
}
catch (e) { }
var params = {
moreInfoURL: url,
};
if (softblocked) {
params.title = extensionsBundle.getString("softBlockedInstallTitle");
params.message1 = extensionsBundle.getFormattedString("softBlockedInstallMsg",
[args.GetString(0)]);
var accept = de.getButton("accept");
accept.label = extensionsBundle.getString("softBlockedInstallAcceptLabel");
accept.accessKey = extensionsBundle.getString("softBlockedInstallAcceptKey");
de.getButton("cancel").focus();
document.addEventListener("dialogaccept", allowInstall, false);
}
else {
params.title = extensionsBundle.getString("blocklistedInstallTitle2");
params.message1 = extensionsBundle.getFormattedString("blocklistedInstallMsg2",
[args.GetString(0)]);
de.buttons = "accept";
de.getButton("accept").focus();
}
}
else {
items = window.arguments[0];
params = window.arguments[1];
}
var addons = document.getElementById("addonsChildren");
if (items.length > 0)
document.getElementById("addonsTree").hidden = false;
// Fill the addons list
for (var item of items) {
var treeitem = document.createElementNS(kXULNS, "treeitem");
var treerow = document.createElementNS(kXULNS, "treerow");
var treecell = document.createElementNS(kXULNS, "treecell");
treecell.setAttribute("label", item);
treerow.appendChild(treecell);
treeitem.appendChild(treerow);
addons.appendChild(treeitem);
}
// Set the messages
var messages = ["message1", "message2", "message3"];
for (let messageEntry of messages) {
if (messageEntry in params) {
var message = document.getElementById(messageEntry);
message.hidden = false;
message.appendChild(document.createTextNode(params[messageEntry]));
}
}
document.getElementById("infoIcon").className =
params["iconClass"] ? "spaced " + params["iconClass"] : "spaced alert-icon";
if ("moreInfoURL" in params && params["moreInfoURL"]) {
message = document.getElementById("moreInfo");
message.value = extensionsBundle.getString("moreInfoText");
message.setAttribute("href", params["moreInfoURL"]);
document.getElementById("moreInfoBox").hidden = false;
}
// Set the window title
if ("title" in params)
document.title = params.title;
// Set up the buttons
if ("buttons" in params) {
gButtons = params.buttons;
var buttonString = "";
for (var buttonType in gButtons)
buttonString += "," + buttonType;
de.buttons = buttonString.substr(1);
for (buttonType in gButtons) {
var button = de.getButton(buttonType);
button.label = gButtons[buttonType].label;
if (gButtons[buttonType].focused)
button.focus();
document.addEventListener(kDialog + buttonType, handleButtonCommand, true);
}
}
}
function shutdown() {
for (var buttonType in gButtons)
document.removeEventListener(kDialog + buttonType, handleButtonCommand, true);
}
function allowInstall() {
var args = window.arguments[0];
args.SetInt(1, 1);
}
/**
* Watch for the user hitting one of the buttons to dismiss the dialog
* and report the result back to the caller through the |result| property on
* the arguments object.
*/
function handleButtonCommand(event) {
window.arguments[1].result = event.type.substr(kDialog.length);
}

View file

@ -0,0 +1,44 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/"?>
<dialog id="addonList" windowtype="Addons:List"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onunload="shutdown();"
buttons="accept,cancel" onload="init();">
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript"
src="chrome://mozapps/content/extensions/list.js"/>
<stringbundle id="extensionsBundle"
src="chrome://mozapps/locale/extensions/extensions.properties"/>
<stringbundle id="brandBundle"
src="chrome://branding/locale/brand.properties"/>
<hbox align="start">
<vbox>
<image id="infoIcon"/>
</vbox>
<vbox class="spaced" style="min-width: 20em; max-width: 40em">
<label id="message1" class="spaced" hidden="true"/>
<separator class="thin"/>
<tree id="addonsTree" rows="6" hidecolumnpicker="true" hidden="true" class="spaced">
<treecols style="max-width: 25em;">
<treecol flex="1" id="nameColumn" hideheader="true"/>
</treecols>
<treechildren id="addonsChildren"/>
</tree>
<label id="message2" class="spaced" hidden="true"/>
<label class="bold spaced" id="message3" hidden="true"/>
<hbox id="moreInfoBox" hidden="true">
<label id="moreInfo" class="text-link spaced"/>
<spacer flex="1"/>
</hbox>
</vbox>
</hbox>
</dialog>

View file

@ -0,0 +1,129 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
var gAddon = null;
// If the user enables the add-on through some other UI close this window
var EnableListener = {
onEnabling: function EnableListener_onEnabling(aAddon) {
if (aAddon.id == gAddon.id)
window.close();
}
}
AddonManager.addAddonListener(EnableListener);
function initialize() {
// About URIs don't implement nsIURL so we have to find the query string
// manually
let spec = document.location.href;
let pos = spec.indexOf("?");
let query = "";
if (pos >= 0)
query = spec.substring(pos + 1);
// Just assume the query is "id=<id>"
let id = query.substring(3);
if (!id) {
window.location = "about:blank";
return;
}
let bundle = Services.strings.createBundle("chrome://mozapps/locale/extensions/newaddon.properties");
AddonManager.getAddonByID(id, function initialize_getAddonByID(aAddon) {
// If the add-on doesn't exist or it is already enabled or it cannot be
// enabled then this UI is useless, just close it. This shouldn't normally
// happen unless session restore restores the tab
if (!aAddon || !aAddon.userDisabled ||
!(aAddon.permissions & AddonManager.PERM_CAN_ENABLE)) {
window.close();
return;
}
gAddon = aAddon;
document.getElementById("addon-info").setAttribute("type", aAddon.type);
let icon = document.getElementById("icon");
if (aAddon.icon64URL)
icon.src = aAddon.icon64URL;
else if (aAddon.iconURL)
icon.src = aAddon.iconURL;
let name = bundle.formatStringFromName("name", [aAddon.name, aAddon.version],
2);
document.getElementById("name").value = name;
if (aAddon.creator) {
let creator = bundle.formatStringFromName("author", [aAddon.creator], 1);
document.getElementById("author").value = creator;
} else {
document.getElementById("author").hidden = true;
}
let uri = "getResourceURI" in aAddon ? aAddon.getResourceURI() : null;
let locationLabel = document.getElementById("location");
if (uri instanceof Ci.nsIFileURL) {
let location = bundle.formatStringFromName("location", [uri.file.path], 1);
locationLabel.value = location;
locationLabel.setAttribute("tooltiptext", location);
} else {
document.getElementById("location").hidden = true;
}
var event = document.createEvent("Events");
event.initEvent("AddonDisplayed", true, true);
document.dispatchEvent(event);
});
}
function unload() {
AddonManager.removeAddonListener(EnableListener);
}
function continueClicked() {
AddonManager.removeAddonListener(EnableListener);
if (document.getElementById("allow").checked) {
gAddon.userDisabled = false;
if (gAddon.pendingOperations & AddonManager.PENDING_ENABLE) {
document.getElementById("allow").disabled = true;
document.getElementById("buttonDeck").selectedPanel = document.getElementById("restartPanel");
return;
}
}
window.close();
}
function restartClicked() {
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
createInstance(Ci.nsISupportsPRBool);
Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
"restart");
if (cancelQuit.data)
return; // somebody canceled our quit request
window.close();
let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
getService(Components.interfaces.nsIAppStartup);
appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
}
function cancelClicked() {
gAddon.userDisabled = true;
AddonManager.addAddonListener(EnableListener);
document.getElementById("allow").disabled = false;
document.getElementById("buttonDeck").selectedPanel = document.getElementById("continuePanel");
}

View file

@ -0,0 +1,66 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/newaddon.css"?>
<!DOCTYPE page [
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
%brandDTD;
<!ENTITY % newaddonDTD SYSTEM "chrome://mozapps/locale/extensions/newaddon.dtd">
%newaddonDTD;
]>
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xhtml="http://www.w3.org/1999/xhtml" title="&title;"
disablefastfind="true" id="addon-page" onload="initialize()"
onunload="unload()" role="application" align="stretch" pack="stretch">
<xhtml:link rel="shortcut icon" style="display: none"
href="chrome://mozapps/skin/extensions/extensionGeneric-16.png"/>
<script type="application/javascript"
src="chrome://mozapps/content/extensions/newaddon.js"/>
<scrollbox id="addon-scrollbox" align="center">
<spacer id="spacer-start"/>
<vbox id="addon-container" class="main-content">
<description>&intro;</description>
<hbox id="addon-info">
<image id="icon"/>
<vbox flex="1">
<label id="name"/>
<label id="author"/>
<label id="location" crop="end"/>
</vbox>
</hbox>
<hbox id="warning">
<image id="warning-icon"/>
<description flex="1">&warning;</description>
</hbox>
<checkbox id="allow" label="&allow;"/>
<description id="later">&later;</description>
<deck id="buttonDeck">
<hbox id="continuePanel">
<button id="continue-button" label="&continue;"
oncommand="continueClicked()"/>
</hbox>
<hbox id="restartPanel">
<spacer id="restartSpacer"/>
<description id="restartMessage" flex="1">&restartMessage;</description>
<button id="restart-button" label="&restartButton;" oncommand="restartClicked()"/>
<button id="cancel-button" label="&cancelButton;" oncommand="cancelClicked()"/>
</hbox>
</deck>
</vbox>
<spacer id="spacer-end"/>
</scrollbox>
</page>

View file

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE window SYSTEM "chrome://mozapps/locale/plugins/plugins.dtd">
<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<setting type="control" title="&plugin.file;">
<label class="text-list" id="pluginLibraries"/>
</setting>
<setting type="control" title="&plugin.mimeTypes;">
<label class="text-list" id="pluginMimeTypes"/>
</setting>
<setting type="bool" pref="dom.ipc.plugins.flash.disable-protected-mode"
inverted="true" title="&plugin.flashProtectedMode.label;"
id="pluginEnableProtectedMode"
learnmore="https://support.mozilla.org/kb/flash-protected-mode-settings" />
</vbox>

View file

@ -0,0 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#select .addon {
-moz-binding: url("chrome://mozapps/content/extensions/selectAddons.xml#addon-select");
}
#confirm .addon {
-moz-binding: url("chrome://mozapps/content/extensions/selectAddons.xml#addon-confirm");
}
#select-scrollbox,
#confirm-scrollbox {
overflow-y: auto;
-moz-box-orient: vertical;
}
.addon:not([optionalupdate]) .addon-action-update,
.addon[optionalupdate] .addon-action-message {
display: none;
}

View file

@ -0,0 +1,343 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
Components.utils.import("resource://gre/modules/AddonManager.jsm");
Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
const Cc = Components.classes;
const Ci = Components.interfaces;
var gView = null;
function showView(aView) {
gView = aView;
gView.show();
// If the view's show method immediately showed a different view then don't
// do anything else
if (gView != aView)
return;
let viewNode = document.getElementById(gView.nodeID);
viewNode.parentNode.selectedPanel = viewNode;
// For testing dispatch an event when the view changes
var event = document.createEvent("Events");
event.initEvent("ViewChanged", true, true);
viewNode.dispatchEvent(event);
}
function showButtons(aCancel, aBack, aNext, aDone) {
document.getElementById("cancel").hidden = !aCancel;
document.getElementById("back").hidden = !aBack;
document.getElementById("next").hidden = !aNext;
document.getElementById("done").hidden = !aDone;
}
function isAddonDistroInstalled(aID) {
let branch = Services.prefs.getBranch("extensions.installedDistroAddon.");
if (!branch.prefHasUserValue(aID))
return false;
return branch.getBoolPref(aID);
}
function orderForScope(aScope) {
return aScope == AddonManager.SCOPE_PROFILE ? 1 : 0;
}
var gAddons = {};
var gChecking = {
nodeID: "checking",
_progress: null,
_addonCount: 0,
_completeCount: 0,
show: function gChecking_show() {
showButtons(true, false, false, false);
this._progress = document.getElementById("checking-progress");
AddonManager.getAllAddons(aAddons => {
if (aAddons.length == 0) {
window.close();
return;
}
aAddons = aAddons.filter(function gChecking_filterAddons(aAddon) {
if (aAddon.type == "plugin" || aAddon.type == "service")
return false;
if (aAddon.type == "theme") {
// Don't show application shipped themes
if (aAddon.scope == AddonManager.SCOPE_APPLICATION)
return false;
// Don't show already disabled themes
if (aAddon.userDisabled)
return false;
}
return true;
});
this._addonCount = aAddons.length;
this._progress.value = 0;
this._progress.max = aAddons.length;
this._progress.mode = "determined";
AddonRepository.repopulateCache().then(() => {
for (let addonItem of aAddons) {
// Ignore disabled themes
if (addonItem.type != "theme" || !addonItem.userDisabled) {
gAddons[addonItem.id] = {
addon: addonItem,
install: null,
wasActive: addonItem.isActive
}
}
addonItem.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
}
});
});
},
onUpdateAvailable: function gChecking_onUpdateAvailable(aAddon, aInstall) {
// If the add-on can be upgraded then remember the new version
if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE)
gAddons[aAddon.id].install = aInstall;
},
onUpdateFinished: function gChecking_onUpdateFinished(aAddon, aError) {
this._completeCount++;
this._progress.value = this._completeCount;
if (this._completeCount < this._addonCount)
return;
var addons = [gAddons[id] for (id in gAddons)];
addons.sort(function sortAddons(a, b) {
let orderA = orderForScope(a.addon.scope);
let orderB = orderForScope(b.addon.scope);
if (orderA != orderB)
return orderA - orderB;
return String.localeCompare(a.addon.name, b.addon.name);
});
let rows = document.getElementById("select-rows");
let lastAddon = null;
for (let entry of addons) {
if (lastAddon &&
orderForScope(entry.addon.scope) != orderForScope(lastAddon.scope)) {
let separator = document.createElement("separator");
rows.appendChild(separator);
}
let row = document.createElement("row");
row.setAttribute("id", entry.addon.id);
row.setAttribute("class", "addon");
rows.appendChild(row);
row.setAddon(entry.addon, entry.install, entry.wasActive,
isAddonDistroInstalled(entry.addon.id));
if (entry.install)
entry.install.addListener(gUpdate);
lastAddon = entry.addon;
}
showView(gSelect);
}
};
var gSelect = {
nodeID: "select",
show: function gSelect_show() {
this.updateButtons();
},
updateButtons: function gSelect_updateButtons() {
for (let row = document.getElementById("select-rows").firstChild;
row; row = row.nextSibling) {
if (row.localName == "separator")
continue;
if (row.action) {
showButtons(false, false, true, false);
return;
}
}
showButtons(false, false, false, true);
},
next: function gSelect_next() {
showView(gConfirm);
},
done: function gSelect_done() {
window.close();
}
};
var gConfirm = {
nodeID: "confirm",
show: function gConfirm_show() {
showButtons(false, true, false, true);
let box = document.getElementById("confirm-scrollbox").firstChild;
while (box) {
box.hidden = true;
while (box.lastChild != box.firstChild)
box.removeChild(box.lastChild);
box = box.nextSibling;
}
for (let row = document.getElementById("select-rows").firstChild;
row; row = row.nextSibling) {
if (row.localName == "separator")
continue;
let action = row.action;
if (!action)
continue;
let list = document.getElementById(action + "-list");
list.hidden = false;
let item = document.createElement("hbox");
item.setAttribute("id", row._addon.id);
item.setAttribute("class", "addon");
item.setAttribute("type", row._addon.type);
item.setAttribute("name", row._addon.name);
if (action == "update" || action == "enable")
item.setAttribute("active", "true");
list.appendChild(item);
if (action == "update")
showButtons(false, true, true, false);
}
},
back: function gConfirm_back() {
showView(gSelect);
},
next: function gConfirm_next() {
showView(gUpdate);
},
done: function gConfirm_done() {
for (let row = document.getElementById("select-rows").firstChild;
row; row = row.nextSibling) {
if (row.localName != "separator")
row.apply();
}
window.close();
}
}
var gUpdate = {
nodeID: "update",
_progress: null,
_waitingCount: 0,
_completeCount: 0,
_errorCount: 0,
show: function gUpdate_show() {
showButtons(true, false, false, false);
this._progress = document.getElementById("update-progress");
for (let row = document.getElementById("select-rows").firstChild;
row; row = row.nextSibling) {
if (row.localName != "separator")
row.apply();
}
this._progress.mode = "determined";
this._progress.max = this._waitingCount;
this._progress.value = this._completeCount;
},
checkComplete: function gUpdate_checkComplete() {
this._progress.value = this._completeCount;
if (this._completeCount < this._waitingCount)
return;
if (this._errorCount > 0) {
showView(gErrors);
return;
}
window.close();
},
onDownloadStarted: function gUpdate_onDownloadStarted(aInstall) {
this._waitingCount++;
},
onDownloadFailed: function gUpdate_onDownloadFailed(aInstall) {
this._errorCount++;
this._completeCount++;
this.checkComplete();
},
onInstallFailed: function gUpdate_onInstallFailed(aInstall) {
this._errorCount++;
this._completeCount++;
this.checkComplete();
},
onInstallEnded: function gUpdate_onInstallEnded(aInstall) {
this._completeCount++;
this.checkComplete();
}
};
var gErrors = {
nodeID: "errors",
show: function gErrors_show() {
showButtons(false, false, false, true);
},
done: function gErrors_done() {
window.close();
}
};
window.addEventListener("load", function loadEventListener() {
showView(gChecking); }, false);
// When closing the window cancel any pending or in-progress installs
window.addEventListener("unload", function unloadEventListener() {
for (let id in gAddons) {
let entry = gAddons[id];
if (!entry.install)
return;
aEntry.install.removeListener(gUpdate);
if (entry.install.state != AddonManager.STATE_INSTALLED &&
entry.install.state != AddonManager.STATE_DOWNLOAD_FAILED &&
entry.install.state != AddonManager.STATE_INSTALL_FAILED) {
entry.install.cancel();
}
}
}, false);

View file

@ -0,0 +1,235 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE window [
<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/selectAddons.dtd">
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%updateDTD;
%brandDTD;
]>
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="addon-select">
<content>
<xul:hbox class="select-keep select-cell">
<xul:checkbox class="addon-keep-checkbox" anonid="keep"
xbl:inherits="tooltiptext=name"
oncommand="document.getBindingParent(this).keepChanged();"/>
</xul:hbox>
<xul:hbox class="select-icon select-cell">
<xul:image class="addon-icon" xbl:inherits="type"/>
</xul:hbox>
<xul:hbox class="select-name select-cell">
<xul:label class="addon-name" crop="end" style="&select.name.style;"
xbl:inherits="xbl:text=name"/>
</xul:hbox>
<xul:hbox class="select-action select-cell">
<xul:label class="addon-action-message" style="&select.action.style;"
xbl:inherits="xbl:text=action"/>
<xul:checkbox anonid="update" checked="true" class="addon-action-update"
oncommand="document.getBindingParent(this).updateChanged();"
style="&select.action.style;" xbl:inherits="label=action"/>
</xul:hbox>
<xul:hbox class="select-source select-cell">
<xul:label class="addon-source" xbl:inherits="xbl:text=source"/>
</xul:hbox>
</content>
<implementation>
<field name="_addon"/>
<field name="_disabled"/>
<field name="_install"/>
<field name="_wasActive"/>
<field name="_keep">document.getAnonymousElementByAttribute(this, "anonid", "keep");</field>
<field name="_update">document.getAnonymousElementByAttribute(this, "anonid", "update");</field>
<field name="_strings">document.getElementById("strings");</field>
<property name="action" readonly="true">
<getter><![CDATA[
if (!this._keep.checked) {
if (this._wasActive)
return "disable";
else
return null;
}
if (this._addon.appDisabled && !this._install)
return "incompatible";
if (this._install && (AddonManager.shouldAutoUpdate(this._addon) || this._update.checked))
return "update";
if (this._addon.permissions & AddonManager.PERM_CAN_ENABLE)
return "enable";
return null;
]]></getter>
</property>
<method name="setAddon">
<parameter name="aAddon"/>
<parameter name="aInstall"/>
<parameter name="aWasActive"/>
<parameter name="aDistroInstalled"/>
<body><![CDATA[
this._addon = aAddon;
this._install = aInstall;
this._wasActive = aWasActive;
this.setAttribute("name", aAddon.name);
this.setAttribute("type", aAddon.type);
// User and bundled add-ons default to staying enabled,
// others default to disabled.
switch (aAddon.scope) {
case AddonManager.SCOPE_PROFILE:
this._keep.checked = !aAddon.userDisabled;
if (aDistroInstalled)
this.setAttribute("source", this._strings.getString("source.bundled"));
else
this.setAttribute("source", this._strings.getString("source.profile"));
break;
default:
this._keep.checked = false;
this.setAttribute("source", this._strings.getString("source.other"));
}
this.updateAction();
]]></body>
</method>
<method name="setActionMessage">
<body><![CDATA[
if (!this._keep.checked) {
// If the user no longer wants this add-on then it is either being
// disabled or nothing is changing. Don't complicate matters by
// talking about updates for this case
if (this._wasActive)
this.setAttribute("action", this._strings.getString("action.disabled"));
else
this.setAttribute("action", "");
this.removeAttribute("optionalupdate");
return;
}
if (this._addon.appDisabled && !this._install) {
// If the add-on is incompatible and there is no update available
// then it will remain disabled
this.setAttribute("action", this._strings.getString("action.incompatible"));
this.removeAttribute("optionalupdate");
return;
}
if (this._install) {
if (!AddonManager.shouldAutoUpdate(this._addon)) {
this.setAttribute("optionalupdate", "true");
// If manual updating for the add-on then display the right
// text depending on whether the update is required to make
// the add-on work or not
if (this._addon.appDisabled)
this.setAttribute("action", this._strings.getString("action.neededupdate"));
else
this.setAttribute("action", this._strings.getString("action.unneededupdate"));
return;
}
this.removeAttribute("optionalupdate");
// If the update is needed to make the add-on compatible then
// say so otherwise just say nothing about it
if (this._addon.appDisabled) {
this.setAttribute("action", this._strings.getString("action.autoupdate"));
return;
}
}
else {
this.removeAttribute("optionalupdate");
}
// If the add-on didn't used to be active and it now is (via a
// compatibility update) or it can be enabled then the action is to
// enable the add-on
if (!this._wasActive && (this._addon.isActive || this._addon.permissions & AddonManager.PERM_CAN_ENABLE)) {
this.setAttribute("action", this._strings.getString("action.enabled"));
return;
}
// In all other cases the add-on is simply remaining enabled
this.setAttribute("action", "");
]]></body>
</method>
<method name="updateAction">
<body><![CDATA[
this.setActionMessage();
let installingUpdate = this._install &&
(AddonManager.shouldAutoUpdate(this._addon) ||
this._update.checked);
if (this._keep.checked && (!this._addon.appDisabled || installingUpdate))
this.setAttribute("active", "true");
else
this.removeAttribute("active");
gSelect.updateButtons();
]]></body>
</method>
<method name="updateChanged">
<body><![CDATA[
this.updateAction();
]]></body>
</method>
<method name="keepChanged">
<body><![CDATA[
this.updateAction();
]]></body>
</method>
<method name="keep">
<body><![CDATA[
this._keep.checked = true;
this.keepChanged();
]]></body>
</method>
<method name="disable">
<body><![CDATA[
this._keep.checked = false;
this.keepChanged();
]]></body>
</method>
<method name="apply">
<body><![CDATA[
this._addon.userDisabled = !this._keep.checked;
if (!this._install || !this._keep.checked)
return;
if (AddonManager.shouldAutoUpdate(this._addon) || this._update.checked)
this._install.install();
]]></body>
</method>
</implementation>
</binding>
<binding id="addon-confirm">
<content>
<xul:image class="addon-icon" xbl:inherits="type"/>
<xul:label class="addon-name" xbl:inherits="xbl:text=name"/>
</content>
</binding>
</bindings>

View file

@ -0,0 +1,124 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/content/extensions/selectAddons.css" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/selectAddons.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/selectAddons.dtd">
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%updateDTD;
%brandDTD;
]>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
style="&upgrade.style;" id="select-window">
<script type="application/javascript" src="chrome://mozapps/content/extensions/selectAddons.js"/>
<stringbundle id="strings" src="chrome://mozapps/locale/extensions/selectAddons.properties"/>
<deck id="view-deck" flex="1" align="stretch" pack="stretch">
<vbox id="checking" align="stretch">
<vbox flex="1">
<label id="checking-heading" class="heading">&checking.heading;</label>
</vbox>
<progressmeter id="checking-progress" class="progress" mode="undetermined"/>
<vbox flex="1">
<label id="checking-progress-label" class="progress-label">&checking.progress.label;</label>
</vbox>
</vbox>
<vbox id="select" align="stretch">
<label id="select-heading" class="heading">&select.heading;</label>
<description id="select-description">&select.description;</description>
<vbox id="select-list" align="stretch" flex="1">
<hbox id="select-header">
<hbox class="select-keep select-cell" style="&select.keep.style;">
<label value="&select.keep;"/>
</hbox>
<hbox class="select-icon select-cell"/>
<hbox id="heading-name" class="select-name select-cell" style="&select.name.style;">
<label value="&select.name;"/>
</hbox>
<hbox id="heading-action" class="select-action select-cell" style="&select.action.style;">
<label value="&select.action;"/>
</hbox>
<hbox class="select-source select-cell" flex="1">
<label value="&select.source;"/>
</hbox>
</hbox>
<scrollbox id="select-scrollbox" flex="1">
<grid id="select-grid" flex="1">
<columns>
<column style="&select.keep.style;"/>
<column/>
<column id="column-name"/>
<column id="column-action" class="select-action"/>
<column class="select-source" flex="1"/>
</columns>
<rows id="select-rows"/>
</grid>
</scrollbox>
</vbox>
</vbox>
<vbox id="confirm" align="stretch">
<label id="confirm-heading" class="heading">&confirm.heading;</label>
<description id="confirm-description">&confirm.description;</description>
<scrollbox id="confirm-scrollbox" flex="1">
<vbox id="disable-list" class="action-list" hidden="true">
<label class="action-header">&action.disable.heading;</label>
</vbox>
<vbox id="incompatible-list" class="action-list" hidden="true">
<label class="action-header">&action.incompatible.heading;</label>
</vbox>
<vbox id="update-list" class="action-list" hidden="true">
<label class="action-header">&action.update.heading;</label>
</vbox>
<vbox id="enable-list" class="action-list" hidden="true">
<label class="action-header">&action.enable.heading;</label>
</vbox>
</scrollbox>
</vbox>
<vbox id="update" align="stretch">
<vbox flex="1">
<label id="update-heading" class="heading">&update.heading;</label>
</vbox>
<progressmeter id="update-progress" class="progress" mode="undetermined"/>
<vbox flex="1">
<label id="update-progress-label" class="progress-label">&update.progress.label;</label>
</vbox>
</vbox>
<vbox id="errors">
<vbox flex="1">
<label id="errors-heading" class="heading">&errors.heading;</label>
</vbox>
<description id="errors-description" value="&errors.description;"/>
<spacer flex="1"/>
</vbox>
</deck>
<hbox id="footer" align="center">
<label id="footer-label" flex="1">&footer.label;</label>
<button id="cancel" label="&cancel.label;" oncommand="window.close()"/>
<button id="back" label="&back.label;" oncommand="gView.back()" hidden="true"/>
<button id="next" label="&next.label;" oncommand="gView.next()" hidden="true"/>
<button id="done" label="&done.label;" oncommand="gView.done()" hidden="true"/>
</hbox>
</window>

View file

@ -0,0 +1,508 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE page [
<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
%extensionsDTD;
]>
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="settings">
<content orient="vertical">
<xul:label class="settings-title" xbl:inherits="xbl:text=label" flex="1"/>
<children/>
</content>
</binding>
<binding id="setting-base">
<implementation>
<constructor><![CDATA[
this.preferenceChanged();
this.addEventListener("keypress", function(event) {
event.stopPropagation();
}, false);
if (this.usePref)
Services.prefs.addObserver(this.pref, this._observer, true);
]]></constructor>
<field name="_observer"><![CDATA[({
_self: this,
QueryInterface: function(aIID) {
const Ci = Components.interfaces;
if (aIID.equals(Ci.nsIObserver) ||
aIID.equals(Ci.nsISupportsWeakReference) ||
aIID.equals(Ci.nsISupports))
return this;
throw Components.Exception("No interface", Components.results.NS_ERROR_NO_INTERFACE);
},
observe: function(aSubject, aTopic, aPrefName) {
if (aTopic != "nsPref:changed")
return;
if (this._self.pref == aPrefName)
this._self.preferenceChanged();
}
})]]>
</field>
<method name="fireEvent">
<parameter name="eventName"/>
<parameter name="funcStr"/>
<body>
<![CDATA[
let body = funcStr || this.getAttribute(eventName);
if (!body)
return;
try {
let event = document.createEvent("Events");
event.initEvent(eventName, true, true);
let f = new Function("event", body);
f.call(this, event);
}
catch (e) {
Cu.reportError(e);
}
]]>
</body>
</method>
<method name="valueFromPreference">
<body>
<![CDATA[
// Should be code to set the from the preference input.value
throw Components.Exception("No valueFromPreference implementation",
Components.results.NS_ERROR_NOT_IMPLEMENTED);
]]>
</body>
</method>
<method name="valueToPreference">
<body>
<![CDATA[
// Should be code to set the input.value from the preference
throw Components.Exception("No valueToPreference implementation",
Components.results.NS_ERROR_NOT_IMPLEMENTED);
]]>
</body>
</method>
<method name="inputChanged">
<body>
<![CDATA[
if (this.usePref && !this._updatingInput) {
this.valueToPreference();
this.fireEvent("oninputchanged");
}
]]>
</body>
</method>
<method name="preferenceChanged">
<body>
<![CDATA[
if (this.usePref) {
this._updatingInput = true;
try {
this.valueFromPreference();
this.fireEvent("onpreferencechanged");
} catch (e) {}
this._updatingInput = false;
}
]]>
</body>
</method>
<property name="usePref" readonly="true" onget="return this.hasAttribute('pref');"/>
<property name="pref" readonly="true" onget="return this.getAttribute('pref');"/>
<property name="type" readonly="true" onget="return this.getAttribute('type');"/>
<property name="value" onget="return this.input.value;" onset="return this.input.value = val;"/>
<field name="_updatingInput">false</field>
<field name="input">document.getAnonymousElementByAttribute(this, "anonid", "input");</field>
<field name="settings">
this.parentNode.localName == "settings" ? this.parentNode : null;
</field>
</implementation>
</binding>
<binding id="setting-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
<content>
<xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
</xul:hbox>
<xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
<xul:label class="preferences-learnmore text-link"
onclick="document.getBindingParent(this).openLearnMore()">&setting.learnmore;</xul:label>
</xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:checkbox anonid="input" xbl:inherits="disabled,onlabel,offlabel,label=checkboxlabel" oncommand="inputChanged();"/>
</xul:hbox>
</content>
<implementation>
<method name="valueFromPreference">
<body>
<![CDATA[
let val = Services.prefs.getBoolPref(this.pref);
this.value = this.inverted ? !val : val;
]]>
</body>
</method>
<method name="valueToPreference">
<body>
<![CDATA[
let val = this.value;
Services.prefs.setBoolPref(this.pref, this.inverted ? !val : val);
]]>
</body>
</method>
<property name="value" onget="return this.input.checked;" onset="return this.input.setChecked(val);"/>
<property name="inverted" readonly="true" onget="return this.getAttribute('inverted');"/>
<method name="openLearnMore">
<body>
<![CDATA[
window.open(this.getAttribute("learnmore"), "_blank");
]]>
</body>
</method>
</implementation>
</binding>
<binding id="setting-boolint" extends="chrome://mozapps/content/extensions/setting.xml#setting-bool">
<implementation>
<method name="valueFromPreference">
<body>
<![CDATA[
let val = Services.prefs.getIntPref(this.pref);
this.value = (val == this.getAttribute("on"));
]]>
</body>
</method>
<method name="valueToPreference">
<body>
<![CDATA[
Services.prefs.setIntPref(this.pref, this.getAttribute(this.value ? "on" : "off"));
]]>
</body>
</method>
</implementation>
</binding>
<binding id="setting-localized-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-bool">
<implementation>
<method name="valueFromPreference">
<body>
<![CDATA[
let val = Services.prefs.getComplexValue(this.pref, Components.interfaces.nsIPrefLocalizedString).data;
if(this.inverted) val = !val;
this.value = (val == "true");
]]>
</body>
</method>
<method name="valueToPreference">
<body>
<![CDATA[
let val = this.value;
if(this.inverted) val = !val;
let pref = Components.classes["@mozilla.org/pref-localizedstring;1"].createInstance(Components.interfaces.nsIPrefLocalizedString);
pref.data = this.inverted ? (!val).toString() : val.toString();
Services.prefs.setComplexValue(this.pref, Components.interfaces.nsIPrefLocalizedString, pref);
]]>
</body>
</method>
</implementation>
</binding>
<binding id="setting-integer" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
<content>
<xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
</xul:hbox>
<xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
</xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:textbox type="number" anonid="input" oninput="inputChanged();" onchange="inputChanged();"
xbl:inherits="disabled,emptytext,min,max,increment,hidespinbuttons,wraparound,size"/>
</xul:hbox>
</content>
<implementation>
<method name="valueFromPreference">
<body>
<![CDATA[
let val = Services.prefs.getIntPref(this.pref);
this.value = val;
]]>
</body>
</method>
<method name="valueToPreference">
<body>
<![CDATA[
Services.prefs.setIntPref(this.pref, this.value);
]]>
</body>
</method>
</implementation>
</binding>
<binding id="setting-control" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
<content>
<xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
</xul:hbox>
<xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
</xul:vbox>
<xul:hbox class="preferences-alignment">
<children/>
</xul:hbox>
</content>
</binding>
<binding id="setting-string" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
<content>
<xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
</xul:hbox>
<xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
</xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:textbox anonid="input" flex="1" oninput="inputChanged();"
xbl:inherits="disabled,emptytext,type=inputtype,min,max,increment,hidespinbuttons,decimalplaces,wraparound"/>
</xul:hbox>
</content>
<implementation>
<method name="valueFromPreference">
<body>
<![CDATA[
const nsISupportsString = Components.interfaces.nsISupportsString;
this.value = Services.prefs.getComplexValue(this.pref, nsISupportsString).data;
]]>
</body>
</method>
<method name="valueToPreference">
<body>
<![CDATA[
const nsISupportsString = Components.interfaces.nsISupportsString;
let iss = Components.classes["@mozilla.org/supports-string;1"].createInstance(nsISupportsString);
iss.data = this.value;
Services.prefs.setComplexValue(this.pref, nsISupportsString, iss);
]]>
</body>
</method>
</implementation>
</binding>
<binding id="setting-color" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
<content>
<xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
</xul:hbox>
<xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
</xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:colorpicker type="button" anonid="input" xbl:inherits="disabled" onchange="document.getBindingParent(this).inputChanged();"/>
</xul:hbox>
</content>
<implementation>
<method name="valueFromPreference">
<body>
<![CDATA[
// We must wait for the colorpicker's binding to be applied before setting the value
if (!this.input.color)
this.input.initialize();
this.value = Services.prefs.getCharPref(this.pref);
]]>
</body>
</method>
<method name="valueToPreference">
<body>
<![CDATA[
Services.prefs.setCharPref(this.pref, this.value);
]]>
</body>
</method>
<property name="value" onget="return this.input.color;" onset="return this.input.color = val;"/>
</implementation>
</binding>
<binding id="setting-path" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
<content>
<xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
</xul:hbox>
<xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
</xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:button type="button" anonid="button" label="&settings.path.button.label;" xbl:inherits="disabled" oncommand="showPicker();"/>
<xul:label anonid="input" flex="1" crop="center" xbl:inherits="disabled"/>
</xul:hbox>
</content>
<implementation>
<method name="showPicker">
<body>
<![CDATA[
var filePicker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
filePicker.init(window, this.getAttribute("title"),
this.type == "file" ? Ci.nsIFilePicker.modeOpen : Ci.nsIFilePicker.modeGetFolder);
if (this.value) {
try {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(this.value);
filePicker.displayDirectory = this.type == "file" ? file.parent : file;
if (this.type == "file") {
filePicker.defaultString = file.leafName;
}
} catch (e) {}
}
if (filePicker.show() != Ci.nsIFilePicker.returnCancel) {
this.value = filePicker.file.path;
this.inputChanged();
}
]]>
</body>
</method>
<method name="valueFromPreference">
<body>
<![CDATA[
this.value = Services.prefs.getCharPref(this.pref);
]]>
</body>
</method>
<method name="valueToPreference">
<body>
<![CDATA[
Services.prefs.setCharPref(this.pref, this.value);
]]>
</body>
</method>
<field name="_value"></field>
<property name="value">
<getter>
<![CDATA[
return this._value;
]]>
</getter>
<setter>
<![CDATA[
this._value = val;
let label = "";
if (val) {
try {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(val);
label = this.hasAttribute("fullpath") ? file.path : file.leafName;
} catch (e) {}
}
this.input.tooltipText = val;
return this.input.value = label;
]]>
</setter>
</property>
</implementation>
</binding>
<binding id="setting-multi" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
<content>
<xul:vbox>
<xul:hbox class="preferences-alignment">
<xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
</xul:hbox>
<xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
</xul:vbox>
<xul:hbox class="preferences-alignment">
<children includes="radiogroup|menulist"/>
</xul:hbox>
</content>
<implementation>
<constructor>
<![CDATA[
this.control.addEventListener("command", this.inputChanged.bind(this), false);
]]>
</constructor>
<method name="valueFromPreference">
<body>
<![CDATA[
let val;
switch (Services.prefs.getPrefType(this.pref)) {
case Ci.nsIPrefBranch.PREF_STRING:
val = Services.prefs.getCharPref(this.pref);
break;
case Ci.nsIPrefBranch.PREF_INT:
val = Services.prefs.getIntPref(this.pref);
break;
case Ci.nsIPrefBranch.PREF_BOOL:
val = Services.prefs.getBoolPref(this.pref).toString();
break;
default:
return;
}
if ("itemCount" in this.control) {
for (let i = 0; i < this.control.itemCount; i++) {
if (this.control.getItemAtIndex(i).value == val) {
this.control.selectedIndex = i;
break;
}
}
} else {
this.control.setAttribute("value", val);
}
]]>
</body>
</method>
<method name="valueToPreference">
<body>
<![CDATA[
// We might not have a pref already set, so we guess the type from the value attribute
let val = this.control.selectedItem.value;
if (val == "true" || val == "false")
Services.prefs.setBoolPref(this.pref, val == "true");
else if (/^-?\d+$/.test(val))
Services.prefs.setIntPref(this.pref, val);
else
Services.prefs.setCharPref(this.pref, val);
]]>
</body>
</method>
<field name="control">this.getElementsByTagName(this.getAttribute("type") == "radio" ? "radiogroup" : "menulist")[0];</field>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,663 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This UI is only opened from the Extension Manager when the app is upgraded.
"use strict";
const PREF_UPDATE_EXTENSIONS_ENABLED = "extensions.update.enabled";
const PREF_XPINSTALL_ENABLED = "xpinstall.enabled";
// timeout (in milliseconds) to wait for response to the metadata ping
const METADATA_TIMEOUT = 30000;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/addons/AddonRepository.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Log", "resource://gre/modules/Log.jsm");
let logger = null;
var gUpdateWizard = {
// When synchronizing app compatibility info this contains all installed
// add-ons. When checking for compatible versions this contains only
// incompatible add-ons.
addons: [],
// Contains a Set of IDs for add-on that were disabled by the application update.
affectedAddonIDs: null,
// The add-ons that we found updates available for
addonsToUpdate: [],
shouldSuggestAutoChecking: false,
shouldAutoCheck: false,
xpinstallEnabled: true,
xpinstallLocked: false,
// cached AddonInstall entries for add-ons we might want to update,
// keyed by add-on ID
addonInstalls: new Map(),
shuttingDown: false,
// Count the add-ons disabled by this update, enabled/disabled by
// metadata checks, and upgraded.
disabled: 0,
metadataEnabled: 0,
metadataDisabled: 0,
upgraded: 0,
upgradeFailed: 0,
upgradeDeclined: 0,
init: function gUpdateWizard_init()
{
logger = Log.repository.getLogger("addons.update-dialog");
// XXX could we pass the addons themselves rather than the IDs?
this.affectedAddonIDs = new Set(window.arguments[0]);
try {
this.shouldSuggestAutoChecking =
!Services.prefs.getBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED);
}
catch (e) {
}
try {
this.xpinstallEnabled = Services.prefs.getBoolPref(PREF_XPINSTALL_ENABLED);
this.xpinstallLocked = Services.prefs.prefIsLocked(PREF_XPINSTALL_ENABLED);
}
catch (e) {
}
if (Services.io.offline)
document.documentElement.currentPage = document.getElementById("offline");
else
document.documentElement.currentPage = document.getElementById("versioninfo");
},
onWizardFinish: function gUpdateWizard_onWizardFinish ()
{
if (this.shouldSuggestAutoChecking)
Services.prefs.setBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED, this.shouldAutoCheck);
},
_setUpButton: function gUpdateWizard_setUpButton(aButtonID, aButtonKey, aDisabled)
{
var strings = document.getElementById("updateStrings");
var button = document.documentElement.getButton(aButtonID);
if (aButtonKey) {
button.label = strings.getString(aButtonKey);
try {
button.setAttribute("accesskey", strings.getString(aButtonKey + "Accesskey"));
}
catch (e) {
}
}
button.disabled = aDisabled;
},
setButtonLabels: function gUpdateWizard_setButtonLabels(aBackButton, aBackButtonIsDisabled,
aNextButton, aNextButtonIsDisabled,
aCancelButton, aCancelButtonIsDisabled)
{
this._setUpButton("back", aBackButton, aBackButtonIsDisabled);
this._setUpButton("next", aNextButton, aNextButtonIsDisabled);
this._setUpButton("cancel", aCancelButton, aCancelButtonIsDisabled);
},
/////////////////////////////////////////////////////////////////////////////
// Update Errors
errorItems: [],
checkForErrors: function gUpdateWizard_checkForErrors(aElementIDToShow)
{
if (this.errorItems.length > 0)
document.getElementById(aElementIDToShow).hidden = false;
},
onWizardClose: function gUpdateWizard_onWizardClose(aEvent)
{
return this.onWizardCancel();
},
onWizardCancel: function gUpdateWizard_onWizardCancel()
{
gUpdateWizard.shuttingDown = true;
// Allow add-ons to continue downloading and installing
// in the background, though some may require a later restart
// Pages that are waiting for user input go into the background
// on cancel
if (gMismatchPage.waiting) {
logger.info("Dialog closed in mismatch page");
if (gUpdateWizard.addonInstalls.size > 0) {
gInstallingPage.startInstalls([i for ([, i] of gUpdateWizard.addonInstalls)]);
}
return true;
}
// Pages that do asynchronous things will just keep running and check
// gUpdateWizard.shuttingDown to trigger background behaviour
if (!gInstallingPage.installing) {
logger.info("Dialog closed while waiting for updated compatibility information");
}
else {
logger.info("Dialog closed while downloading and installing updates");
}
return true;
}
};
var gOfflinePage = {
onPageAdvanced: function gOfflinePage_onPageAdvanced()
{
Services.io.offline = false;
return true;
},
toggleOffline: function gOfflinePage_toggleOffline()
{
var nextbtn = document.documentElement.getButton("next");
nextbtn.disabled = !nextbtn.disabled;
}
}
// Addon listener to count addons enabled/disabled by metadata checks
let listener = {
onDisabled: function listener_onDisabled(aAddon) {
gUpdateWizard.affectedAddonIDs.add(aAddon.id);
gUpdateWizard.metadataDisabled++;
},
onEnabled: function listener_onEnabled(aAddon) {
gUpdateWizard.affectedAddonIDs.delete(aAddon.id);
gUpdateWizard.metadataEnabled++;
}
};
var gVersionInfoPage = {
_completeCount: 0,
_totalCount: 0,
_versionInfoDone: false,
onPageShow: Task.async(function* gVersionInfoPage_onPageShow() {
gUpdateWizard.setButtonLabels(null, true,
"nextButtonText", true,
"cancelButtonText", false);
gUpdateWizard.disabled = gUpdateWizard.affectedAddonIDs.size;
// Ensure compatibility overrides are up to date before checking for
// individual addon updates.
AddonManager.addAddonListener(listener);
if (AddonRepository.isMetadataStale()) {
// Do the metadata ping, listening for any newly enabled/disabled add-ons.
yield AddonRepository.repopulateCache(METADATA_TIMEOUT);
if (gUpdateWizard.shuttingDown) {
logger.debug("repopulateCache completed after dialog closed");
}
}
// Fetch the add-ons that are still affected by this update.
let idlist = [id for (id of gUpdateWizard.affectedAddonIDs)];
if (idlist.length < 1) {
gVersionInfoPage.onAllUpdatesFinished();
return;
}
logger.debug("Fetching affected addons " + idlist.toSource());
let fetchedAddons = yield new Promise((resolve, reject) =>
AddonManager.getAddonsByIDs(idlist, resolve));
// We shouldn't get nulls here, but let's be paranoid...
gUpdateWizard.addons = [a for (a of fetchedAddons) if (a)];
if (gUpdateWizard.addons.length < 1) {
gVersionInfoPage.onAllUpdatesFinished();
return;
}
gVersionInfoPage._totalCount = gUpdateWizard.addons.length;
for (let addon of gUpdateWizard.addons) {
logger.debug("VersionInfo Finding updates for ${id}", addon);
addon.findUpdates(gVersionInfoPage, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
}
}),
onAllUpdatesFinished: function gVersionInfoPage_onAllUpdatesFinished() {
AddonManager.removeAddonListener(listener);
AddonManagerPrivate.recordSimpleMeasure("appUpdate_disabled",
gUpdateWizard.disabled);
AddonManagerPrivate.recordSimpleMeasure("appUpdate_metadata_enabled",
gUpdateWizard.metadataEnabled);
AddonManagerPrivate.recordSimpleMeasure("appUpdate_metadata_disabled",
gUpdateWizard.metadataDisabled);
// Record 0 for these here in case we exit early; values will be replaced
// later if we actually upgrade any.
AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgraded", 0);
AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeFailed", 0);
AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeDeclined", 0);
// Filter out any add-ons that are now enabled.
logger.debug("VersionInfo updates finished: found " +
[addon.id + ":" + addon.appDisabled for (addon of gUpdateWizard.addons)].toSource());
let filteredAddons = [];
for (let a of gUpdateWizard.addons) {
if (a.appDisabled) {
logger.debug("Continuing with add-on " + a.id);
filteredAddons.push(a);
}
else if (gUpdateWizard.addonInstalls.has(a.id)) {
gUpdateWizard.addonInstalls.get(a.id).cancel();
gUpdateWizard.addonInstalls.delete(a.id);
}
}
gUpdateWizard.addons = filteredAddons;
if (gUpdateWizard.shuttingDown) {
// jump directly to updating auto-update add-ons in the background
if (gUpdateWizard.addonInstalls.size > 0) {
gInstallingPage.startInstalls([i for ([, i] of gUpdateWizard.addonInstalls)]);
}
return;
}
if (filteredAddons.length > 0) {
if (!gUpdateWizard.xpinstallEnabled && gUpdateWizard.xpinstallLocked) {
document.documentElement.currentPage = document.getElementById("adminDisabled");
return;
}
document.documentElement.currentPage = document.getElementById("mismatch");
}
else {
logger.info("VersionInfo: No updates require further action");
// VersionInfo compatibility updates resolved all compatibility problems,
// close this window and continue starting the application...
//XXX Bug 314754 - We need to use setTimeout to close the window due to
// the EM using xmlHttpRequest when checking for updates.
setTimeout(close, 0);
}
},
/////////////////////////////////////////////////////////////////////////////
// UpdateListener
onUpdateFinished: function gVersionInfoPage_onUpdateFinished(aAddon, status) {
++this._completeCount;
if (status != AddonManager.UPDATE_STATUS_NO_ERROR) {
logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount +
" failed for " + aAddon.id + ": " + status);
gUpdateWizard.errorItems.push(aAddon);
}
else {
logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount +
" finished for " + aAddon.id);
}
// If we're not in the background, just make a list of add-ons that have
// updates available
if (!gUpdateWizard.shuttingDown) {
// If we're still in the update check window and the add-on is now active
// then it won't have been disabled by startup
if (aAddon.active) {
AddonManagerPrivate.removeStartupChange(AddonManager.STARTUP_CHANGE_DISABLED, aAddon.id);
gUpdateWizard.metadataEnabled++;
}
// Update the status text and progress bar
var updateStrings = document.getElementById("updateStrings");
var statusElt = document.getElementById("versioninfo.status");
var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]);
statusElt.setAttribute("value", statusString);
// Update the status text and progress bar
var progress = document.getElementById("versioninfo.progress");
progress.mode = "normal";
progress.value = Math.ceil((this._completeCount / this._totalCount) * 100);
}
if (this._completeCount == this._totalCount)
this.onAllUpdatesFinished();
},
onUpdateAvailable: function gVersionInfoPage_onUpdateAvailable(aAddon, aInstall) {
logger.debug("VersionInfo got an install for " + aAddon.id + ": " + aAddon.version);
gUpdateWizard.addonInstalls.set(aAddon.id, aInstall);
},
};
var gMismatchPage = {
waiting: false,
onPageShow: function gMismatchPage_onPageShow()
{
gMismatchPage.waiting = true;
gUpdateWizard.setButtonLabels(null, true,
"mismatchCheckNow", false,
"mismatchDontCheck", false);
document.documentElement.getButton("next").focus();
var incompatible = document.getElementById("mismatch.incompatible");
for (let addon of gUpdateWizard.addons) {
var listitem = document.createElement("listitem");
listitem.setAttribute("label", addon.name + " " + addon.version);
incompatible.appendChild(listitem);
}
}
};
var gUpdatePage = {
_totalCount: 0,
_completeCount: 0,
onPageShow: function gUpdatePage_onPageShow()
{
gMismatchPage.waiting = false;
gUpdateWizard.setButtonLabels(null, true,
"nextButtonText", true,
"cancelButtonText", false);
document.documentElement.getButton("next").focus();
gUpdateWizard.errorItems = [];
this._totalCount = gUpdateWizard.addons.length;
for (let addon of gUpdateWizard.addons) {
logger.debug("UpdatePage requesting update for " + addon.id);
// Redundant call to find updates again here when we already got them
// in the VersionInfo page: https://bugzilla.mozilla.org/show_bug.cgi?id=960597
addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
}
},
onAllUpdatesFinished: function gUpdatePage_onAllUpdatesFinished() {
if (gUpdateWizard.shuttingDown)
return;
var nextPage = document.getElementById("noupdates");
if (gUpdateWizard.addonsToUpdate.length > 0)
nextPage = document.getElementById("found");
document.documentElement.currentPage = nextPage;
},
/////////////////////////////////////////////////////////////////////////////
// UpdateListener
onUpdateAvailable: function gUpdatePage_onUpdateAvailable(aAddon, aInstall) {
logger.debug("UpdatePage got an update for " + aAddon.id + ": " + aAddon.version);
gUpdateWizard.addonsToUpdate.push(aInstall);
},
onUpdateFinished: function gUpdatePage_onUpdateFinished(aAddon, status) {
if (status != AddonManager.UPDATE_STATUS_NO_ERROR)
gUpdateWizard.errorItems.push(aAddon);
++this._completeCount;
if (!gUpdateWizard.shuttingDown) {
// Update the status text and progress bar
var updateStrings = document.getElementById("updateStrings");
var statusElt = document.getElementById("checking.status");
var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]);
statusElt.setAttribute("value", statusString);
var progress = document.getElementById("checking.progress");
progress.value = Math.ceil((this._completeCount / this._totalCount) * 100);
}
if (this._completeCount == this._totalCount)
this.onAllUpdatesFinished()
},
};
var gFoundPage = {
onPageShow: function gFoundPage_onPageShow()
{
gUpdateWizard.setButtonLabels(null, true,
"installButtonText", false,
null, false);
var foundUpdates = document.getElementById("found.updates");
var itemCount = gUpdateWizard.addonsToUpdate.length;
for (let install of gUpdateWizard.addonsToUpdate) {
let listItem = foundUpdates.appendItem(install.name + " " + install.version);
listItem.setAttribute("type", "checkbox");
listItem.setAttribute("checked", "true");
listItem.install = install;
}
if (!gUpdateWizard.xpinstallEnabled) {
document.getElementById("xpinstallDisabledAlert").hidden = false;
document.getElementById("enableXPInstall").focus();
document.documentElement.getButton("next").disabled = true;
}
else {
document.documentElement.getButton("next").focus();
document.documentElement.getButton("next").disabled = false;
}
},
toggleXPInstallEnable: function gFoundPage_toggleXPInstallEnable(aEvent)
{
var enabled = aEvent.target.checked;
gUpdateWizard.xpinstallEnabled = enabled;
var pref = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
pref.setBoolPref(PREF_XPINSTALL_ENABLED, enabled);
this.updateNextButton();
},
updateNextButton: function gFoundPage_updateNextButton()
{
if (!gUpdateWizard.xpinstallEnabled) {
document.documentElement.getButton("next").disabled = true;
return;
}
var oneChecked = false;
var foundUpdates = document.getElementById("found.updates");
var updates = foundUpdates.getElementsByTagName("listitem");
for (let update of updates) {
if (!update.checked)
continue;
oneChecked = true;
break;
}
gUpdateWizard.setButtonLabels(null, true,
"installButtonText", true,
null, false);
document.getElementById("found").setAttribute("next", "installing");
document.documentElement.getButton("next").disabled = !oneChecked;
}
};
var gInstallingPage = {
_installs : [],
_errors : [],
_strings : null,
_currentInstall : -1,
_installing : false,
// Initialize fields we need for installing and tracking progress,
// and start iterating through the installations
startInstalls: function gInstallingPage_startInstalls(aInstallList) {
if (!gUpdateWizard.xpinstallEnabled) {
return;
}
logger.debug("Start installs for "
+ [i.existingAddon.id for (i of aInstallList)].toSource());
this._errors = [];
this._installs = aInstallList;
this._installing = true;
this.startNextInstall();
},
onPageShow: function gInstallingPage_onPageShow()
{
gUpdateWizard.setButtonLabels(null, true,
"nextButtonText", true,
null, true);
var foundUpdates = document.getElementById("found.updates");
var updates = foundUpdates.getElementsByTagName("listitem");
let toInstall = [];
for (let update of updates) {
if (!update.checked) {
logger.info("User chose to cancel update of " + update.label);
gUpdateWizard.upgradeDeclined++;
update.install.cancel();
continue;
}
toInstall.push(update.install);
}
this._strings = document.getElementById("updateStrings");
this.startInstalls(toInstall);
},
startNextInstall: function gInstallingPage_startNextInstall() {
if (this._currentInstall >= 0) {
this._installs[this._currentInstall].removeListener(this);
}
this._currentInstall++;
if (this._installs.length == this._currentInstall) {
Services.obs.notifyObservers(null, "TEST:all-updates-done", null);
AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgraded",
gUpdateWizard.upgraded);
AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeFailed",
gUpdateWizard.upgradeFailed);
AddonManagerPrivate.recordSimpleMeasure("appUpdate_upgradeDeclined",
gUpdateWizard.upgradeDeclined);
this._installing = false;
if (gUpdateWizard.shuttingDown) {
return;
}
var nextPage = this._errors.length > 0 ? "installerrors" : "finished";
document.getElementById("installing").setAttribute("next", nextPage);
document.documentElement.advance();
return;
}
let install = this._installs[this._currentInstall];
if (gUpdateWizard.shuttingDown && !AddonManager.shouldAutoUpdate(install.existingAddon)) {
logger.debug("Don't update " + install.existingAddon.id + " in background");
gUpdateWizard.upgradeDeclined++;
install.cancel();
this.startNextInstall();
return;
}
install.addListener(this);
install.install();
},
/////////////////////////////////////////////////////////////////////////////
// InstallListener
onDownloadStarted: function gInstallingPage_onDownloadStarted(aInstall) {
if (gUpdateWizard.shuttingDown) {
return;
}
var strings = document.getElementById("updateStrings");
var label = strings.getFormattedString("downloadingPrefix", [aInstall.name]);
var actionItem = document.getElementById("actionItem");
actionItem.value = label;
},
onDownloadProgress: function gInstallingPage_onDownloadProgress(aInstall) {
if (gUpdateWizard.shuttingDown) {
return;
}
var downloadProgress = document.getElementById("downloadProgress");
downloadProgress.value = Math.ceil(100 * aInstall.progress / aInstall.maxProgress);
},
onDownloadEnded: function gInstallingPage_onDownloadEnded(aInstall) {
},
onDownloadFailed: function gInstallingPage_onDownloadFailed(aInstall) {
this._errors.push(aInstall);
gUpdateWizard.upgradeFailed++;
this.startNextInstall();
},
onInstallStarted: function gInstallingPage_onInstallStarted(aInstall) {
if (gUpdateWizard.shuttingDown) {
return;
}
var strings = document.getElementById("updateStrings");
var label = strings.getFormattedString("installingPrefix", [aInstall.name]);
var actionItem = document.getElementById("actionItem");
actionItem.value = label;
},
onInstallEnded: function gInstallingPage_onInstallEnded(aInstall, aAddon) {
if (!gUpdateWizard.shuttingDown) {
// Remember that this add-on was updated during startup
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
aAddon.id);
}
gUpdateWizard.upgraded++;
this.startNextInstall();
},
onInstallFailed: function gInstallingPage_onInstallFailed(aInstall) {
this._errors.push(aInstall);
gUpdateWizard.upgradeFailed++;
this.startNextInstall();
}
};
var gInstallErrorsPage = {
onPageShow: function gInstallErrorsPage_onPageShow()
{
gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
document.documentElement.getButton("finish").focus();
},
};
// Displayed when there are incompatible add-ons and the xpinstall.enabled
// pref is false and locked.
var gAdminDisabledPage = {
onPageShow: function gAdminDisabledPage_onPageShow()
{
gUpdateWizard.setButtonLabels(null, true, null, true,
"cancelButtonText", true);
document.documentElement.getButton("finish").focus();
}
};
// Displayed when selected add-on updates have been installed without error.
// There can still be add-ons that are not compatible and don't have an update.
var gFinishedPage = {
onPageShow: function gFinishedPage_onPageShow()
{
gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
document.documentElement.getButton("finish").focus();
if (gUpdateWizard.shouldSuggestAutoChecking) {
document.getElementById("finishedCheckDisabled").hidden = false;
gUpdateWizard.shouldAutoCheck = true;
}
else
document.getElementById("finishedCheckEnabled").hidden = false;
document.documentElement.getButton("finish").focus();
}
};
// Displayed when there are incompatible add-ons and there are no available
// updates.
var gNoUpdatesPage = {
onPageShow: function gNoUpdatesPage_onPageLoad(aEvent)
{
gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
if (gUpdateWizard.shouldSuggestAutoChecking) {
document.getElementById("noupdatesCheckDisabled").hidden = false;
gUpdateWizard.shouldAutoCheck = true;
}
else
document.getElementById("noupdatesCheckEnabled").hidden = false;
gUpdateWizard.checkForErrors("updateCheckErrorNotFound");
document.documentElement.getButton("finish").focus();
}
};

View file

@ -0,0 +1,196 @@
<?xml version="1.0"?>
# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/extensions/update.css" type="text/css"?>
<!DOCTYPE wizard [
<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/update.dtd">
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%updateDTD;
%brandDTD;
]>
<wizard id="updateWizard"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&updateWizard.title;"
windowtype="Addons:Compatibility"
branded="true"
onload="gUpdateWizard.init();"
onwizardfinish="gUpdateWizard.onWizardFinish();"
onwizardcancel="return gUpdateWizard.onWizardCancel();"
onclose="return gUpdateWizard.onWizardClose(event);"
buttons="accept,cancel">
<script type="application/javascript" src="chrome://mozapps/content/extensions/update.js"/>
<stringbundleset id="updateSet">
<stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
<stringbundle id="updateStrings" src="chrome://mozapps/locale/extensions/update.properties"/>
</stringbundleset>
<wizardpage id="dummy" pageid="dummy"/>
<wizardpage id="offline" pageid="offline" next="versioninfo"
label="&offline.title;"
onpageadvanced="return gOfflinePage.onPageAdvanced();">
<description>&offline.description;</description>
<checkbox id="toggleOffline"
checked="true"
label="&offline.toggleOffline.label;"
accesskey="&offline.toggleOffline.accesskey;"
oncommand="gOfflinePage.toggleOffline();"/>
</wizardpage>
<wizardpage id="versioninfo" pageid="versioninfo" next="mismatch"
label="&versioninfo.wizard.title;"
onpageshow="gVersionInfoPage.onPageShow();">
<label>&versioninfo.top.label;</label>
<separator class="thin"/>
<progressmeter id="versioninfo.progress" mode="undetermined"/>
<hbox align="center">
<image id="versioninfo.throbber" class="throbber"/>
<label flex="1" id="versioninfo.status" crop="right">&versioninfo.waiting;</label>
</hbox>
<separator/>
</wizardpage>
<wizardpage id="mismatch" pageid="mismatch" next="checking"
label="&mismatch.win.title;"
onpageshow="gMismatchPage.onPageShow();">
<label>&mismatch.top.label;</label>
<separator class="thin"/>
<listbox id="mismatch.incompatible" flex="1"/>
<separator class="thin"/>
<label>&mismatch.bottom.label;</label>
</wizardpage>
<wizardpage id="checking" pageid="checking" next="noupdates"
label="&checking.wizard.title;"
onpageshow="gUpdatePage.onPageShow();">
<label>&checking.top.label;</label>
<separator class="thin"/>
<progressmeter id="checking.progress"/>
<hbox align="center">
<image id="checking.throbber" class="throbber"/>
<label id="checking.status" flex="1" crop="right">&checking.status;</label>
</hbox>
</wizardpage>
<wizardpage id="noupdates" pageid="noupdates"
label="&noupdates.wizard.title;"
onpageshow="gNoUpdatesPage.onPageShow();">
<description>&noupdates.intro.desc;</description>
<separator class="thin"/>
<hbox id="updateCheckErrorNotFound" class="alertBox" hidden="true" align="top">
<image id="alert"/>
<description flex="1">&noupdates.error.desc;</description>
</hbox>
<separator class="thin"/>
<description id="noupdatesCheckEnabled" hidden="true">
&noupdates.checkEnabled.desc;
</description>
<vbox id="noupdatesCheckDisabled" hidden="true">
<description>&finished.checkDisabled.desc;</description>
<checkbox label="&enableChecking.label;" checked="true"
oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
</vbox>
<separator flex="1"/>
#ifndef XP_MACOSX
<label>&clickFinish.label;</label>
#else
<label>&clickFinish.labelMac;</label>
#endif
<separator class="thin"/>
</wizardpage>
<wizardpage id="found" pageid="found" next="installing"
label="&found.wizard.title;"
onpageshow="gFoundPage.onPageShow();">
<label>&found.top.label;</label>
<separator class="thin"/>
<listbox id="found.updates" flex="1" seltype="multiple"
onclick="gFoundPage.updateNextButton();"/>
<separator class="thin"/>
<vbox align="left" id="xpinstallDisabledAlert" hidden="true">
<description>&found.disabledXPinstall.label;</description>
<checkbox label="&found.enableXPInstall.label;"
id="enableXPInstall"
accesskey="&found.enableXPInstall.accesskey;"
oncommand="gFoundPage.toggleXPInstallEnable(event);"/>
</vbox>
</wizardpage>
<wizardpage id="installing" pageid="installing" next="finished"
label="&installing.wizard.title;"
onpageshow="gInstallingPage.onPageShow();">
<label>&installing.top.label;</label>
<progressmeter id="downloadProgress"/>
<hbox align="center">
<image id="installing.throbber" class="throbber"/>
<label id="actionItem" flex="1" crop="right"/>
</hbox>
<separator/>
</wizardpage>
<wizardpage id="installerrors" pageid="installerrors"
label="&installerrors.wizard.title;"
onpageshow="gInstallErrorsPage.onPageShow();">
<hbox align="top" class="alertBox">
<description flex="1">&installerrors.intro.label;</description>
</hbox>
<separator flex="1"/>
#ifndef XP_MACOSX
<label>&clickFinish.label;</label>
#else
<label>&clickFinish.labelMac;</label>
#endif
<separator class="thin"/>
</wizardpage>
<wizardpage id="adminDisabled" pageid="adminDisabled"
label="&adminDisabled.wizard.title;"
onpageshow="gAdminDisabledPage.onPageShow();">
<separator/>
<hbox class="alertBox" align="top">
<image id="alert"/>
<description flex="1">&adminDisabled.warning.label;</description>
</hbox>
<separator flex="1"/>
#ifndef XP_MACOSX
<label>&clickFinish.label;</label>
#else
<label>&clickFinish.labelMac;</label>
#endif
<separator class="thin"/>
</wizardpage>
<wizardpage id="finished" pageid="finished"
label="&finished.wizard.title;"
onpageshow="gFinishedPage.onPageShow();">
<label>&finished.top.label;</label>
<separator/>
<description id="finishedCheckEnabled" hidden="true">
&finished.checkEnabled.desc;
</description>
<vbox id="finishedCheckDisabled" hidden="true">
<description>&finished.checkDisabled.desc;</description>
<checkbox label="&enableChecking.label;" checked="true"
oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
</vbox>
<separator flex="1"/>
#ifndef XP_MACOSX
<label>&clickFinish.label;</label>
#else
<label>&clickFinish.labelMac;</label>
#endif
<separator class="thin"/>
</wizardpage>
</wizard>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<xsl:stylesheet version="1.0" xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Any elements not otherwise specified will be stripped but the contents
will be displayed. All attributes are stripped from copied elements. -->
<!-- Block these elements and their contents -->
<xsl:template match="xhtml:head|xhtml:script|xhtml:style">
</xsl:template>
<!-- Allowable styling elements -->
<xsl:template match="xhtml:b|xhtml:i|xhtml:em|xhtml:strong|xhtml:u|xhtml:q|xhtml:sub|xhtml:sup|xhtml:code">
<xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>
<!-- Allowable block formatting elements -->
<xsl:template match="xhtml:h1|xhtml:h2|xhtml:h3|xhtml:p|xhtml:div|xhtml:blockquote|xhtml:pre">
<xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>
<!-- Allowable list formatting elements -->
<xsl:template match="xhtml:ul|xhtml:ol|xhtml:li|xhtml:dl|xhtml:dt|xhtml:dd">
<xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>
<!-- These elements are copied and their contents dropped -->
<xsl:template match="xhtml:br|xhtml:hr">
<xsl:copy/>
</xsl:template>
<!-- The root document -->
<xsl:template match="/">
<xhtml:body><xsl:apply-templates/></xhtml:body>
</xsl:template>
</xsl:stylesheet>

View file

@ -0,0 +1,8 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
installitem {
-moz-binding: url("chrome://mozapps/content/xpinstall/xpinstallItem.xml#installitem");
display: -moz-box;
}

View file

@ -0,0 +1,196 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var XPInstallConfirm = {};
XPInstallConfirm.init = function XPInstallConfirm_init()
{
Components.utils.import("resource://gre/modules/AddonManager.jsm");
var _installCountdown;
var _installCountdownInterval;
var _focused;
var _timeout;
// Default to cancelling the install when the window unloads
XPInstallConfirm._installOK = false;
var bundle = document.getElementById("xpinstallConfirmStrings");
let args = window.arguments[0].wrappedJSObject;
// If all installs have already been cancelled in some way then just close
// the window
if (args.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)) {
window.close();
return;
}
var _installCountdownLength = 5;
try {
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
var delay_in_milliseconds = prefs.getIntPref("security.dialog_enable_delay");
_installCountdownLength = Math.round(delay_in_milliseconds / 500);
} catch (ex) { }
var itemList = document.getElementById("itemList");
let installMap = new WeakMap();
let installListener = {
onDownloadCancelled: function(install) {
itemList.removeChild(installMap.get(install));
if (--numItemsToInstall == 0)
window.close();
}
};
var numItemsToInstall = args.installs.length;
for (let install of args.installs) {
var installItem = document.createElement("installitem");
itemList.appendChild(installItem);
installItem.name = install.addon.name;
installItem.url = install.sourceURI.spec;
var icon = install.iconURL;
if (icon)
installItem.icon = icon;
var type = install.type;
if (type)
installItem.type = type;
if (install.certName) {
installItem.cert = bundle.getFormattedString("signed", [install.certName]);
}
else {
installItem.cert = bundle.getString("unverified");
}
installItem.signed = install.certName ? "true" : "false";
installMap.set(install, installItem);
install.addListener(installListener);
}
var introString = bundle.getString("itemWarnIntroSingle");
if (numItemsToInstall > 4)
introString = bundle.getFormattedString("itemWarnIntroMultiple", [numItemsToInstall]);
var textNode = document.createTextNode(introString);
var introNode = document.getElementById("itemWarningIntro");
while (introNode.hasChildNodes())
introNode.removeChild(introNode.firstChild);
introNode.appendChild(textNode);
var okButton = document.documentElement.getButton("accept");
okButton.focus();
function okButtonCountdown() {
_installCountdown -= 1;
if (_installCountdown < 1) {
okButton.label = bundle.getString("installButtonLabel");
okButton.disabled = false;
clearInterval(_installCountdownInterval);
}
else
okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
}
function myfocus() {
// Clear the timeout if it exists so only the last one will be used.
if (_timeout)
clearTimeout(_timeout);
// Use setTimeout since the last focus or blur event to fire is the one we
// want
_timeout = setTimeout(setWidgetsAfterFocus, 0);
}
function setWidgetsAfterFocus() {
if (_focused)
return;
_installCountdown = _installCountdownLength;
_installCountdownInterval = setInterval(okButtonCountdown, 500);
okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
_focused = true;
}
function myblur() {
// Clear the timeout if it exists so only the last one will be used.
if (_timeout)
clearTimeout(_timeout);
// Use setTimeout since the last focus or blur event to fire is the one we
// want
_timeout = setTimeout(setWidgetsAfterBlur, 0);
}
function setWidgetsAfterBlur() {
if (!_focused)
return;
// Set _installCountdown to the inital value set in setWidgetsAfterFocus
// plus 1 so when the window is focused there is immediate ui feedback.
_installCountdown = _installCountdownLength + 1;
okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
okButton.disabled = true;
clearInterval(_installCountdownInterval);
_focused = false;
}
function myUnload() {
if (_installCountdownLength > 0) {
document.removeEventListener("focus", myfocus, true);
document.removeEventListener("blur", myblur, true);
}
window.removeEventListener("unload", myUnload, false);
for (let install of args.installs)
install.removeListener(installListener);
// Now perform the desired action - either install the
// addons or cancel the installations
if (XPInstallConfirm._installOK) {
for (let install of args.installs)
install.install();
}
else {
for (let install of args.installs) {
if (install.state != AddonManager.STATE_CANCELLED)
install.cancel();
}
}
}
window.addEventListener("unload", myUnload, false);
if (_installCountdownLength > 0) {
document.addEventListener("focus", myfocus, true);
document.addEventListener("blur", myblur, true);
okButton.disabled = true;
setWidgetsAfterFocus();
}
else
okButton.label = bundle.getString("installButtonLabel");
}
XPInstallConfirm.onOK = function XPInstallConfirm_onOk()
{
Components.classes["@mozilla.org/base/telemetry;1"].
getService(Components.interfaces.nsITelemetry).
getHistogramById("SECURITY_UI").
add(Components.interfaces.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
// Perform the install or cancel after the window has unloaded
XPInstallConfirm._installOK = true;
return true;
}
XPInstallConfirm.onCancel = function XPInstallConfirm_onCancel()
{
// Perform the install or cancel after the window has unloaded
XPInstallConfirm._installOK = false;
return true;
}

View file

@ -0,0 +1,37 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://mozapps/content/xpinstall/xpinstallConfirm.css" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/skin/xpinstall/xpinstallConfirm.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://mozapps/locale/xpinstall/xpinstallConfirm.dtd">
<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="xpinstallConfirm" title="&dialog.title;" style="&dialog.style;"
windowtype="Addons:Install"
onload="XPInstallConfirm.init()"
ondialogaccept="return XPInstallConfirm.onOK();"
ondialogcancel="return XPInstallConfirm.onCancel();">
<script src="chrome://mozapps/content/xpinstall/xpinstallConfirm.js" type="application/javascript"/>
<stringbundle id="xpinstallConfirmStrings"
src="chrome://mozapps/locale/xpinstall/xpinstallConfirm.properties"/>
<vbox flex="1" id="dialogContentBox">
<hbox id="xpinstallheader" align="start">
<image class="alert-icon"/>
<vbox flex="1">
<description class="warning">&warningPrimary.label;</description>
<description>&warningSecondary.label;</description>
</vbox>
</hbox>
<label id="itemWarningIntro"/>
<vbox id="itemList" class="listbox" flex="1" style="overflow: auto;"/>
</vbox>
</dialog>

View file

@ -0,0 +1,51 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE bindings SYSTEM "chrome://mozapps/locale/xpinstall/xpinstallConfirm.dtd">
<bindings id="xpinstallItemBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="installitem">
<resources>
<stylesheet src="chrome://mozapps/skin/xpinstall/xpinstallConfirm.css"/>
</resources>
<content>
<xul:hbox flex="1">
<xul:vbox align="center" pack="center" class="xpinstallIconContainer">
<xul:image class="xpinstallItemIcon" xbl:inherits="src=icon"/>
</xul:vbox>
<xul:vbox flex="1" pack="center">
<xul:hbox class="xpinstallItemNameRow" align="center">
<xul:label class="xpinstallItemName" xbl:inherits="value=name" crop="right"/>
<xul:label class="xpinstallItemSigned" xbl:inherits="value=cert,signed"/>
</xul:hbox>
<xul:hbox class="xpinstallItemDetailsRow" align="center">
<xul:textbox class="xpinstallItemURL" xbl:inherits="value=url" flex="1" readonly="true" crop="right"/>
</xul:hbox>
</xul:vbox>
</xul:hbox>
</content>
<implementation>
<property name="name" onset="this.setAttribute('name', val); return val;"
onget="return this.getAttribute('name');"/>
<property name="cert" onset="this.setAttribute('cert', val); return val;"
onget="return this.getAttribute('cert');"/>
<property name="signed" onset="this.setAttribute('signed', val); return val;"
onget="return this.getAttribute('signed');"/>
<property name="url" onset="this.setAttribute('url', val); return val;"
onget="return this.getAttribute('url');"/>
<property name="icon" onset="this.setAttribute('icon', val); return val;"
onget="return this.getAttribute('icon');"/>
<property name="type" onset="this.setAttribute('type', val); return val;"
onget="return this.getAttribute('type');"/>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,20 @@
component {66354bc9-7ed1-4692-ae1d-8da97d6b205e} nsBlocklistService.js
contract @mozilla.org/extensions/blocklist;1 {66354bc9-7ed1-4692-ae1d-8da97d6b205e}
category profile-after-change nsBlocklistService @mozilla.org/extensions/blocklist;1
#ifndef MOZ_WIDGET_GONK
category update-timer nsBlocklistService @mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400
component {4399533d-08d1-458c-a87a-235f74451cfa} addonManager.js
contract @mozilla.org/addons/integration;1 {4399533d-08d1-458c-a87a-235f74451cfa}
category update-timer addonManager @mozilla.org/addons/integration;1,getService,addon-background-update-timer,extensions.update.interval,86400
component {7beb3ba8-6ec3-41b4-b67c-da89b8518922} amContentHandler.js
contract @mozilla.org/uriloader/content-handler;1?type=application/x-xpinstall {7beb3ba8-6ec3-41b4-b67c-da89b8518922}
component {0f38e086-89a3-40a5-8ffc-9b694de1d04a} amWebInstallListener.js
contract @mozilla.org/addons/web-install-listener;1 {0f38e086-89a3-40a5-8ffc-9b694de1d04a}
component {9df8ef2b-94da-45c9-ab9f-132eb55fddf1} amInstallTrigger.js
contract @mozilla.org/addons/installtrigger;1 {9df8ef2b-94da-45c9-ab9f-132eb55fddf1}
category JavaScript-global-property InstallTrigger @mozilla.org/addons/installtrigger;1
#ifndef MOZ_WIDGET_ANDROID
category addon-provider-module PluginProvider resource://gre/modules/addons/PluginProvider.jsm
#endif
category addon-provider-module GMPProvider resource://gre/modules/addons/GMPProvider.jsm
#endif

View file

@ -0,0 +1,180 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const KEY_PROFILEDIR = "ProfD";
const FILE_EXTENSIONS_LOG = "extensions.log";
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
const LOGGER_FILE_PERM = parseInt("666", 8);
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
Components.utils.import("resource://gre/modules/FileUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = [ "LogManager" ];
var gDebugLogEnabled = false;
function formatLogMessage(aType, aName, aStr, aException) {
let message = aType.toUpperCase() + " " + aName + ": " + aStr;
if (aException) {
if (typeof aException == "number")
return message + ": " + Components.Exception("", aException).name;
message = message + ": " + aException;
// instanceOf doesn't work here, let's duck type
if (aException.fileName)
message = message + " (" + aException.fileName + ":" + aException.lineNumber + ")";
if (aException.message == "too much recursion")
dump(message + "\n" + aException.stack + "\n");
}
return message;
}
function getStackDetails(aException) {
// Defensively wrap all this to ensure that failing to get the message source
// doesn't stop the message from being logged
try {
if (aException) {
if (aException instanceof Ci.nsIException) {
return {
sourceName: aException.filename,
lineNumber: aException.lineNumber
};
}
if (typeof aException == "object") {
return {
sourceName: aException.fileName,
lineNumber: aException.lineNumber
};
}
}
let stackFrame = Components.stack.caller.caller.caller;
return {
sourceName: stackFrame.filename,
lineNumber: stackFrame.lineNumber
};
}
catch (e) {
return {
sourceName: null,
lineNumber: 0
};
}
}
function AddonLogger(aName) {
this.name = aName;
}
AddonLogger.prototype = {
name: null,
error: function AddonLogger_error(aStr, aException) {
let message = formatLogMessage("error", this.name, aStr, aException);
let stack = getStackDetails(aException);
let consoleMessage = Cc["@mozilla.org/scripterror;1"].
createInstance(Ci.nsIScriptError);
consoleMessage.init(message, stack.sourceName, null, stack.lineNumber, 0,
Ci.nsIScriptError.errorFlag, "component javascript");
Services.console.logMessage(consoleMessage);
// Always dump errors, in case the Console Service isn't listening yet
dump("*** " + message + "\n");
try {
var tstamp = new Date();
var logfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_LOG]);
var stream = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
stream.init(logfile, 0x02 | 0x08 | 0x10, LOGGER_FILE_PERM, 0); // write, create, append
var writer = Cc["@mozilla.org/intl/converter-output-stream;1"].
createInstance(Ci.nsIConverterOutputStream);
writer.init(stream, "UTF-8", 0, 0x0000);
writer.writeString(tstamp.toLocaleFormat("%Y-%m-%d %H:%M:%S ") +
message + " at " + stack.sourceName + ":" +
stack.lineNumber + "\n");
writer.close();
}
catch (e) { }
},
warn: function AddonLogger_warn(aStr, aException) {
let message = formatLogMessage("warn", this.name, aStr, aException);
let stack = getStackDetails(aException);
let consoleMessage = Cc["@mozilla.org/scripterror;1"].
createInstance(Ci.nsIScriptError);
consoleMessage.init(message, stack.sourceName, null, stack.lineNumber, 0,
Ci.nsIScriptError.warningFlag, "component javascript");
Services.console.logMessage(consoleMessage);
if (gDebugLogEnabled)
dump("*** " + message + "\n");
},
log: function AddonLogger_log(aStr, aException) {
if (gDebugLogEnabled) {
let message = formatLogMessage("log", this.name, aStr, aException);
dump("*** " + message + "\n");
Services.console.logStringMessage(message);
}
}
};
this.LogManager = {
getLogger: function LogManager_getLogger(aName, aTarget) {
let logger = new AddonLogger(aName);
if (aTarget) {
["error", "warn", "log"].forEach(function(name) {
let fname = name.toUpperCase();
delete aTarget[fname];
aTarget[fname] = function LogManager_targetName(aStr, aException) {
logger[name](aStr, aException);
};
});
}
return logger;
}
};
var PrefObserver = {
init: function PrefObserver_init() {
Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
Services.obs.addObserver(this, "xpcom-shutdown", false);
this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
},
observe: function PrefObserver_observe(aSubject, aTopic, aData) {
if (aTopic == "xpcom-shutdown") {
Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
Services.obs.removeObserver(this, "xpcom-shutdown");
}
else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
try {
gDebugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED);
}
catch (e) {
gDebugLogEnabled = false;
}
}
}
};
PrefObserver.init();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,518 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
const KEY_PROFILEDIR = "ProfD";
const FILE_DATABASE = "addons.sqlite";
const LAST_DB_SCHEMA = 4;
// Add-on properties present in the columns of the database
const PROP_SINGLE = ["id", "type", "name", "version", "creator", "description",
"fullDescription", "developerComments", "eula",
"homepageURL", "supportURL", "contributionURL",
"contributionAmount", "averageRating", "reviewCount",
"reviewURL", "totalDownloads", "weeklyDownloads",
"dailyUsers", "sourceURI", "repositoryStatus", "size",
"updateDate"];
Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.repository.sqlmigrator";
// Create a new logger for use by the Addons Repository SQL Migrator
// (Requires AddonManager.jsm)
let logger = Log.repository.getLogger(LOGGER_ID);
this.EXPORTED_SYMBOLS = ["AddonRepository_SQLiteMigrator"];
this.AddonRepository_SQLiteMigrator = {
/**
* Migrates data from a previous SQLite version of the
* database to the JSON version.
*
* @param structFunctions an object that contains functions
* to create the various objects used
* in the new JSON format
* @param aCallback A callback to be called when migration
* finishes, with the results in an array
* @returns bool True if a migration will happen (DB was
* found and succesfully opened)
*/
migrate: function(aCallback) {
if (!this._openConnection()) {
this._closeConnection();
aCallback([]);
return false;
}
logger.debug("Importing addon repository from previous " + FILE_DATABASE + " storage.");
this._retrieveStoredData((results) => {
this._closeConnection();
let resultArray = [addon for ([,addon] of Iterator(results))];
logger.debug(resultArray.length + " addons imported.")
aCallback(resultArray);
});
return true;
},
/**
* Synchronously opens a new connection to the database file.
*
* @return bool Whether the DB was opened successfully.
*/
_openConnection: function AD_openConnection() {
delete this.connection;
let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
if (!dbfile.exists())
return false;
try {
this.connection = Services.storage.openUnsharedDatabase(dbfile);
} catch (e) {
return false;
}
this.connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
// Any errors in here should rollback
try {
this.connection.beginTransaction();
switch (this.connection.schemaVersion) {
case 0:
return false;
case 1:
logger.debug("Upgrading database schema to version 2");
this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN width INTEGER");
this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN height INTEGER");
this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailWidth INTEGER");
this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailHeight INTEGER");
case 2:
logger.debug("Upgrading database schema to version 3");
this.connection.createTable("compatibility_override",
"addon_internal_id INTEGER, " +
"num INTEGER, " +
"type TEXT, " +
"minVersion TEXT, " +
"maxVersion TEXT, " +
"appID TEXT, " +
"appMinVersion TEXT, " +
"appMaxVersion TEXT, " +
"PRIMARY KEY (addon_internal_id, num)");
case 3:
logger.debug("Upgrading database schema to version 4");
this.connection.createTable("icon",
"addon_internal_id INTEGER, " +
"size INTEGER, " +
"url TEXT, " +
"PRIMARY KEY (addon_internal_id, size)");
this._createIndices();
this._createTriggers();
this.connection.schemaVersion = LAST_DB_SCHEMA;
case LAST_DB_SCHEMA:
break;
default:
return false;
}
this.connection.commitTransaction();
} catch (e) {
logger.error("Failed to open " + FILE_DATABASE + ". Data import will not happen.", e);
this.logSQLError(this.connection.lastError, this.connection.lastErrorString);
this.connection.rollbackTransaction();
return false;
}
return true;
},
_closeConnection: function() {
for each (let stmt in this.asyncStatementsCache)
stmt.finalize();
this.asyncStatementsCache = {};
if (this.connection)
this.connection.asyncClose();
delete this.connection;
},
/**
* Asynchronously retrieve all add-ons from the database, and pass it
* to the specified callback
*
* @param aCallback
* The callback to pass the add-ons back to
*/
_retrieveStoredData: function AD_retrieveStoredData(aCallback) {
let self = this;
let addons = {};
// Retrieve all data from the addon table
function getAllAddons() {
self.getAsyncStatement("getAllAddons").executeAsync({
handleResult: function getAllAddons_handleResult(aResults) {
let row = null;
while ((row = aResults.getNextRow())) {
let internal_id = row.getResultByName("internal_id");
addons[internal_id] = self._makeAddonFromAsyncRow(row);
}
},
handleError: self.asyncErrorLogger,
handleCompletion: function getAllAddons_handleCompletion(aReason) {
if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
logger.error("Error retrieving add-ons from database. Returning empty results");
aCallback({});
return;
}
getAllDevelopers();
}
});
}
// Retrieve all data from the developer table
function getAllDevelopers() {
self.getAsyncStatement("getAllDevelopers").executeAsync({
handleResult: function getAllDevelopers_handleResult(aResults) {
let row = null;
while ((row = aResults.getNextRow())) {
let addon_internal_id = row.getResultByName("addon_internal_id");
if (!(addon_internal_id in addons)) {
logger.warn("Found a developer not linked to an add-on in database");
continue;
}
let addon = addons[addon_internal_id];
if (!addon.developers)
addon.developers = [];
addon.developers.push(self._makeDeveloperFromAsyncRow(row));
}
},
handleError: self.asyncErrorLogger,
handleCompletion: function getAllDevelopers_handleCompletion(aReason) {
if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
logger.error("Error retrieving developers from database. Returning empty results");
aCallback({});
return;
}
getAllScreenshots();
}
});
}
// Retrieve all data from the screenshot table
function getAllScreenshots() {
self.getAsyncStatement("getAllScreenshots").executeAsync({
handleResult: function getAllScreenshots_handleResult(aResults) {
let row = null;
while ((row = aResults.getNextRow())) {
let addon_internal_id = row.getResultByName("addon_internal_id");
if (!(addon_internal_id in addons)) {
logger.warn("Found a screenshot not linked to an add-on in database");
continue;
}
let addon = addons[addon_internal_id];
if (!addon.screenshots)
addon.screenshots = [];
addon.screenshots.push(self._makeScreenshotFromAsyncRow(row));
}
},
handleError: self.asyncErrorLogger,
handleCompletion: function getAllScreenshots_handleCompletion(aReason) {
if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
logger.error("Error retrieving screenshots from database. Returning empty results");
aCallback({});
return;
}
getAllCompatOverrides();
}
});
}
function getAllCompatOverrides() {
self.getAsyncStatement("getAllCompatOverrides").executeAsync({
handleResult: function getAllCompatOverrides_handleResult(aResults) {
let row = null;
while ((row = aResults.getNextRow())) {
let addon_internal_id = row.getResultByName("addon_internal_id");
if (!(addon_internal_id in addons)) {
logger.warn("Found a compatibility override not linked to an add-on in database");
continue;
}
let addon = addons[addon_internal_id];
if (!addon.compatibilityOverrides)
addon.compatibilityOverrides = [];
addon.compatibilityOverrides.push(self._makeCompatOverrideFromAsyncRow(row));
}
},
handleError: self.asyncErrorLogger,
handleCompletion: function getAllCompatOverrides_handleCompletion(aReason) {
if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
logger.error("Error retrieving compatibility overrides from database. Returning empty results");
aCallback({});
return;
}
getAllIcons();
}
});
}
function getAllIcons() {
self.getAsyncStatement("getAllIcons").executeAsync({
handleResult: function getAllIcons_handleResult(aResults) {
let row = null;
while ((row = aResults.getNextRow())) {
let addon_internal_id = row.getResultByName("addon_internal_id");
if (!(addon_internal_id in addons)) {
logger.warn("Found an icon not linked to an add-on in database");
continue;
}
let addon = addons[addon_internal_id];
let { size, url } = self._makeIconFromAsyncRow(row);
addon.icons[size] = url;
if (size == 32)
addon.iconURL = url;
}
},
handleError: self.asyncErrorLogger,
handleCompletion: function getAllIcons_handleCompletion(aReason) {
if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
logger.error("Error retrieving icons from database. Returning empty results");
aCallback({});
return;
}
let returnedAddons = {};
for each (let addon in addons)
returnedAddons[addon.id] = addon;
aCallback(returnedAddons);
}
});
}
// Begin asynchronous process
getAllAddons();
},
// A cache of statements that are used and need to be finalized on shutdown
asyncStatementsCache: {},
/**
* Gets a cached async statement or creates a new statement if it doesn't
* already exist.
*
* @param aKey
* A unique key to reference the statement
* @return a mozIStorageAsyncStatement for the SQL corresponding to the
* unique key
*/
getAsyncStatement: function AD_getAsyncStatement(aKey) {
if (aKey in this.asyncStatementsCache)
return this.asyncStatementsCache[aKey];
let sql = this.queries[aKey];
try {
return this.asyncStatementsCache[aKey] = this.connection.createAsyncStatement(sql);
} catch (e) {
logger.error("Error creating statement " + aKey + " (" + sql + ")");
throw Components.Exception("Error creating statement " + aKey + " (" + sql + "): " + e,
e.result);
}
},
// The queries used by the database
queries: {
getAllAddons: "SELECT internal_id, id, type, name, version, " +
"creator, creatorURL, description, fullDescription, " +
"developerComments, eula, homepageURL, supportURL, " +
"contributionURL, contributionAmount, averageRating, " +
"reviewCount, reviewURL, totalDownloads, weeklyDownloads, " +
"dailyUsers, sourceURI, repositoryStatus, size, updateDate " +
"FROM addon",
getAllDevelopers: "SELECT addon_internal_id, name, url FROM developer " +
"ORDER BY addon_internal_id, num",
getAllScreenshots: "SELECT addon_internal_id, url, width, height, " +
"thumbnailURL, thumbnailWidth, thumbnailHeight, caption " +
"FROM screenshot ORDER BY addon_internal_id, num",
getAllCompatOverrides: "SELECT addon_internal_id, type, minVersion, " +
"maxVersion, appID, appMinVersion, appMaxVersion " +
"FROM compatibility_override " +
"ORDER BY addon_internal_id, num",
getAllIcons: "SELECT addon_internal_id, size, url FROM icon " +
"ORDER BY addon_internal_id, size",
},
/**
* Make add-on structure from an asynchronous row.
*
* @param aRow
* The asynchronous row to use
* @return The created add-on
*/
_makeAddonFromAsyncRow: function AD__makeAddonFromAsyncRow(aRow) {
// This is intentionally not an AddonSearchResult object in order
// to allow AddonDatabase._parseAddon to parse it, same as if it
// was read from the JSON database.
let addon = { icons: {} };
for (let prop of PROP_SINGLE) {
addon[prop] = aRow.getResultByName(prop)
};
return addon;
},
/**
* Make a developer from an asynchronous row
*
* @param aRow
* The asynchronous row to use
* @return The created developer
*/
_makeDeveloperFromAsyncRow: function AD__makeDeveloperFromAsyncRow(aRow) {
let name = aRow.getResultByName("name");
let url = aRow.getResultByName("url")
return new AddonManagerPrivate.AddonAuthor(name, url);
},
/**
* Make a screenshot from an asynchronous row
*
* @param aRow
* The asynchronous row to use
* @return The created screenshot
*/
_makeScreenshotFromAsyncRow: function AD__makeScreenshotFromAsyncRow(aRow) {
let url = aRow.getResultByName("url");
let width = aRow.getResultByName("width");
let height = aRow.getResultByName("height");
let thumbnailURL = aRow.getResultByName("thumbnailURL");
let thumbnailWidth = aRow.getResultByName("thumbnailWidth");
let thumbnailHeight = aRow.getResultByName("thumbnailHeight");
let caption = aRow.getResultByName("caption");
return new AddonManagerPrivate.AddonScreenshot(url, width, height, thumbnailURL,
thumbnailWidth, thumbnailHeight, caption);
},
/**
* Make a CompatibilityOverride from an asynchronous row
*
* @param aRow
* The asynchronous row to use
* @return The created CompatibilityOverride
*/
_makeCompatOverrideFromAsyncRow: function AD_makeCompatOverrideFromAsyncRow(aRow) {
let type = aRow.getResultByName("type");
let minVersion = aRow.getResultByName("minVersion");
let maxVersion = aRow.getResultByName("maxVersion");
let appID = aRow.getResultByName("appID");
let appMinVersion = aRow.getResultByName("appMinVersion");
let appMaxVersion = aRow.getResultByName("appMaxVersion");
return new AddonManagerPrivate.AddonCompatibilityOverride(type,
minVersion,
maxVersion,
appID,
appMinVersion,
appMaxVersion);
},
/**
* Make an icon from an asynchronous row
*
* @param aRow
* The asynchronous row to use
* @return An object containing the size and URL of the icon
*/
_makeIconFromAsyncRow: function AD_makeIconFromAsyncRow(aRow) {
let size = aRow.getResultByName("size");
let url = aRow.getResultByName("url");
return { size: size, url: url };
},
/**
* A helper function to log an SQL error.
*
* @param aError
* The storage error code associated with the error
* @param aErrorString
* An error message
*/
logSQLError: function AD_logSQLError(aError, aErrorString) {
logger.error("SQL error " + aError + ": " + aErrorString);
},
/**
* A helper function to log any errors that occur during async statements.
*
* @param aError
* A mozIStorageError to log
*/
asyncErrorLogger: function AD_asyncErrorLogger(aError) {
logger.error("Async SQL error " + aError.result + ": " + aError.message);
},
/**
* Synchronously creates the triggers in the database.
*/
_createTriggers: function AD__createTriggers() {
this.connection.executeSimpleSQL("DROP TRIGGER IF EXISTS delete_addon");
this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " +
"ON addon BEGIN " +
"DELETE FROM developer WHERE addon_internal_id=old.internal_id; " +
"DELETE FROM screenshot WHERE addon_internal_id=old.internal_id; " +
"DELETE FROM compatibility_override WHERE addon_internal_id=old.internal_id; " +
"DELETE FROM icon WHERE addon_internal_id=old.internal_id; " +
"END");
},
/**
* Synchronously creates the indices in the database.
*/
_createIndices: function AD__createIndices() {
this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS developer_idx " +
"ON developer (addon_internal_id)");
this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS screenshot_idx " +
"ON screenshot (addon_internal_id)");
this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS compatibility_override_idx " +
"ON compatibility_override (addon_internal_id)");
this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS icon_idx " +
"ON icon (addon_internal_id)");
}
}

View file

@ -0,0 +1,772 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* The AddonUpdateChecker is responsible for retrieving the update information
* from an add-on's remote update manifest.
*/
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = [ "AddonUpdateChecker" ];
const TIMEOUT = 60 * 1000;
const PREFIX_NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
const PREFIX_ITEM = "urn:mozilla:item:";
const PREFIX_EXTENSION = "urn:mozilla:extension:";
const PREFIX_THEME = "urn:mozilla:theme:";
const TOOLKIT_ID = "toolkit@mozilla.org"
#ifdef MOZ_PHOENIX_EXTENSIONS
const FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"
#endif
const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
const PREF_UPDATE_REQUIREBUILTINCERTS = "extensions.update.requireBuiltInCerts";
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
"resource://gre/modules/addons/AddonRepository.jsm");
// Shared code for suppressing bad cert dialogs.
XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() {
let certUtils = {};
Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
return certUtils;
});
var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
getService(Ci.nsIRDFService);
Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.update-checker";
// Create a new logger for use by the Addons Update Checker
// (Requires AddonManager.jsm)
let logger = Log.repository.getLogger(LOGGER_ID);
/**
* A serialisation method for RDF data that produces an identical string
* for matching RDF graphs.
* The serialisation is not complete, only assertions stemming from a given
* resource are included, multiple references to the same resource are not
* permitted, and the RDF prolog and epilog are not included.
* RDF Blob and Date literals are not supported.
*/
function RDFSerializer() {
this.cUtils = Cc["@mozilla.org/rdf/container-utils;1"].
getService(Ci.nsIRDFContainerUtils);
this.resources = [];
}
RDFSerializer.prototype = {
INDENT: " ", // The indent used for pretty-printing
resources: null, // Array of the resources that have been found
/**
* Escapes characters from a string that should not appear in XML.
*
* @param aString
* The string to be escaped
* @return a string with all characters invalid in XML character data
* converted to entity references.
*/
escapeEntities: function RDFS_escapeEntities(aString) {
aString = aString.replace(/&/g, "&amp;");
aString = aString.replace(/</g, "&lt;");
aString = aString.replace(/>/g, "&gt;");
return aString.replace(/"/g, "&quot;");
},
/**
* Serializes all the elements of an RDF container.
*
* @param aDs
* The RDF datasource
* @param aContainer
* The RDF container to output the child elements of
* @param aIndent
* The current level of indent for pretty-printing
* @return a string containing the serialized elements.
*/
serializeContainerItems: function RDFS_serializeContainerItems(aDs, aContainer,
aIndent) {
var result = "";
var items = aContainer.GetElements();
while (items.hasMoreElements()) {
var item = items.getNext().QueryInterface(Ci.nsIRDFResource);
result += aIndent + "<RDF:li>\n"
result += this.serializeResource(aDs, item, aIndent + this.INDENT);
result += aIndent + "</RDF:li>\n"
}
return result;
},
/**
* Serializes all em:* (see EM_NS) properties of an RDF resource except for
* the em:signature property. As this serialization is to be compared against
* the manifest signature it cannot contain the em:signature property itself.
*
* @param aDs
* The RDF datasource
* @param aResource
* The RDF resource that contains the properties to serialize
* @param aIndent
* The current level of indent for pretty-printing
* @return a string containing the serialized properties.
* @throws if the resource contains a property that cannot be serialized
*/
serializeResourceProperties: function RDFS_serializeResourceProperties(aDs,
aResource,
aIndent) {
var result = "";
var items = [];
var arcs = aDs.ArcLabelsOut(aResource);
while (arcs.hasMoreElements()) {
var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource);
if (arc.ValueUTF8.substring(0, PREFIX_NS_EM.length) != PREFIX_NS_EM)
continue;
var prop = arc.ValueUTF8.substring(PREFIX_NS_EM.length);
if (prop == "signature")
continue;
var targets = aDs.GetTargets(aResource, arc, true);
while (targets.hasMoreElements()) {
var target = targets.getNext();
if (target instanceof Ci.nsIRDFResource) {
var item = aIndent + "<em:" + prop + ">\n";
item += this.serializeResource(aDs, target, aIndent + this.INDENT);
item += aIndent + "</em:" + prop + ">\n";
items.push(item);
}
else if (target instanceof Ci.nsIRDFLiteral) {
items.push(aIndent + "<em:" + prop + ">" +
this.escapeEntities(target.Value) + "</em:" + prop + ">\n");
}
else if (target instanceof Ci.nsIRDFInt) {
items.push(aIndent + "<em:" + prop + " NC:parseType=\"Integer\">" +
target.Value + "</em:" + prop + ">\n");
}
else {
throw Components.Exception("Cannot serialize unknown literal type");
}
}
}
items.sort();
result += items.join("");
return result;
},
/**
* Recursively serializes an RDF resource and all resources it links to.
* This will only output EM_NS properties and will ignore any em:signature
* property.
*
* @param aDs
* The RDF datasource
* @param aResource
* The RDF resource to serialize
* @param aIndent
* The current level of indent for pretty-printing. If undefined no
* indent will be added
* @return a string containing the serialized resource.
* @throws if the RDF data contains multiple references to the same resource.
*/
serializeResource: function RDFS_serializeResource(aDs, aResource, aIndent) {
if (this.resources.indexOf(aResource) != -1 ) {
// We cannot output multiple references to the same resource.
throw Components.Exception("Cannot serialize multiple references to " + aResource.Value);
}
if (aIndent === undefined)
aIndent = "";
this.resources.push(aResource);
var container = null;
var type = "Description";
if (this.cUtils.IsSeq(aDs, aResource)) {
type = "Seq";
container = this.cUtils.MakeSeq(aDs, aResource);
}
else if (this.cUtils.IsAlt(aDs, aResource)) {
type = "Alt";
container = this.cUtils.MakeAlt(aDs, aResource);
}
else if (this.cUtils.IsBag(aDs, aResource)) {
type = "Bag";
container = this.cUtils.MakeBag(aDs, aResource);
}
var result = aIndent + "<RDF:" + type;
if (!gRDF.IsAnonymousResource(aResource))
result += " about=\"" + this.escapeEntities(aResource.ValueUTF8) + "\"";
result += ">\n";
if (container)
result += this.serializeContainerItems(aDs, container, aIndent + this.INDENT);
result += this.serializeResourceProperties(aDs, aResource, aIndent + this.INDENT);
result += aIndent + "</RDF:" + type + ">\n";
return result;
}
}
/**
* Parses an RDF style update manifest into an array of update objects.
*
* @param aId
* The ID of the add-on being checked for updates
* @param aUpdateKey
* An optional update key for the add-on
* @param aRequest
* The XMLHttpRequest that has retrieved the update manifest
* @return an array of update objects
* @throws if the update manifest is invalid in any way
*/
function parseRDFManifest(aId, aUpdateKey, aRequest) {
function EM_R(aProp) {
return gRDF.GetResource(PREFIX_NS_EM + aProp);
}
function getValue(aLiteral) {
if (aLiteral instanceof Ci.nsIRDFLiteral)
return aLiteral.Value;
if (aLiteral instanceof Ci.nsIRDFResource)
return aLiteral.Value;
if (aLiteral instanceof Ci.nsIRDFInt)
return aLiteral.Value;
return null;
}
function getProperty(aDs, aSource, aProperty) {
return getValue(aDs.GetTarget(aSource, EM_R(aProperty), true));
}
function getBooleanProperty(aDs, aSource, aProperty) {
let propValue = aDs.GetTarget(aSource, EM_R(aProperty), true);
if (!propValue)
return undefined;
return getValue(propValue) == "true";
}
function getRequiredProperty(aDs, aSource, aProperty) {
let value = getProperty(aDs, aSource, aProperty);
if (!value)
throw Components.Exception("Update manifest is missing a required " + aProperty + " property.");
return value;
}
let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
createInstance(Ci.nsIRDFXMLParser);
let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
createInstance(Ci.nsIRDFDataSource);
rdfParser.parseString(ds, aRequest.channel.URI, aRequest.responseText);
// Differentiating between add-on types is deprecated
let extensionRes = gRDF.GetResource(PREFIX_EXTENSION + aId);
let themeRes = gRDF.GetResource(PREFIX_THEME + aId);
let itemRes = gRDF.GetResource(PREFIX_ITEM + aId);
let addonRes = ds.ArcLabelsOut(extensionRes).hasMoreElements() ? extensionRes
: ds.ArcLabelsOut(themeRes).hasMoreElements() ? themeRes
: itemRes;
// If we have an update key then the update manifest must be signed
if (aUpdateKey) {
let signature = getProperty(ds, addonRes, "signature");
if (!signature)
throw Components.Exception("Update manifest for " + aId + " does not contain a required signature");
let serializer = new RDFSerializer();
let updateString = null;
try {
updateString = serializer.serializeResource(ds, addonRes);
}
catch (e) {
throw Components.Exception("Failed to generate signed string for " + aId + ". Serializer threw " + e,
e.result);
}
let result = false;
try {
let verifier = Cc["@mozilla.org/security/datasignatureverifier;1"].
getService(Ci.nsIDataSignatureVerifier);
result = verifier.verifyData(updateString, signature, aUpdateKey);
}
catch (e) {
throw Components.Exception("The signature or updateKey for " + aId + " is malformed." +
"Verifier threw " + e, e.result);
}
if (!result)
throw Components.Exception("The signature for " + aId + " was not created by the add-on's updateKey");
}
let updates = ds.GetTarget(addonRes, EM_R("updates"), true);
// A missing updates property doesn't count as a failure, just as no avialable
// update information
if (!updates) {
logger.warn("Update manifest for " + aId + " did not contain an updates property");
return [];
}
if (!(updates instanceof Ci.nsIRDFResource))
throw Components.Exception("Missing updates property for " + addonRes.Value);
let cu = Cc["@mozilla.org/rdf/container-utils;1"].
getService(Ci.nsIRDFContainerUtils);
if (!cu.IsContainer(ds, updates))
throw Components.Exception("Updates property was not an RDF container");
let results = [];
let ctr = Cc["@mozilla.org/rdf/container;1"].
createInstance(Ci.nsIRDFContainer);
ctr.Init(ds, updates);
let items = ctr.GetElements();
while (items.hasMoreElements()) {
let item = items.getNext().QueryInterface(Ci.nsIRDFResource);
let version = getProperty(ds, item, "version");
if (!version) {
logger.warn("Update manifest is missing a required version property.");
continue;
}
logger.debug("Found an update entry for " + aId + " version " + version);
let targetApps = ds.GetTargets(item, EM_R("targetApplication"), true);
while (targetApps.hasMoreElements()) {
let targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource);
let appEntry = {};
try {
appEntry.id = getRequiredProperty(ds, targetApp, "id");
appEntry.minVersion = getRequiredProperty(ds, targetApp, "minVersion");
appEntry.maxVersion = getRequiredProperty(ds, targetApp, "maxVersion");
}
catch (e) {
logger.warn(e);
continue;
}
let result = {
id: aId,
version: version,
multiprocessCompatible: getBooleanProperty(ds, item, "multiprocessCompatible"),
updateURL: getProperty(ds, targetApp, "updateLink"),
updateHash: getProperty(ds, targetApp, "updateHash"),
updateInfoURL: getProperty(ds, targetApp, "updateInfoURL"),
strictCompatibility: !!getBooleanProperty(ds, targetApp, "strictCompatibility"),
targetApplications: [appEntry]
};
if (result.updateURL && AddonManager.checkUpdateSecurity &&
result.updateURL.substring(0, 6) != "https:" &&
(!result.updateHash || result.updateHash.substring(0, 3) != "sha")) {
logger.warn("updateLink " + result.updateURL + " is not secure and is not verified" +
" by a strong enough hash (needs to be sha1 or stronger).");
delete result.updateURL;
delete result.updateHash;
}
results.push(result);
}
}
return results;
}
/**
* Starts downloading an update manifest and then passes it to an appropriate
* parser to convert to an array of update objects
*
* @param aId
* The ID of the add-on being checked for updates
* @param aUpdateKey
* An optional update key for the add-on
* @param aUrl
* The URL of the update manifest
* @param aObserver
* An observer to pass results to
*/
function UpdateParser(aId, aUpdateKey, aUrl, aObserver) {
this.id = aId;
this.updateKey = aUpdateKey;
this.observer = aObserver;
this.url = aUrl;
let requireBuiltIn = true;
try {
requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS);
}
catch (e) {
}
logger.debug("Requesting " + aUrl);
try {
this.request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
this.request.open("GET", this.url, true);
this.request.channel.notificationCallbacks = new CertUtils.BadCertHandler(!requireBuiltIn);
this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
// Prevent the request from writing to cache.
this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
this.request.overrideMimeType("text/xml");
this.request.setRequestHeader("Moz-XPI-Update", "1", true);
this.request.timeout = TIMEOUT;
var self = this;
this.request.addEventListener("load", function loadEventListener(event) { self.onLoad() }, false);
this.request.addEventListener("error", function errorEventListener(event) { self.onError() }, false);
this.request.addEventListener("timeout", function timeoutEventListener(event) { self.onTimeout() }, false);
this.request.send(null);
}
catch (e) {
logger.error("Failed to request update manifest", e);
}
}
UpdateParser.prototype = {
id: null,
updateKey: null,
observer: null,
request: null,
url: null,
/**
* Called when the manifest has been successfully loaded.
*/
onLoad: function UP_onLoad() {
let request = this.request;
this.request = null;
this._doneAt = new Error("place holder");
let requireBuiltIn = true;
try {
requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS);
}
catch (e) {
}
try {
CertUtils.checkCert(request.channel, !requireBuiltIn);
}
catch (e) {
this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
return;
}
if (!Components.isSuccessCode(request.status)) {
logger.warn("Request failed: " + this.url + " - " + request.status);
this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
return;
}
let channel = request.channel;
if (channel instanceof Ci.nsIHttpChannel && !channel.requestSucceeded) {
logger.warn("Request failed: " + this.url + " - " + channel.responseStatus +
": " + channel.responseStatusText);
this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
return;
}
let xml = request.responseXML;
if (!xml || xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) {
logger.warn("Update manifest was not valid XML");
this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
return;
}
// We currently only know about RDF update manifests
if (xml.documentElement.namespaceURI == PREFIX_NS_RDF) {
let results = null;
try {
results = parseRDFManifest(this.id, this.updateKey, request);
}
catch (e) {
logger.warn("onUpdateCheckComplete failed to parse RDF manifest", e);
this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
return;
}
if ("onUpdateCheckComplete" in this.observer) {
try {
this.observer.onUpdateCheckComplete(results);
}
catch (e) {
logger.warn("onUpdateCheckComplete notification failed", e);
}
}
else {
logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker"));
}
return;
}
logger.warn("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI);
this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT);
},
/**
* Called when the request times out
*/
onTimeout: function() {
this.request = null;
this._doneAt = new Error("Timed out");
logger.warn("Request for " + this.url + " timed out");
this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT);
},
/**
* Called when the manifest failed to load.
*/
onError: function UP_onError() {
if (!Components.isSuccessCode(this.request.status)) {
logger.warn("Request failed: " + this.url + " - " + this.request.status);
}
else if (this.request.channel instanceof Ci.nsIHttpChannel) {
try {
if (this.request.channel.requestSucceeded) {
logger.warn("Request failed: " + this.url + " - " +
this.request.channel.responseStatus + ": " +
this.request.channel.responseStatusText);
}
}
catch (e) {
logger.warn("HTTP Request failed for an unknown reason");
}
}
else {
logger.warn("Request failed for an unknown reason");
}
this.request = null;
this._doneAt = new Error("UP_onError");
this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
},
/**
* Helper method to notify the observer that an error occured.
*/
notifyError: function UP_notifyError(aStatus) {
if ("onUpdateCheckError" in this.observer) {
try {
this.observer.onUpdateCheckError(aStatus);
}
catch (e) {
logger.warn("onUpdateCheckError notification failed", e);
}
}
},
/**
* Called to cancel an in-progress update check.
*/
cancel: function UP_cancel() {
if (!this.request) {
logger.error("Trying to cancel already-complete request", this._doneAt);
return;
}
this.request.abort();
this.request = null;
this._doneAt = new Error("UP_cancel");
this.notifyError(AddonUpdateChecker.ERROR_CANCELLED);
}
};
/**
* Tests if an update matches a version of the application or platform
*
* @param aUpdate
* The available update
* @param aAppVersion
* The application version to use
* @param aPlatformVersion
* The platform version to use
* @param aIgnoreMaxVersion
* Ignore maxVersion when testing if an update matches. Optional.
* @param aIgnoreStrictCompat
* Ignore strictCompatibility when testing if an update matches. Optional.
* @param aCompatOverrides
* AddonCompatibilityOverride objects to match against. Optional.
* @return true if the update is compatible with the application/platform
*/
function matchesVersions(aUpdate, aAppVersion, aPlatformVersion,
aIgnoreMaxVersion, aIgnoreStrictCompat,
aCompatOverrides) {
if (aCompatOverrides) {
let override = AddonRepository.findMatchingCompatOverride(aUpdate.version,
aCompatOverrides,
aAppVersion,
aPlatformVersion);
if (override && override.type == "incompatible")
return false;
}
if (aUpdate.strictCompatibility && !aIgnoreStrictCompat)
aIgnoreMaxVersion = false;
let result = false;
for (let app of aUpdate.targetApplications) {
if (app.id == Services.appinfo.ID) {
return (Services.vc.compare(aAppVersion, app.minVersion) >= 0) &&
(aIgnoreMaxVersion || (Services.vc.compare(aAppVersion, app.maxVersion) <= 0));
}
#ifdef MOZ_PHOENIX_EXTENSIONS
if (app.id == FIREFOX_ID) {
return (Services.vc.compare(aAppVersion, app.minVersion) >= 0) &&
(aIgnoreMaxVersion || (Services.vc.compare(aAppVersion, app.maxVersion) <= 0));
}
#endif
if (app.id == TOOLKIT_ID) {
result = (Services.vc.compare(aPlatformVersion, app.minVersion) >= 0) &&
(aIgnoreMaxVersion || (Services.vc.compare(aPlatformVersion, app.maxVersion) <= 0));
}
}
return result;
}
this.AddonUpdateChecker = {
// These must be kept in sync with AddonManager
// The update check timed out
ERROR_TIMEOUT: -1,
// There was an error while downloading the update information.
ERROR_DOWNLOAD_ERROR: -2,
// The update information was malformed in some way.
ERROR_PARSE_ERROR: -3,
// The update information was not in any known format.
ERROR_UNKNOWN_FORMAT: -4,
// The update information was not correctly signed or there was an SSL error.
ERROR_SECURITY_ERROR: -5,
// The update was cancelled
ERROR_CANCELLED: -6,
/**
* Retrieves the best matching compatibility update for the application from
* a list of available update objects.
*
* @param aUpdates
* An array of update objects
* @param aVersion
* The version of the add-on to get new compatibility information for
* @param aIgnoreCompatibility
* An optional parameter to get the first compatibility update that
* is compatible with any version of the application or toolkit
* @param aAppVersion
* The version of the application or null to use the current version
* @param aPlatformVersion
* The version of the platform or null to use the current version
* @param aIgnoreMaxVersion
* Ignore maxVersion when testing if an update matches. Optional.
* @param aIgnoreStrictCompat
* Ignore strictCompatibility when testing if an update matches. Optional.
* @return an update object if one matches or null if not
*/
getCompatibilityUpdate: function AUC_getCompatibilityUpdate(aUpdates, aVersion,
aIgnoreCompatibility,
aAppVersion,
aPlatformVersion,
aIgnoreMaxVersion,
aIgnoreStrictCompat) {
if (!aAppVersion)
aAppVersion = Services.appinfo.version;
if (!aPlatformVersion)
aPlatformVersion = Services.appinfo.platformVersion;
for (let update of aUpdates) {
if (Services.vc.compare(update.version, aVersion) == 0) {
if (aIgnoreCompatibility) {
for (let targetApp of update.targetApplications) {
let id = targetApp.id;
#ifdef MOZ_PHOENIX_EXTENSIONS
if (id == Services.appinfo.ID || id == FIREFOX_ID ||
id == TOOLKIT_ID)
#else
if (id == Services.appinfo.ID || id == TOOLKIT_ID)
#endif
return update;
}
}
else if (matchesVersions(update, aAppVersion, aPlatformVersion,
aIgnoreMaxVersion, aIgnoreStrictCompat)) {
return update;
}
}
}
return null;
},
/**
* Returns the newest available update from a list of update objects.
*
* @param aUpdates
* An array of update objects
* @param aAppVersion
* The version of the application or null to use the current version
* @param aPlatformVersion
* The version of the platform or null to use the current version
* @param aIgnoreMaxVersion
* When determining compatible updates, ignore maxVersion. Optional.
* @param aIgnoreStrictCompat
* When determining compatible updates, ignore strictCompatibility. Optional.
* @param aCompatOverrides
* Array of AddonCompatibilityOverride to take into account. Optional.
* @return an update object if one matches or null if not
*/
getNewestCompatibleUpdate: function AUC_getNewestCompatibleUpdate(aUpdates,
aAppVersion,
aPlatformVersion,
aIgnoreMaxVersion,
aIgnoreStrictCompat,
aCompatOverrides) {
if (!aAppVersion)
aAppVersion = Services.appinfo.version;
if (!aPlatformVersion)
aPlatformVersion = Services.appinfo.platformVersion;
let blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
getService(Ci.nsIBlocklistService);
let newest = null;
for (let update of aUpdates) {
if (!update.updateURL)
continue;
let state = blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion);
if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
continue;
if ((newest == null || (Services.vc.compare(newest.version, update.version) < 0)) &&
matchesVersions(update, aAppVersion, aPlatformVersion,
aIgnoreMaxVersion, aIgnoreStrictCompat,
aCompatOverrides)) {
newest = update;
}
}
return newest;
},
/**
* Starts an update check.
*
* @param aId
* The ID of the add-on being checked for updates
* @param aUpdateKey
* An optional update key for the add-on
* @param aUrl
* The URL of the add-on's update manifest
* @param aObserver
* An observer to notify of results
* @return UpdateParser so that the caller can use UpdateParser.cancel() to shut
* down in-progress update requests
*/
checkForUpdates: function AUC_checkForUpdates(aId, aUpdateKey, aUrl,
aObserver) {
return new UpdateParser(aId, aUpdateKey, aUrl, aObserver);
}
};

View file

@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
(function() {
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
let nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
"initWithPath");
const MSG_JAR_FLUSH = "AddonJarFlush";
try {
if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
// Propagate JAR cache flush notifications across process boundaries.
addMessageListener(MSG_JAR_FLUSH, function jar_flushMessageListener(message) {
let file = new nsIFile(message.data);
Services.obs.notifyObservers(file, "flush-cache-entry", null);
});
}
} catch(e) {
Cu.reportError(e);
}
})();

View file

@ -0,0 +1,606 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = [];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/GMPUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(
this, "GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm");
XPCOMUtils.defineLazyModuleGetter(
this, "setTimeout", "resource://gre/modules/Timer.jsm");
const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME = "type.%ID%.name";
const SEC_IN_A_DAY = 24 * 60 * 60;
// How long to wait after a user enabled EME before attempting to download CDMs.
const GMP_CHECK_DELAY = 10 * 1000; // milliseconds
const NS_GRE_DIR = "GreD";
const CLEARKEY_PLUGIN_ID = "gmp-clearkey";
const CLEARKEY_VERSION = "0.1";
const GMP_LICENSE_INFO = "gmp_license_info";
const GMP_PLUGINS = [
{
id: OPEN_H264_ID,
name: "openH264_name",
description: "openH264_description",
// The following licenseURL is part of an awful hack to include the OpenH264
// license without having bug 624602 fixed yet, and intentionally ignores
// localisation.
licenseURL: "chrome://mozapps/content/extensions/OpenH264-license.txt",
homepageURL: "http://www.openh264.org/",
optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul"
},
{
id: EME_ADOBE_ID,
name: "eme-adobe_name",
description: "eme-adobe_description",
licenseURL: "http://help.adobe.com/en_US/primetime/drm/HTML5_CDM_EULA/index.html",
homepageURL: "http://help.adobe.com/en_US/primetime/drm/HTML5_CDM",
optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul",
isEME: true
}];
XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
() => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
XPCOMUtils.defineLazyGetter(this, "gmpService",
() => Cc["@mozilla.org/goanna-media-plugin-service;1"].getService(Ci.mozIGoannaMediaPluginService));
let messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
let gLogger;
let gLogAppenderDump = null;
function configureLogging() {
if (!gLogger) {
gLogger = Log.repository.getLogger("Toolkit.GMP");
gLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
}
gLogger.level = GMPPrefs.get(GMPPrefs.KEY_LOGGING_LEVEL, Log.Level.Warn);
let logDumping = GMPPrefs.get(GMPPrefs.KEY_LOGGING_DUMP, false);
if (logDumping != !!gLogAppenderDump) {
if (logDumping) {
gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
gLogger.addAppender(gLogAppenderDump);
} else {
gLogger.removeAppender(gLogAppenderDump);
gLogAppenderDump = null;
}
}
}
/**
* The GMPWrapper provides the info for the various GMP plugins to public
* callers through the API.
*/
function GMPWrapper(aPluginInfo) {
this._plugin = aPluginInfo;
this._log =
Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
"GMPWrapper(" +
this._plugin.id + ") ");
Preferences.observe(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED,
this._plugin.id),
this.onPrefEnabledChanged, this);
Preferences.observe(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION,
this._plugin.id),
this.onPrefVersionChanged, this);
if (this._plugin.isEME) {
Preferences.observe(GMPPrefs.KEY_EME_ENABLED,
this.onPrefEMEGlobalEnabledChanged, this);
messageManager.addMessageListener("EMEVideo:ContentMediaKeysRequest", this);
}
}
GMPWrapper.prototype = {
// An active task that checks for plugin updates and installs them.
_updateTask: null,
_gmpPath: null,
_isUpdateCheckPending: false,
optionsType: AddonManager.OPTIONS_TYPE_INLINE,
get optionsURL() { return this._plugin.optionsURL; },
set gmpPath(aPath) { this._gmpPath = aPath; },
get gmpPath() {
if (!this._gmpPath && this.isInstalled) {
this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
this._plugin.id,
GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION,
null, this._plugin.id));
}
return this._gmpPath;
},
get id() { return this._plugin.id; },
get type() { return "plugin"; },
get isGMPlugin() { return true; },
get name() { return this._plugin.name; },
get creator() { return null; },
get homepageURL() { return this._plugin.homepageURL; },
get description() { return this._plugin.description; },
get fullDescription() { return this._plugin.fullDescription; },
get version() { return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION, null,
this._plugin.id); },
get isActive() { return !this.appDisabled && !this.userDisabled; },
get appDisabled() {
if (this._plugin.isEME && !GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true)) {
// If "media.eme.enabled" is false, all EME plugins are disabled.
return true;
}
return false;
},
get userDisabled() {
return !GMPPrefs.get(GMPPrefs.KEY_PLUGIN_ENABLED, true, this._plugin.id);
},
set userDisabled(aVal) { GMPPrefs.set(GMPPrefs.KEY_PLUGIN_ENABLED,
aVal === false,
this._plugin.id); },
get blocklistState() { return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; },
get size() { return 0; },
get scope() { return AddonManager.SCOPE_APPLICATION; },
get pendingOperations() { return AddonManager.PENDING_NONE; },
get operationsRequiringRestart() { return AddonManager.OP_NEEDS_RESTART_NONE },
get permissions() {
let permissions = 0;
if (!this.appDisabled) {
permissions |= AddonManager.PERM_CAN_UPGRADE;
permissions |= this.userDisabled ? AddonManager.PERM_CAN_ENABLE :
AddonManager.PERM_CAN_DISABLE;
}
return permissions;
},
get updateDate() {
let time = Number(GMPPrefs.get(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, null,
this._plugin.id));
if (time !== NaN && this.isInstalled) {
return new Date(time * 1000)
}
return null;
},
get isCompatible() {
return true;
},
get isPlatformCompatible() {
return true;
},
get providesUpdatesSecurely() {
return true;
},
get foreignInstall() {
return false;
},
isCompatibleWith: function(aAppVersion, aPlatformVersion) {
return true;
},
get applyBackgroundUpdates() {
if (!GMPPrefs.isSet(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id)) {
return AddonManager.AUTOUPDATE_DEFAULT;
}
return GMPPrefs.get(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id) ?
AddonManager.AUTOUPDATE_ENABLE : AddonManager.AUTOUPDATE_DISABLE;
},
set applyBackgroundUpdates(aVal) {
if (aVal == AddonManager.AUTOUPDATE_DEFAULT) {
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id);
} else if (aVal == AddonManager.AUTOUPDATE_ENABLE) {
GMPPrefs.set(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id);
} else if (aVal == AddonManager.AUTOUPDATE_DISABLE) {
GMPPrefs.set(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, false, this._plugin.id);
}
},
findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
this._log.trace("findUpdates() - " + this._plugin.id + " - reason=" +
aReason);
AddonManagerPrivate.callNoUpdateListeners(this, aListener);
if (aReason === AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
if (!AddonManager.shouldAutoUpdate(this)) {
this._log.trace("findUpdates() - " + this._plugin.id +
" - no autoupdate");
return Promise.resolve(false);
}
let secSinceLastCheck =
Date.now() / 1000 - Preferences.get(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
if (secSinceLastCheck <= SEC_IN_A_DAY) {
this._log.trace("findUpdates() - " + this._plugin.id +
" - last check was less then a day ago");
return Promise.resolve(false);
}
} else if (aReason !== AddonManager.UPDATE_WHEN_USER_REQUESTED) {
this._log.trace("findUpdates() - " + this._plugin.id +
" - the given reason to update is not supported");
return Promise.resolve(false);
}
if (this._updateTask !== null) {
this._log.trace("findUpdates() - " + this._plugin.id +
" - update task already running");
return this._updateTask;
}
this._updateTask = Task.spawn(function* GMPProvider_updateTask() {
this._log.trace("findUpdates() - updateTask");
try {
let installManager = new GMPInstallManager();
let gmpAddons = yield installManager.checkForAddons();
let update = gmpAddons.find(function(aAddon) {
return aAddon.id === this._plugin.id;
}, this);
if (update && update.isValid && !update.isInstalled) {
this._log.trace("findUpdates() - found update for " +
this._plugin.id + ", installing");
yield installManager.installAddon(update);
} else {
this._log.trace("findUpdates() - no updates for " + this._plugin.id);
}
this._log.info("findUpdates() - updateTask succeeded for " +
this._plugin.id);
} catch (e) {
this._log.error("findUpdates() - updateTask for " + this._plugin.id +
" threw", e);
throw e;
} finally {
this._updateTask = null;
return true;
}
}.bind(this));
return this._updateTask;
},
get pluginMimeTypes() { return []; },
get pluginLibraries() {
if (this.isInstalled) {
let path = this.version;
return [path];
}
return [];
},
get pluginFullpath() {
if (this.isInstalled) {
let path = OS.Path.join(OS.Constants.Path.profileDir,
this._plugin.id,
this.version);
return [path];
}
return [];
},
get isInstalled() {
return this.version && this.version.length > 0;
},
_handleEnabledChanged: function() {
AddonManagerPrivate.callAddonListeners(this.isActive ?
"onEnabling" : "onDisabling",
this, false);
if (this._gmpPath) {
if (this.isActive) {
this._log.info("onPrefEnabledChanged() - adding gmp directory " +
this._gmpPath);
gmpService.addPluginDirectory(this._gmpPath);
} else {
this._log.info("onPrefEnabledChanged() - removing gmp directory " +
this._gmpPath);
gmpService.removePluginDirectory(this._gmpPath);
}
}
AddonManagerPrivate.callAddonListeners(this.isActive ?
"onEnabled" : "onDisabled",
this);
},
onPrefEMEGlobalEnabledChanged: function() {
AddonManagerPrivate.callAddonListeners("onPropertyChanged", this,
["appDisabled"]);
if (this.appDisabled) {
this.uninstallPlugin();
} else {
AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
null, false);
AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
AddonManagerPrivate.callAddonListeners("onInstalled", this);
this.checkForUpdates(GMP_CHECK_DELAY);
}
if (!this.userDisabled) {
this._handleEnabledChanged();
}
},
checkForUpdates: function(delay) {
if (this._isUpdateCheckPending) {
return;
}
this._isUpdateCheckPending = true;
GMPPrefs.reset(GMPPrefs.KEY_UPDATE_LAST_CHECK, null);
// Delay this in case the user changes his mind and doesn't want to
// enable EME after all.
setTimeout(() => {
if (!this.appDisabled) {
let gmpInstallManager = new GMPInstallManager();
// We don't really care about the results, if someone is interested
// they can check the log.
gmpInstallManager.simpleCheckAndInstall().then(null, () => {});
}
this._isUpdateCheckPending = false;
}, delay);
},
receiveMessage: function({target: browser, data: data}) {
this._log.trace("receiveMessage() data=" + data);
let parsedData;
try {
parsedData = JSON.parse(data);
} catch(ex) {
this._log.error("Malformed EME video message with data: " + data);
return;
}
let {status: status, keySystem: keySystem} = parsedData;
if (status == "cdm-not-installed" || status == "cdm-insufficient-version") {
this.checkForUpdates(0);
}
},
onPrefEnabledChanged: function() {
if (!this._plugin.isEME || !this.appDisabled) {
this._handleEnabledChanged();
}
},
onPrefVersionChanged: function() {
AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
if (this._gmpPath) {
this._log.info("onPrefVersionChanged() - unregistering gmp directory " +
this._gmpPath);
gmpService.removeAndDeletePluginDirectory(this._gmpPath, true /* can defer */);
}
AddonManagerPrivate.callAddonListeners("onUninstalled", this);
AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
null, false);
AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
this._gmpPath = null;
if (this.isInstalled) {
this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
this._plugin.id,
GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION,
null, this._plugin.id));
}
if (this._gmpPath && this.isActive) {
this._log.info("onPrefVersionChanged() - registering gmp directory " +
this._gmpPath);
gmpService.addPluginDirectory(this._gmpPath);
}
AddonManagerPrivate.callAddonListeners("onInstalled", this);
},
uninstallPlugin: function() {
AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
if (this.gmpPath) {
this._log.info("uninstallPlugin() - unregistering gmp directory " +
this.gmpPath);
gmpService.removeAndDeletePluginDirectory(this.gmpPath);
}
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_VERSION, this.id);
AddonManagerPrivate.callAddonListeners("onUninstalled", this);
},
shutdown: function() {
Preferences.ignore(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED,
this._plugin.id),
this.onPrefEnabledChanged, this);
Preferences.ignore(GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION,
this._plugin.id),
this.onPrefVersionChanged, this);
if (this._plugin.isEME) {
Preferences.ignore(GMPPrefs.KEY_EME_ENABLED,
this.onPrefEMEGlobalEnabledChanged, this);
messageManager.removeMessageListener("EMEVideo:ContentMediaKeysRequest", this);
}
return this._updateTask;
},
};
let GMPProvider = {
get name() { return "GMPProvider"; },
_plugins: null,
startup: function() {
configureLogging();
this._log = Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
"GMPProvider.");
let telemetry = {};
this.buildPluginList();
this.ensureProperCDMInstallState();
Preferences.observe(GMPPrefs.KEY_LOG_BASE, configureLogging);
for (let [id, plugin] of this._plugins) {
let wrapper = plugin.wrapper;
let gmpPath = wrapper.gmpPath;
let isEnabled = wrapper.isActive;
this._log.trace("startup - enabled=" + isEnabled + ", gmpPath=" +
gmpPath);
if (gmpPath && isEnabled) {
this._log.info("startup - adding gmp directory " + gmpPath);
try {
gmpService.addPluginDirectory(gmpPath);
} catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') {
this._log.warn("startup - adding gmp directory failed with " +
e.name + " - sandboxing not available?", e);
}
}
if (this.isEnabled) {
telemetry[id] = {
userDisabled: wrapper.userDisabled,
version: wrapper.version,
applyBackgroundUpdates: wrapper.applyBackgroundUpdates,
};
}
}
if (Preferences.get(GMPPrefs.KEY_EME_ENABLED, false)) {
try {
let greDir = Services.dirsvc.get(NS_GRE_DIR,
Ci.nsILocalFile);
let clearkeyPath = OS.Path.join(greDir.path,
CLEARKEY_PLUGIN_ID,
CLEARKEY_VERSION);
this._log.info("startup - adding clearkey CDM directory " +
clearkeyPath);
gmpService.addPluginDirectory(clearkeyPath);
} catch (e) {
this._log.warn("startup - adding clearkey CDM failed", e);
}
}
AddonManagerPrivate.setTelemetryDetails("GMP", telemetry);
},
shutdown: function() {
this._log.trace("shutdown");
Preferences.ignore(GMPPrefs.KEY_LOG_BASE, configureLogging);
let shutdownTask = Task.spawn(function* GMPProvider_shutdownTask() {
this._log.trace("shutdown - shutdownTask");
let shutdownSucceeded = true;
for (let plugin of this._plugins.values()) {
try {
yield plugin.wrapper.shutdown();
} catch (e) {
shutdownSucceeded = false;
}
}
this._plugins = null;
if (!shutdownSucceeded) {
throw new Error("Shutdown failed");
}
}.bind(this));
return shutdownTask;
},
getAddonByID: function(aId, aCallback) {
if (!this.isEnabled) {
aCallback(null);
return;
}
let plugin = this._plugins.get(aId);
if (plugin && !GMPUtils.isPluginHidden(plugin)) {
aCallback(plugin.wrapper);
} else {
aCallback(null);
}
},
getAddonsByTypes: function(aTypes, aCallback) {
if (!this.isEnabled ||
(aTypes && aTypes.indexOf("plugin") < 0)) {
aCallback([]);
return;
}
let results = [p.wrapper for ([id, p] of this._plugins)
if (!GMPUtils.isPluginHidden(p))];
aCallback(results);
},
get isEnabled() {
return GMPPrefs.get(GMPPrefs.KEY_PROVIDER_ENABLED, false);
},
generateFullDescription: function(aLicenseURL, aLicenseInfo) {
return "<xhtml:a href=\"" + aLicenseURL + "\" target=\"_blank\">" +
aLicenseInfo + "</xhtml:a>."
},
buildPluginList: function() {
let licenseInfo = pluginsBundle.GetStringFromName(GMP_LICENSE_INFO);
this._plugins = new Map();
for (let aPlugin of GMP_PLUGINS) {
let plugin = {
id: aPlugin.id,
name: pluginsBundle.GetStringFromName(aPlugin.name),
description: pluginsBundle.GetStringFromName(aPlugin.description),
homepageURL: aPlugin.homepageURL,
optionsURL: aPlugin.optionsURL,
wrapper: null,
isEME: aPlugin.isEME,
};
if (aPlugin.licenseURL) {
plugin.fullDescription =
this.generateFullDescription(aPlugin.licenseURL, licenseInfo);
}
plugin.wrapper = new GMPWrapper(plugin);
this._plugins.set(plugin.id, plugin);
}
},
ensureProperCDMInstallState: function() {
if (!GMPPrefs.get(GMPPrefs.KEY_EME_ENABLED, true)) {
for (let [id, plugin] of this._plugins) {
if (plugin.isEME && plugin.wrapper.isInstalled) {
gmpService.addPluginDirectory(plugin.wrapper.gmpPath);
plugin.wrapper.uninstallPlugin();
}
}
}
},
};
AddonManagerPrivate.registerProvider(GMPProvider, [
new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 6000,
AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
]);

View file

@ -0,0 +1,198 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["LightweightThemeImageOptimizer"];
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
const ORIGIN_TOP_RIGHT = 1;
const ORIGIN_BOTTOM_LEFT = 2;
this.LightweightThemeImageOptimizer = {
optimize: function LWTIO_optimize(aThemeData, aScreen) {
let data = Utils.createCopy(aThemeData);
if (!data.headerURL) {
return data;
}
data.headerURL = ImageCropper.getCroppedImageURL(
data.headerURL, aScreen, ORIGIN_TOP_RIGHT);
if (data.footerURL) {
data.footerURL = ImageCropper.getCroppedImageURL(
data.footerURL, aScreen, ORIGIN_BOTTOM_LEFT);
}
return data;
},
purge: function LWTIO_purge() {
let dir = FileUtils.getDir("ProfD", ["lwtheme"]);
dir.followLinks = false;
try {
dir.remove(true);
} catch (e) {}
}
};
Object.freeze(LightweightThemeImageOptimizer);
let ImageCropper = {
_inProgress: {},
getCroppedImageURL:
function ImageCropper_getCroppedImageURL(aImageURL, aScreen, aOrigin) {
// We can crop local files, only.
if (!aImageURL.startsWith("file://")) {
return aImageURL;
}
if (Services.prefs.getBoolPref("lightweightThemes.animation.enabled")) {
//Don't crop if animated
return aImageURL;
}
// Generate the cropped image's file name using its
// base name and the current screen size.
let uri = Services.io.newURI(aImageURL, null, null);
let file = uri.QueryInterface(Ci.nsIFileURL).file;
// Make sure the source file exists.
if (!file.exists()) {
return aImageURL;
}
let fileName = file.leafName + "-" + aScreen.width + "x" + aScreen.height;
let croppedFile = FileUtils.getFile("ProfD", ["lwtheme", fileName]);
// If we have a local file that is not in progress, return it.
if (croppedFile.exists() && !(croppedFile.path in this._inProgress)) {
let fileURI = Services.io.newFileURI(croppedFile);
// Copy the query part to avoid wrong caching.
fileURI.QueryInterface(Ci.nsIURL).query = uri.query;
return fileURI.spec;
}
// Crop the given image in the background.
this._crop(uri, croppedFile, aScreen, aOrigin);
// Return the original image while we're waiting for the cropped version
// to be written to disk.
return aImageURL;
},
_crop: function ImageCropper_crop(aURI, aTargetFile, aScreen, aOrigin) {
let inProgress = this._inProgress;
inProgress[aTargetFile.path] = true;
function resetInProgress() {
delete inProgress[aTargetFile.path];
}
ImageFile.read(aURI, function crop_readImageFile(aInputStream, aContentType) {
if (aInputStream && aContentType) {
let image = ImageTools.decode(aInputStream, aContentType);
if (image && image.width && image.height) {
let stream = ImageTools.encode(image, aScreen, aOrigin, aContentType);
if (stream) {
ImageFile.write(aTargetFile, stream, resetInProgress);
return;
}
}
}
resetInProgress();
});
}
};
let ImageFile = {
read: function ImageFile_read(aURI, aCallback) {
this._netUtil.asyncFetch2(
aURI,
function read_asyncFetch(aInputStream, aStatus, aRequest) {
if (Components.isSuccessCode(aStatus) && aRequest instanceof Ci.nsIChannel) {
let channel = aRequest.QueryInterface(Ci.nsIChannel);
aCallback(aInputStream, channel.contentType);
} else {
aCallback();
}
},
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_IMAGE);
},
write: function ImageFile_write(aFile, aInputStream, aCallback) {
let fos = FileUtils.openSafeFileOutputStream(aFile);
this._netUtil.asyncCopy(aInputStream, fos, function write_asyncCopy(aResult) {
FileUtils.closeSafeFileOutputStream(fos);
// Remove the file if writing was not successful.
if (!Components.isSuccessCode(aResult)) {
try {
aFile.remove(false);
} catch (e) {}
}
aCallback();
});
}
};
XPCOMUtils.defineLazyModuleGetter(ImageFile, "_netUtil",
"resource://gre/modules/NetUtil.jsm", "NetUtil");
let ImageTools = {
decode: function ImageTools_decode(aInputStream, aContentType) {
let outParam = {value: null};
try {
this._imgTools.decodeImageData(aInputStream, aContentType, outParam);
} catch (e) {}
return outParam.value;
},
encode: function ImageTools_encode(aImage, aScreen, aOrigin, aContentType) {
let stream;
let width = Math.min(aImage.width, aScreen.width);
let height = Math.min(aImage.height, aScreen.height);
let x = aOrigin == ORIGIN_TOP_RIGHT ? aImage.width - width : 0;
try {
stream = this._imgTools.encodeCroppedImage(aImage, aContentType, x, 0,
width, height);
} catch (e) {}
return stream;
}
};
XPCOMUtils.defineLazyServiceGetter(ImageTools, "_imgTools",
"@mozilla.org/image/tools;1", "imgITools");
let Utils = {
createCopy: function Utils_createCopy(aData) {
let copy = {};
for (let [k, v] in Iterator(aData)) {
copy[k] = v;
}
return copy;
}
};

View file

@ -0,0 +1,560 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = [];
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
const STRING_TYPE_NAME = "type.%ID%.name";
const LIST_UPDATED_TOPIC = "plugins-list-updated";
const FLASH_MIME_TYPE = "application/x-shockwave-flash";
Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.plugins";
// Create a new logger for use by the Addons Plugin Provider
// (Requires AddonManager.jsm)
let logger = Log.repository.getLogger(LOGGER_ID);
function getIDHashForString(aStr) {
// return the two-digit hexadecimal code for a byte
function toHexString(charCode)
("0" + charCode.toString(16)).slice(-2);
let hasher = Cc["@mozilla.org/security/hash;1"].
createInstance(Ci.nsICryptoHash);
hasher.init(Ci.nsICryptoHash.MD5);
let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(Ci.nsIStringInputStream);
stringStream.data = aStr ? aStr : "null";
hasher.updateFromStream(stringStream, -1);
// convert the binary hash data to a hex string.
let binary = hasher.finish(false);
let hash = [toHexString(binary.charCodeAt(i)) for (i in binary)].join("").toLowerCase();
return "{" + hash.substr(0, 8) + "-" +
hash.substr(8, 4) + "-" +
hash.substr(12, 4) + "-" +
hash.substr(16, 4) + "-" +
hash.substr(20) + "}";
}
var PluginProvider = {
get name() "PluginProvider",
// A dictionary mapping IDs to names and descriptions
plugins: null,
startup: function PL_startup() {
Services.obs.addObserver(this, LIST_UPDATED_TOPIC, false);
Services.obs.addObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, false);
},
/**
* Called when the application is shutting down. Only necessary for tests
* to be able to simulate a shutdown.
*/
shutdown: function PL_shutdown() {
this.plugins = null;
Services.obs.removeObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
Services.obs.removeObserver(this, LIST_UPDATED_TOPIC);
},
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case AddonManager.OPTIONS_NOTIFICATION_DISPLAYED:
this.getAddonByID(aData, function PL_displayPluginInfo(plugin) {
if (!plugin)
return;
let libLabel = aSubject.getElementById("pluginLibraries");
libLabel.textContent = plugin.pluginLibraries.join(", ");
let typeLabel = aSubject.getElementById("pluginMimeTypes"), types = [];
for (let type of plugin.pluginMimeTypes) {
let extras = [type.description.trim(), type.suffixes].
filter(function(x) x).join(": ");
types.push(type.type + (extras ? " (" + extras + ")" : ""));
}
typeLabel.textContent = types.join(",\n");
let showProtectedModePref = canDisableFlashProtectedMode(plugin);
aSubject.getElementById("pluginEnableProtectedMode")
.setAttribute("collapsed", showProtectedModePref ? "" : "true");
});
break;
case LIST_UPDATED_TOPIC:
if (this.plugins)
this.updatePluginList();
break;
}
},
/**
* Creates a PluginWrapper for a plugin object.
*/
buildWrapper: function PL_buildWrapper(aPlugin) {
return new PluginWrapper(aPlugin.id,
aPlugin.name,
aPlugin.description,
aPlugin.tags);
},
/**
* Called to get an Addon with a particular ID.
*
* @param aId
* The ID of the add-on to retrieve
* @param aCallback
* A callback to pass the Addon to
*/
getAddonByID: function PL_getAddon(aId, aCallback) {
if (!this.plugins)
this.buildPluginList();
if (aId in this.plugins)
aCallback(this.buildWrapper(this.plugins[aId]));
else
aCallback(null);
},
/**
* Called to get Addons of a particular type.
*
* @param aTypes
* An array of types to fetch. Can be null to get all types.
* @param callback
* A callback to pass an array of Addons to
*/
getAddonsByTypes: function PL_getAddonsByTypes(aTypes, aCallback) {
if (aTypes && aTypes.indexOf("plugin") < 0) {
aCallback([]);
return;
}
if (!this.plugins)
this.buildPluginList();
let results = [];
for (let id in this.plugins) {
this.getAddonByID(id, function(aAddon) {
results.push(aAddon);
});
}
aCallback(results);
},
/**
* Called to get Addons that have pending operations.
*
* @param aTypes
* An array of types to fetch. Can be null to get all types
* @param aCallback
* A callback to pass an array of Addons to
*/
getAddonsWithOperationsByTypes: function PL_getAddonsWithOperationsByTypes(aTypes, aCallback) {
aCallback([]);
},
/**
* Called to get the current AddonInstalls, optionally restricting by type.
*
* @param aTypes
* An array of types or null to get all types
* @param aCallback
* A callback to pass the array of AddonInstalls to
*/
getInstallsByTypes: function PL_getInstallsByTypes(aTypes, aCallback) {
aCallback([]);
},
/**
* Builds a list of the current plugins reported by the plugin host
*
* @return a dictionary of plugins indexed by our generated ID
*/
getPluginList: function PL_getPluginList() {
let tags = Cc["@mozilla.org/plugin/host;1"].
getService(Ci.nsIPluginHost).
getPluginTags({});
let list = {};
let seenPlugins = {};
for (let tag of tags) {
if (!(tag.name in seenPlugins))
seenPlugins[tag.name] = {};
if (!(tag.description in seenPlugins[tag.name])) {
let plugin = {
id: getIDHashForString(tag.name + tag.description),
// XXX Flash name substitution like in browser-plugins.js, aboutPermissions.js, permissions.js
name: tag.name == "Shockwave Flash" ? "Adobe Flash" : tag.name,
description: tag.description,
tags: [tag]
};
seenPlugins[tag.name][tag.description] = plugin;
list[plugin.id] = plugin;
}
else {
seenPlugins[tag.name][tag.description].tags.push(tag);
}
}
return list;
},
/**
* Builds the list of known plugins from the plugin host
*/
buildPluginList: function PL_buildPluginList() {
this.plugins = this.getPluginList();
},
/**
* Updates the plugins from the plugin host by comparing the current plugins
* to the last known list sending out any necessary API notifications for
* changes.
*/
updatePluginList: function PL_updatePluginList() {
let newList = this.getPluginList();
let lostPlugins = [this.buildWrapper(this.plugins[id])
for each (id in Object.keys(this.plugins)) if (!(id in newList))];
let newPlugins = [this.buildWrapper(newList[id])
for each (id in Object.keys(newList)) if (!(id in this.plugins))];
let matchedIDs = [id for each (id in Object.keys(newList)) if (id in this.plugins)];
// The plugin host generates new tags for every plugin after a scan and
// if the plugin's filename has changed then the disabled state won't have
// been carried across, send out notifications for anything that has
// changed (see bug 830267).
let changedWrappers = [];
for (let id of matchedIDs) {
let oldWrapper = this.buildWrapper(this.plugins[id]);
let newWrapper = this.buildWrapper(newList[id]);
if (newWrapper.isActive != oldWrapper.isActive) {
AddonManagerPrivate.callAddonListeners(newWrapper.isActive ?
"onEnabling" : "onDisabling",
newWrapper, false);
changedWrappers.push(newWrapper);
}
}
// Notify about new installs
for (let plugin of newPlugins) {
AddonManagerPrivate.callInstallListeners("onExternalInstall", null,
plugin, null, false);
AddonManagerPrivate.callAddonListeners("onInstalling", plugin, false);
}
// Notify for any plugins that have vanished.
for (let plugin of lostPlugins)
AddonManagerPrivate.callAddonListeners("onUninstalling", plugin, false);
this.plugins = newList;
// Signal that new installs are complete
for (let plugin of newPlugins)
AddonManagerPrivate.callAddonListeners("onInstalled", plugin);
// Signal that enables/disables are complete
for (let wrapper of changedWrappers) {
AddonManagerPrivate.callAddonListeners(wrapper.isActive ?
"onEnabled" : "onDisabled",
wrapper);
}
// Signal that uninstalls are complete
for (let plugin of lostPlugins)
AddonManagerPrivate.callAddonListeners("onUninstalled", plugin);
}
};
function isFlashPlugin(aPlugin) {
for (let type of aPlugin.pluginMimeTypes) {
if (type.type == FLASH_MIME_TYPE) {
return true;
}
}
return false;
}
// Protected mode is win32-only, not win64
function canDisableFlashProtectedMode(aPlugin) {
return isFlashPlugin(aPlugin) && Services.appinfo.XPCOMABI == "x86-msvc";
}
/**
* The PluginWrapper wraps a set of nsIPluginTags to provide the data visible to
* public callers through the API.
*/
function PluginWrapper(aId, aName, aDescription, aTags) {
let safedesc = aDescription.replace(/<\/?[a-z][^>]*>/gi, " ");
let homepageURL = null;
if (/<A\s+HREF=[^>]*>/i.test(aDescription))
homepageURL = /<A\s+HREF=["']?([^>"'\s]*)/i.exec(aDescription)[1];
this.__defineGetter__("id", function() aId);
this.__defineGetter__("type", function() "plugin");
this.__defineGetter__("name", function() aName);
this.__defineGetter__("creator", function() null);
this.__defineGetter__("description", function() safedesc);
this.__defineGetter__("version", function() aTags[0].version);
this.__defineGetter__("homepageURL", function() homepageURL);
this.__defineGetter__("isActive", function() !aTags[0].blocklisted && !aTags[0].disabled);
this.__defineGetter__("appDisabled", function() aTags[0].blocklisted);
this.__defineGetter__("userDisabled", function() {
if (aTags[0].disabled)
return true;
if ((Services.prefs.getBoolPref("plugins.click_to_play") && aTags[0].clicktoplay) ||
this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE ||
this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE)
return AddonManager.STATE_ASK_TO_ACTIVATE;
return false;
});
this.__defineSetter__("userDisabled", function(aVal) {
let previousVal = this.userDisabled;
if (aVal === previousVal)
return aVal;
for (let tag of aTags) {
if (aVal === true)
tag.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
else if (aVal === false)
tag.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
else if (aVal == AddonManager.STATE_ASK_TO_ACTIVATE)
tag.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
}
// If 'userDisabled' was 'true' and we're going to a state that's not
// that, we're enabling, so call those listeners.
if (previousVal === true && aVal !== true) {
AddonManagerPrivate.callAddonListeners("onEnabling", this, false);
AddonManagerPrivate.callAddonListeners("onEnabled", this);
}
// If 'userDisabled' was not 'true' and we're going to a state where
// it is, we're disabling, so call those listeners.
if (previousVal !== true && aVal === true) {
AddonManagerPrivate.callAddonListeners("onDisabling", this, false);
AddonManagerPrivate.callAddonListeners("onDisabled", this);
}
// If the 'userDisabled' value involved AddonManager.STATE_ASK_TO_ACTIVATE,
// call the onPropertyChanged listeners.
if (previousVal == AddonManager.STATE_ASK_TO_ACTIVATE ||
aVal == AddonManager.STATE_ASK_TO_ACTIVATE) {
AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["userDisabled"]);
}
return aVal;
});
this.__defineGetter__("blocklistState", function() {
let bs = Cc["@mozilla.org/extensions/blocklist;1"].
getService(Ci.nsIBlocklistService);
return bs.getPluginBlocklistState(aTags[0]);
});
this.__defineGetter__("blocklistURL", function() {
let bs = Cc["@mozilla.org/extensions/blocklist;1"].
getService(Ci.nsIBlocklistService);
return bs.getPluginBlocklistURL(aTags[0]);
});
this.__defineGetter__("size", function() {
function getDirectorySize(aFile) {
let size = 0;
let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
let entry;
while ((entry = entries.nextFile)) {
if (entry.isSymlink() || !entry.isDirectory())
size += entry.fileSize;
else
size += getDirectorySize(entry);
}
entries.close();
return size;
}
let size = 0;
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
for (let tag of aTags) {
file.initWithPath(tag.fullpath);
if (file.isDirectory())
size += getDirectorySize(file);
else
size += file.fileSize;
}
return size;
});
this.__defineGetter__("pluginLibraries", function() {
let libs = [];
for (let tag of aTags)
libs.push(tag.filename);
return libs;
});
this.__defineGetter__("pluginFullpath", function() {
let paths = [];
for (let tag of aTags)
paths.push(tag.fullpath);
return paths;
})
this.__defineGetter__("pluginMimeTypes", function() {
let types = [];
for (let tag of aTags) {
let mimeTypes = tag.getMimeTypes({});
let mimeDescriptions = tag.getMimeDescriptions({});
let extensions = tag.getExtensions({});
for (let i = 0; i < mimeTypes.length; i++) {
let type = {};
type.type = mimeTypes[i];
type.description = mimeDescriptions[i];
type.suffixes = extensions[i];
types.push(type);
}
}
return types;
});
this.__defineGetter__("installDate", function() {
let date = 0;
for (let tag of aTags) {
date = Math.max(date, tag.lastModifiedTime);
}
return new Date(date);
});
this.__defineGetter__("scope", function() {
let path = aTags[0].fullpath;
// Plugins inside the application directory are in the application scope
let dir = Services.dirsvc.get("APlugns", Ci.nsIFile);
if (path.startsWith(dir.path))
return AddonManager.SCOPE_APPLICATION;
// Plugins inside the profile directory are in the profile scope
dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
if (path.startsWith(dir.path))
return AddonManager.SCOPE_PROFILE;
// Plugins anywhere else in the user's home are in the user scope,
// but not all platforms have a home directory.
try {
dir = Services.dirsvc.get("Home", Ci.nsIFile);
if (path.startsWith(dir.path))
return AddonManager.SCOPE_USER;
} catch (e if (e.result && e.result == Components.results.NS_ERROR_FAILURE)) {
// Do nothing: missing "Home".
}
// Any other locations are system scope
return AddonManager.SCOPE_SYSTEM;
});
this.__defineGetter__("pendingOperations", function() {
return AddonManager.PENDING_NONE;
});
this.__defineGetter__("operationsRequiringRestart", function() {
return AddonManager.OP_NEEDS_RESTART_NONE;
});
this.__defineGetter__("permissions", function() {
let permissions = 0;
if (aTags[0].isEnabledStateLocked) {
return permissions;
}
if (!this.appDisabled) {
if (this.userDisabled !== true)
permissions |= AddonManager.PERM_CAN_DISABLE;
let blocklistState = this.blocklistState;
let isCTPBlocklisted =
(blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE ||
blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE);
if (this.userDisabled !== AddonManager.STATE_ASK_TO_ACTIVATE &&
(Services.prefs.getBoolPref("plugins.click_to_play") ||
isCTPBlocklisted)) {
permissions |= AddonManager.PERM_CAN_ASK_TO_ACTIVATE;
}
if (this.userDisabled !== false && !isCTPBlocklisted) {
permissions |= AddonManager.PERM_CAN_ENABLE;
}
}
return permissions;
});
this.__defineGetter__("optionsType", function() {
if (canDisableFlashProtectedMode(this)) {
return AddonManager.OPTIONS_TYPE_INLINE;
}
return AddonManager.OPTIONS_TYPE_INLINE_INFO;
});
}
PluginWrapper.prototype = {
optionsURL: "chrome://mozapps/content/extensions/pluginPrefs.xul",
get updateDate() {
return this.installDate;
},
get isCompatible() {
return true;
},
get isPlatformCompatible() {
return true;
},
get providesUpdatesSecurely() {
return true;
},
get foreignInstall() {
return true;
},
isCompatibleWith: function(aAppVerison, aPlatformVersion) {
return true;
},
findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
if ("onNoCompatibilityUpdateAvailable" in aListener)
aListener.onNoCompatibilityUpdateAvailable(this);
if ("onNoUpdateAvailable" in aListener)
aListener.onNoUpdateAvailable(this);
if ("onUpdateFinished" in aListener)
aListener.onUpdateFinished(this);
}
};
AddonManagerPrivate.registerProvider(PluginProvider, [
new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,
AddonManager.VIEW_TYPE_LIST, 6000,
AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
]);

View file

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var hunspell, dir;
function startup(data) {
hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
.getService(Components.interfaces.mozISpellCheckingEngine);
dir = data.installPath.clone();
dir.append("dictionaries");
hunspell.addDirectory(dir);
}
function shutdown() {
hunspell.removeDirectory(dir);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,39 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXTRA_JS_MODULES.addons += [
'AddonLogging.jsm',
'AddonRepository_SQLiteMigrator.jsm',
'Content.js',
'GMPProvider.jsm',
'LightweightThemeImageOptimizer.jsm',
'SpellCheckDictionaryBootstrap.js',
]
# Don't ship unused providers on Android
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
EXTRA_JS_MODULES.addons += [
'PluginProvider.jsm',
]
EXTRA_PP_JS_MODULES.addons += [
'AddonRepository.jsm',
'AddonUpdateChecker.jsm',
'XPIProvider.jsm',
'XPIProviderUtils.js',
]
# This is used in multiple places, so is defined here to avoid it getting
# out of sync.
DEFINES['MOZ_EXTENSIONS_DB_SCHEMA'] = 16
# Additional debugging info is exposed in debug builds
if CONFIG['MOZ_EM_DEBUG']:
DEFINES['MOZ_EM_DEBUG'] = 1
# Apperently this needs to be defined because it isn't picked up automagically any more
if CONFIG['MOZ_PHOENIX_EXTENSIONS']:
DEFINES['MOZ_PHOENIX_EXTENSIONS'] = 1

View file

@ -0,0 +1,37 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar:
% content mozapps %content/mozapps/
* content/mozapps/extensions/extensions.xul (content/extensions.xul)
content/mozapps/extensions/extensions.css (content/extensions.css)
* content/mozapps/extensions/extensions.js (content/extensions.js)
* content/mozapps/extensions/extensions.xml (content/extensions.xml)
content/mozapps/extensions/updateinfo.xsl (content/updateinfo.xsl)
content/mozapps/extensions/about.xul (content/about.xul)
content/mozapps/extensions/about.js (content/about.js)
content/mozapps/extensions/list.xul (content/list.xul)
content/mozapps/extensions/list.js (content/list.js)
content/mozapps/extensions/blocklist.xul (content/blocklist.xul)
content/mozapps/extensions/blocklist.js (content/blocklist.js)
content/mozapps/extensions/blocklist.css (content/blocklist.css)
content/mozapps/extensions/blocklist.xml (content/blocklist.xml)
content/mozapps/extensions/selectAddons.xul (content/selectAddons.xul)
content/mozapps/extensions/selectAddons.xml (content/selectAddons.xml)
content/mozapps/extensions/selectAddons.js (content/selectAddons.js)
content/mozapps/extensions/selectAddons.css (content/selectAddons.css)
* content/mozapps/extensions/update.xul (content/update.xul)
content/mozapps/extensions/update.js (content/update.js)
content/mozapps/extensions/eula.xul (content/eula.xul)
content/mozapps/extensions/eula.js (content/eula.js)
content/mozapps/extensions/newaddon.xul (content/newaddon.xul)
content/mozapps/extensions/newaddon.js (content/newaddon.js)
content/mozapps/extensions/setting.xml (content/setting.xml)
content/mozapps/extensions/pluginPrefs.xul (content/pluginPrefs.xul)
content/mozapps/extensions/gmpPrefs.xul (content/gmpPrefs.xul)
content/mozapps/extensions/OpenH264-license.txt (content/OpenH264-license.txt)
content/mozapps/xpinstall/xpinstallConfirm.xul (content/xpinstallConfirm.xul)
content/mozapps/xpinstall/xpinstallConfirm.js (content/xpinstallConfirm.js)
content/mozapps/xpinstall/xpinstallConfirm.css (content/xpinstallConfirm.css)
content/mozapps/xpinstall/xpinstallItem.xml (content/xpinstallItem.xml)

View file

@ -0,0 +1,57 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += ['internal']
TEST_DIRS += ['test']
XPIDL_SOURCES += [
'amIAddonManager.idl',
'amIAddonPathService.idl',
'amIWebInstaller.idl',
'amIWebInstallListener.idl',
]
XPIDL_MODULE = 'extensions'
EXTRA_COMPONENTS += [
'addonManager.js',
'amContentHandler.js',
'amInstallTrigger.js',
'amWebInstallListener.js',
]
EXTRA_PP_COMPONENTS += [
'extensions.manifest',
'nsBlocklistService.js',
]
EXTRA_JS_MODULES += [
'ChromeManifestParser.jsm',
'DeferredSave.jsm',
'LightweightThemeManager.jsm',
]
EXTRA_PP_JS_MODULES += [
'AddonManager.jsm'
]
# Additional debugging info is exposed in debug builds
if CONFIG['MOZ_EM_DEBUG']:
DEFINES['MOZ_EM_DEBUG'] = 1
JAR_MANIFESTS += ['jar.mn']
EXPORTS.mozilla += [
'AddonPathService.h',
]
UNIFIED_SOURCES += [
'AddonPathService.cpp'
]
FAIL_ON_WARNINGS = True
FINAL_LIBRARY = 'xul'

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,105 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This file is a test-only JSM containing utility methods for
// interacting with the add-ons manager.
"use strict";
this.EXPORTED_SYMBOLS = [
"AddonTestUtils",
];
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
this.AddonTestUtils = {
/**
* Uninstall an add-on that is specified by its ID.
*
* The returned promise resolves on successful uninstall and rejects
* if the add-on is not unknown.
*
* @return Promise<restartRequired>
*/
uninstallAddonByID: function (id) {
let deferred = Promise.defer();
AddonManager.getAddonByID(id, (addon) => {
if (!addon) {
deferred.reject(new Error("Add-on is not known: " + id));
return;
}
let listener = {
onUninstalling: function (addon, needsRestart) {
if (addon.id != id) {
return;
}
if (needsRestart) {
AddonManager.removeAddonListener(listener);
deferred.resolve(true);
}
},
onUninstalled: function (addon) {
if (addon.id != id) {
return;
}
AddonManager.removeAddonListener(listener);
deferred.resolve(false);
},
onOperationCancelled: function (addon) {
if (addon.id != id) {
return;
}
AddonManager.removeAddonListener(listener);
deferred.reject(new Error("Uninstall cancelled."));
},
};
AddonManager.addAddonListener(listener);
addon.uninstall();
});
return deferred.promise;
},
/**
* Install an XPI add-on from a URL.
*
* @return Promise<addon>
*/
installXPIFromURL: function (url, hash, name, iconURL, version) {
let deferred = Promise.defer();
AddonManager.getInstallForURL(url, (install) => {
let fail = () => { deferred.reject(new Error("Add-on install failed.")) };
let listener = {
onDownloadCancelled: fail,
onDownloadFailed: fail,
onInstallCancelled: fail,
onInstallFailed: fail,
onInstallEnded: function (install, addon) {
deferred.resolve(addon);
},
};
install.addListener(listener);
install.install();
}, "application/x-xpinstall", hash, name, iconURL, version);
return deferred.promise;
},
};

View file

@ -0,0 +1,20 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
ADDONSRC = $(srcdir)/addons
TESTROOT = $(CURDIR)/$(DEPTH)/_tests/xpcshell/$(relativesrcdir)
TESTXPI = $(TESTROOT)/xpcshell/addons
include $(topsrcdir)/config/rules.mk
libs::
rm -rf $(TESTXPI)
$(NSINSTALL) -D $(TESTXPI)
if [ -d $(ADDONSRC) ]; then \
$(EXIT_ON_ERROR) \
for dir in $(ADDONSRC)/*; do \
base=`basename $$dir` ; \
(cd $$dir && zip -qr $(TESTXPI)/$$base.xpi *) \
done \
fi

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>hardblock@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:name>Hardblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>hardblock@tests.mozilla.org</em:id>
<em:version>2.0</em:version>
<em:name>Hardblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>hardblock@tests.mozilla.org</em:id>
<em:version>3.0</em:version>
<em:name>Hardblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>regexpblock@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:name>RegExp-blocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>regexpblock@tests.mozilla.org</em:id>
<em:version>2.0</em:version>
<em:name>RegExp-blocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>regexpblock@tests.mozilla.org</em:id>
<em:version>3.0</em:version>
<em:name>RegExp-blocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock1@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock1@tests.mozilla.org</em:id>
<em:version>2.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock1@tests.mozilla.org</em:id>
<em:version>3.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock2@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock2@tests.mozilla.org</em:id>
<em:version>2.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock2@tests.mozilla.org</em:id>
<em:version>3.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock3@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock3@tests.mozilla.org</em:id>
<em:version>2.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock3@tests.mozilla.org</em:id>
<em:version>3.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock4@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock4@tests.mozilla.org</em:id>
<em:version>2.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock4@tests.mozilla.org</em:id>
<em:version>3.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock5@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:internalName>test/1.0</em:internalName>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock5@tests.mozilla.org</em:id>
<em:version>2.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:internalName>test/1.0</em:internalName>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>softblock5@tests.mozilla.org</em:id>
<em:version>3.0</em:version>
<em:name>Softblocked add-on</em:name>
<em:internalName>test/1.0</em:internalName>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>3</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,29 @@
Components.utils.import("resource://gre/modules/Services.jsm");
let seenGlobals = new Set();
let scope = this;
function checkGlobal(name, type) {
if (scope[name] && typeof(scope[name]) == type)
seenGlobals.add(name);
}
let wrapped = {};
Services.obs.notifyObservers({ wrappedJSObject: wrapped }, "bootstrap-request-globals", null);
for (let [name, type] of wrapped.expectedGlobals) {
checkGlobal(name, type);
}
function install(data, reason) {
}
function startup(data, reason) {
Services.obs.notifyObservers({
wrappedJSObject: seenGlobals
}, "bootstrap-seen-globals", null);
}
function shutdown(data, reason) {
}
function uninstall(data, reason) {
}

View file

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>bootstrap_globals@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:bootstrap>true</em:bootstrap>
<!-- Front End MetaData -->
<em:name>Test Bootstrap Globals</em:name>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>1</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View file

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>min1max1@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>1</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>Test minVersion 1 maxVersion 1</em:name>
</Description>
</RDF>

View file

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>min1max2@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>1</em:minVersion>
<em:maxVersion>2</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>Test minVersion 1 maxVersion 2</em:name>
</Description>
</RDF>

Some files were not shown because too many files have changed in this diff Show more