Merge f-t to m-c, a=merge

This commit is contained in:
Phil Ringnalda 2015-08-09 15:45:11 -07:00
commit 2ef98b0156
37 changed files with 724 additions and 224 deletions

@ -24,6 +24,19 @@ const PREF_ACCEPTED_POLICY_DATE = PREF_BRANCH + "dataSubmissionPolicyNotifiedTim
const TEST_POLICY_VERSION = 37;
function fakeShowPolicyTimeout(set, clear) {
let reportingPolicy =
Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).Policy;
reportingPolicy.setShowInfobarTimeout = set;
reportingPolicy.clearShowInfobarTimeout = clear;
}
function sendSessionRestoredNotification() {
let reportingPolicyImpl =
Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).TelemetryReportingPolicyImpl;
reportingPolicyImpl.observe(null, "sessionstore-windows-restored", null);
}
/**
* Wait for a tick.
*/
@ -56,6 +69,21 @@ function promiseWaitForNotificationClose(aNotification) {
return deferred.promise;
}
function triggerInfoBar(expectedTimeoutMs) {
let showInfobarCallback = null;
let timeoutMs = null;
fakeShowPolicyTimeout((callback, timeout) => {
showInfobarCallback = callback;
timeoutMs = timeout;
}, () => {});
sendSessionRestoredNotification();
Assert.ok(!!showInfobarCallback, "Must have a timer callback.");
if (expectedTimeoutMs !== undefined) {
Assert.equal(timeoutMs, expectedTimeoutMs, "Timeout should match");
}
showInfobarCallback();
}
let checkInfobarButton = Task.async(function* (aNotification) {
// Check that the button on the data choices infobar does the right thing.
let buttons = aNotification.getElementsByTagName("button");
@ -130,11 +158,11 @@ add_task(function* test_single_window(){
"User not notified about datareporting policy.");
let alertShownPromise = promiseWaitForAlertActive(notificationBox);
// This should be false and trigger the Infobar.
Assert.ok(!TelemetryReportingPolicy.canUpload(),
"User should not be allowed to upload and the infobar should be triggered.");
"User should not be allowed to upload.");
// Wait for the infobar to be displayed.
triggerInfoBar(10 * 1000);
yield alertShownPromise;
Assert.equal(notificationBox.allNotifications.length, 1, "Notification Displayed.");
@ -185,10 +213,11 @@ add_task(function* test_multiple_windows(){
promiseWaitForAlertActive(notificationBoxes[1])
];
// This should be false and trigger the Infobar.
Assert.ok(!TelemetryReportingPolicy.canUpload(),
"User should not be allowed to upload and the infobar should be triggered.");
"User should not be allowed to upload.");
// Wait for the infobars.
triggerInfoBar(10 * 1000);
yield Promise.all(showAlertPromises);
// Both notification were displayed. Close one and check that both gets closed.

@ -5,10 +5,13 @@ const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
// Must run first.
add_task(function* prepare() {
// The test makes only sense if unified complete is enabled.
Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true);
let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
let oldCurrentEngine = Services.search.currentEngine;
Services.search.currentEngine = engine;
registerCleanupFunction(function () {
Services.prefs.clearUserPref("browser.urlbar.unifiedcomplete");
Services.search.currentEngine = oldCurrentEngine;
Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);

@ -1,83 +0,0 @@
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import argparse
import json
import uuid
import sys
import os.path
parser = argparse.ArgumentParser(description='Create install.rdf from manifest.json')
parser.add_argument('--locale')
parser.add_argument('--profile')
parser.add_argument('--uuid')
parser.add_argument('dir')
args = parser.parse_args()
manifestFile = os.path.join(args.dir, 'manifest.json')
manifest = json.load(open(manifestFile))
locale = args.locale
if not locale:
locale = manifest.get('default_locale', 'en-US')
def process_locale(s):
if s.startswith('__MSG_') and s.endswith('__'):
tag = s[6:-2]
path = os.path.join(args.dir, '_locales', locale, 'messages.json')
data = json.load(open(path))
return data[tag]['message']
else:
return s
id = args.uuid
if not id:
id = '{' + str(uuid.uuid4()) + '}'
name = process_locale(manifest['name'])
desc = process_locale(manifest['description'])
version = manifest['version']
installFile = open(os.path.join(args.dir, 'install.rdf'), 'w')
print >>installFile, '<?xml version="1.0"?>'
print >>installFile, '<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"'
print >>installFile, ' xmlns:em="http://www.mozilla.org/2004/em-rdf#">'
print >>installFile
print >>installFile, ' <Description about="urn:mozilla:install-manifest">'
print >>installFile, ' <em:id>{}</em:id>'.format(id)
print >>installFile, ' <em:type>2</em:type>'
print >>installFile, ' <em:name>{}</em:name>'.format(name)
print >>installFile, ' <em:description>{}</em:description>'.format(desc)
print >>installFile, ' <em:version>{}</em:version>'.format(version)
print >>installFile, ' <em:bootstrap>true</em:bootstrap>'
print >>installFile, ' <em:targetApplication>'
print >>installFile, ' <Description>'
print >>installFile, ' <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>'
print >>installFile, ' <em:minVersion>4.0</em:minVersion>'
print >>installFile, ' <em:maxVersion>50.0</em:maxVersion>'
print >>installFile, ' </Description>'
print >>installFile, ' </em:targetApplication>'
print >>installFile, ' </Description>'
print >>installFile, '</RDF>'
installFile.close()
bootstrapPath = os.path.join(os.path.dirname(sys.argv[0]), 'bootstrap.js')
data = open(bootstrapPath).read()
boot = open(os.path.join(args.dir, 'bootstrap.js'), 'w')
boot.write(data)
boot.close()
if args.profile:
os.system('mkdir -p {}/extensions'.format(args.profile))
output = open(args.profile + '/extensions/' + id, 'w')
print >>output, os.path.realpath(args.dir)
output.close()
else:
dir = os.path.realpath(args.dir)
if dir[-1] == os.sep:
dir = dir[:-1]
os.system('cd "{}"; zip ../"{}".xpi -r *'.format(args.dir, os.path.basename(dir)))

@ -60,18 +60,6 @@ document.addEventListener("DOMContentLoaded", function () {
// Update state that depends on preferences.
prefObserver.observe();
// This check can be removed when Tracking Protection is always available.
let tpUIEnabled = false;
try {
tpUIEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.ui.enabled");
} catch (ex) {
// The preference is not available.
}
if (!tpUIEnabled) {
document.getElementById("trackingProtectionSection")
.setAttribute("hidden", "true");
}
}, false);
function openPrivateWindow() {

@ -42,7 +42,7 @@
<div class="list-header">&aboutPrivateBrowsing.info.forgotten;</div>
<ul id="forgotten">
<li>&aboutPrivateBrowsing.info.history;</li>
<li>&aboutPrivateBrowsing.info.search;</li>
<li>&aboutPrivateBrowsing.info.searches;</li>
<li>&aboutPrivateBrowsing.info.cookies;</li>
<li>&aboutPrivateBrowsing.info.temporaryFiles;</li>
</ul>
@ -55,7 +55,7 @@
</ul>
</div>
</div>
<p>&aboutPrivateBrowsing.note;</p>
<p>&aboutPrivateBrowsing.note1;</p>
<a id="learnMore" target="_blank">&aboutPrivateBrowsing.learnMore;</a>
</div>
<div id="trackingProtectionSection"
@ -69,7 +69,7 @@
class="showTpDisabled">&trackingProtection.state.disabled;</span>
</div>
<p id="tpDiagram"/>
<p>&trackingProtection.description;</p>
<p>&trackingProtection.description1;</p>
<!-- Use text links to implement plain styled buttons without an href. -->
<label xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="disableTrackingProtection"
@ -80,7 +80,7 @@
class="text-link showTpDisabled"
value="&trackingProtection.enable;"/>
<p id="tpStartTour"
class="showTpEnabled"><a id="startTour">&trackingProtection.startTour;</a></p>
class="showTpEnabled"><a id="startTour">&trackingProtection.startTour1;</a></p>
</div>
</div>
</body>

@ -17,6 +17,7 @@ support-files =
[browser_privatebrowsing_DownloadLastDirWithCPS.js]
[browser_privatebrowsing_about.js]
tags = trackingprotection
[browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js]
[browser_privatebrowsing_aboutSessionRestore.js]
[browser_privatebrowsing_cache.js]

@ -46,14 +46,12 @@ function* testLinkOpensUrl({ win, tab, elementId, expectedUrl }) {
*/
add_task(function* test_links() {
// Use full version and change the remote URLs to prevent network access.
Services.prefs.setBoolPref("privacy.trackingprotection.ui.enabled", true);
Services.prefs.setCharPref("app.support.baseURL", "https://example.com/");
Services.prefs.setCharPref("privacy.trackingprotection.introURL",
"https://example.com/tour");
registerCleanupFunction(function () {
Services.prefs.clearUserPref("privacy.trackingprotection.introURL");
Services.prefs.clearUserPref("app.support.baseURL");
Services.prefs.clearUserPref("privacy.trackingprotection.ui.enabled");
});
let { win, tab } = yield openAboutPrivateBrowsing();
@ -77,12 +75,10 @@ add_task(function* test_links() {
*/
add_task(function* test_toggleTrackingProtection() {
// Use tour version but disable Tracking Protection.
Services.prefs.setBoolPref("privacy.trackingprotection.ui.enabled", true);
Services.prefs.setBoolPref("privacy.trackingprotection.pbmode.enabled",
true);
registerCleanupFunction(function () {
Services.prefs.clearUserPref("privacy.trackingprotection.pbmode.enabled");
Services.prefs.clearUserPref("privacy.trackingprotection.ui.enabled");
});
let { win, tab } = yield openAboutPrivateBrowsing();

@ -12,13 +12,19 @@
Width of the Private Browsing section.
-->
<!ENTITY aboutPrivateBrowsing.width "25em">
<!-- LOCALIZATION NOTE (aboutPrivateBrowsing.subtitle,
aboutPrivateBrowsing.info.forgotten, aboutPrivateBrowsing.info.kept):
These strings will be replaced by aboutPrivateBrowsing.forgotten and
aboutPrivateBrowsing.kept when the new visual design lands (bug 1192625).
-->
<!ENTITY aboutPrivateBrowsing.title "You're browsing privately">
<!ENTITY aboutPrivateBrowsing.subtitle "In this window, &brandShortName; will not remember any history.">
<!ENTITY aboutPrivateBrowsing.forgotten "In this window, &brandShortName; will not remember:">
<!ENTITY aboutPrivateBrowsing.info.forgotten "Forgotten">
<!ENTITY aboutPrivateBrowsing.info.history "History">
<!ENTITY aboutPrivateBrowsing.info.search "Searches">
<!ENTITY aboutPrivateBrowsing.info.searches "Searches">
<!ENTITY aboutPrivateBrowsing.info.cookies "Cookies">
<!ENTITY aboutPrivateBrowsing.info.temporaryFiles "Temporary Files">
@ -27,7 +33,7 @@
<!ENTITY aboutPrivateBrowsing.info.downloads "Downloads">
<!ENTITY aboutPrivateBrowsing.info.bookmarks "Bookmarks">
<!ENTITY aboutPrivateBrowsing.note "Please note that your employer or Internet service provider can still track the pages you visit.">
<!ENTITY aboutPrivateBrowsing.note1 "Please note that your employer or Internet service provider can still track the pages you visit.">
<!ENTITY aboutPrivateBrowsing.learnMore "Learn More.">
<!-- LOCALIZATION NOTE (trackingProtection.width):
@ -44,8 +50,8 @@
<!ENTITY trackingProtection.state.enabled "ON">
<!ENTITY trackingProtection.state.disabled "OFF">
<!ENTITY trackingProtection.description "Private Windows now block parts of the page that may track your browsing activity.">
<!ENTITY trackingProtection.description1 "Private Windows now block parts of the page that may track your browsing activity.">
<!ENTITY trackingProtection.disable "Turn Tracking Protection Off">
<!ENTITY trackingProtection.enable "Turn Tracking Protection On">
<!ENTITY trackingProtection.startTour "See how this works">
<!ENTITY trackingProtection.startTour1 "See how this works">

@ -87,7 +87,8 @@
#TabsToolbar:not([collapsed="true"]) + #nav-bar {
border-top: 1px solid hsla(0,0%,0%,.3) !important;
background-clip: padding-box;
margin-top: -1px; /* Move up into the TabsToolbar for the inner highlight at the top of the nav-bar */
/* Move up into the TabsToolbar for the inner highlight at the top of the nav-bar */
margin-top: calc(-1 * var(--navbar-tab-toolbar-highlight-overlap));
/* Position the toolbar above the bottom of background tabs */
position: relative;
z-index: 1;
@ -1568,7 +1569,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
#TabsToolbar .toolbarbutton-1 {
margin-bottom: var(--tab-toolbar-navbar-overlap);
margin-bottom: var(--navbar-tab-toolbar-highlight-overlap);
}
#alltabs-button {

@ -207,7 +207,7 @@ toolbarseparator {
#TabsToolbar:not([collapsed="true"]) + #nav-bar:-moz-lwtheme {
border-top: 1px solid hsla(0,0%,0%,.3);
background-clip: padding-box;
margin-top: calc(-1 * var(--tab-toolbar-navbar-overlap));
margin-top: calc(-1 * var(--navbar-tab-toolbar-highlight-overlap));
/* Position the toolbar above the bottom of background tabs */
position: relative;
z-index: 1;
@ -219,7 +219,7 @@ toolbarseparator {
#main-window[tabsintitlebar] #TabsToolbar:not([collapsed="true"]) + #nav-bar:not(:-moz-lwtheme) {
border-top: 1px solid hsla(0,0%,0%,.2);
background-clip: padding-box;
margin-top: calc(-1 * var(--tab-toolbar-navbar-overlap));
margin-top: calc(-1 * var(--navbar-tab-toolbar-highlight-overlap));
/* Position the toolbar above the bottom of background tabs */
position: relative;
z-index: 1;
@ -2847,7 +2847,7 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
#TabsToolbar .toolbarbutton-1 {
margin-bottom: var(--tab-toolbar-navbar-overlap);
margin-bottom: var(--navbar-tab-toolbar-highlight-overlap);
}
#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {

@ -8,6 +8,7 @@
:root {
--tab-toolbar-navbar-overlap: 0px;
--navbar-tab-toolbar-highlight-overlap: 0px;
--space-above-tabbar: 0px;
--toolbarbutton-text-shadow: none;
--backbutton-urlbar-overlap: 0px;
@ -310,11 +311,6 @@ searchbar:not([oneoffui]) .search-go-button {
border-color: transparent;
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:not([collapsed]),
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:not([collapsed]) {
margin-bottom: 0;
}
.tabbrowser-tab {
/* We normally rely on other tab elements for pointer events, but this
theme hides those so we need it set here instead */

@ -6,6 +6,7 @@
:root {
--tab-toolbar-navbar-overlap: 1px;
--navbar-tab-toolbar-highlight-overlap: 1px;
--tab-min-height: 31px;
}
#TabsToolbar {
@ -263,7 +264,7 @@
background-image: url(chrome://browser/skin/tabbrowser/tab-overflow-indicator.png);
background-size: 100% 100%;
width: 14px;
margin-bottom: var(--tab-toolbar-navbar-overlap);
margin-bottom: var(--navbar-tab-toolbar-highlight-overlap);
pointer-events: none;
position: relative;
z-index: 3; /* the selected tab's z-index + 1 */

@ -308,7 +308,8 @@
}
#TabsToolbar:not([collapsed="true"]) + #nav-bar {
margin-top: -1px; /* Move up into the TabsToolbar for the inner highlight at the top of the nav-bar */
/* Move up into the TabsToolbar for the inner highlight at the top of the nav-bar */
margin-top: calc(-1 * var(--navbar-tab-toolbar-highlight-overlap));
/* Position the toolbar above the bottom of background tabs */
position: relative;
z-index: 1;
@ -928,7 +929,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
}
#TabsToolbar .toolbarbutton-1 {
margin-bottom: var(--tab-toolbar-navbar-overlap);
margin-bottom: var(--navbar-tab-toolbar-highlight-overlap);
}
#TabsToolbar .toolbarbutton-1:not([disabled=true]):hover,

@ -6,7 +6,7 @@
"use strict";
// Don't modify this, instead set dom.push.debug.
let gDebuggingEnabled = true;
let gDebuggingEnabled = false;
function debug(s) {
if (gDebuggingEnabled) {

@ -213,6 +213,9 @@ class RefTest(object):
# Likewise for safebrowsing.
prefs['browser.safebrowsing.enabled'] = False
prefs['browser.safebrowsing.malware.enabled'] = False
# Likewise for tracking protection.
prefs['privacy.trackingprotection.enabled'] = False
prefs['privacy.trackingprotection.pbmode.enabled'] = False
# And for snippets.
prefs['browser.snippets.enabled'] = False
prefs['browser.snippets.syncPromo.enabled'] = False

@ -3969,6 +3969,9 @@ public class BrowserApp extends GeckoApp
if (inGuestMode) {
return StartupAction.GUEST;
}
if (RestrictedProfiles.isRestrictedProfile(this)) {
return StartupAction.RESTRICTED;
}
return (passedURL == null ? StartupAction.NORMAL : StartupAction.URL);
}
}

@ -137,7 +137,8 @@ public abstract class GeckoApp
URL, /* launched with a passed URL */
PREFETCH, /* launched with a passed URL that we prefetch */
WEBAPP, /* launched as a webapp runtime */
GUEST /* launched in guest browsing */
GUEST, /* launched in guest browsing */
RESTRICTED /* launched with restricted profile */
}
public static final String ACTION_ALERT_CALLBACK = "org.mozilla.gecko.ACTION_ALERT_CALLBACK";

@ -74,24 +74,15 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)));
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.BOOKMARKS));
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.READING_LIST));
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.HISTORY));
final PanelConfig historyEntry = createBuiltinPanelConfig(mContext, PanelType.HISTORY);
final PanelConfig recentTabsEntry = createBuiltinPanelConfig(mContext, PanelType.RECENT_TABS);
// We disable Synced Tabs for guest mode profiles.
final PanelConfig remoteTabsEntry;
// We disable Synced Tabs for guest mode / restricted profiles.
if (RestrictedProfiles.isAllowed(mContext, Restriction.DISALLOW_MODIFY_ACCOUNTS)) {
remoteTabsEntry = createBuiltinPanelConfig(mContext, PanelType.REMOTE_TABS);
} else {
remoteTabsEntry = null;
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.REMOTE_TABS));
}
panelConfigs.add(historyEntry);
panelConfigs.add(recentTabsEntry);
if (remoteTabsEntry != null) {
panelConfigs.add(remoteTabsEntry);
}
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.RECENT_TABS));
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.READING_LIST));
return new State(panelConfigs, true);
}

@ -34,10 +34,10 @@ public class AboutHomeComponent extends BaseComponent {
private static final List<PanelType> PANEL_ORDERING = Arrays.asList(
PanelType.TOP_SITES,
PanelType.BOOKMARKS,
PanelType.READING_LIST,
PanelType.HISTORY,
PanelType.REMOTE_TABS,
PanelType.RECENT_TABS,
PanelType.REMOTE_TABS
PanelType.READING_LIST
);
// The percentage of the panel to swipe between 0 and 1. This value was set through

@ -1087,7 +1087,7 @@ pref("privacy.donottrackheader.enabled", false);
// Enforce tracking protection in all modes
pref("privacy.trackingprotection.enabled", false);
// Enforce tracking protection in Private Browsing mode
pref("privacy.trackingprotection.pbmode.enabled", false);
pref("privacy.trackingprotection.pbmode.enabled", true);
pref("dom.event.contextmenu.enabled", true);
pref("dom.event.clipboardevents.enabled", true);

@ -5,7 +5,7 @@
},
"global": {
"talos_repo": "https://hg.mozilla.org/build/talos",
"talos_revision": "d44548b8feb9"
"talos_revision": "c7446ecc3bfb"
},
"extra_options": {
"android": [ "--apkPath=%(apk_path)s" ]

@ -141,6 +141,10 @@ let Management = {
this.lazyInit();
this.emitter.emit(hook, ...args);
},
off(hook, callback) {
this.emitter.off(hook, callback);
}
};
// A MessageBroker that's used to send and receive messages for
@ -529,13 +533,13 @@ Extension.prototype = {
},
startup() {
GlobalManager.init(this);
return Promise.all([this.readManifest(), this.readLocaleMessages()]).then(([manifest, messages]) => {
if (this.hasShutdown) {
return;
}
GlobalManager.init(this);
this.manifest = manifest;
this.localeMessages = messages;

@ -337,14 +337,10 @@ let TelemetryReportingPolicyImpl = {
return false;
}
// Make sure the user is notified of the current policy. If he isn't, don't try
// to upload anything.
if (!this._ensureUserNotified()) {
return false;
}
// Submission is enabled and user is notified: upload is allowed.
return true;
// Submission is enabled. We enable upload if user is notified or we need to bypass
// the policy.
const bypassNotification = Preferences.get(PREF_BYPASS_NOTIFICATION, false);
return this.isUserNotifiedOfCurrentPolicy || bypassNotification;
},
/**
@ -358,26 +354,30 @@ let TelemetryReportingPolicyImpl = {
},
/**
* Make sure the user is notified about the policy before allowing upload.
* @return {Boolean} True if the user was notified, false otherwise.
* Show the data choices infobar if the user wasn't already notified and data submission
* is enabled.
*/
_ensureUserNotified: function() {
const BYPASS_NOTIFICATION = Preferences.get(PREF_BYPASS_NOTIFICATION, false);
if (this.isUserNotifiedOfCurrentPolicy || BYPASS_NOTIFICATION) {
return true;
_showInfobar: function() {
if (!this.dataSubmissionEnabled) {
this._log.trace("_showInfobar - Data submission disabled by the policy.");
return;
}
const bypassNotification = Preferences.get(PREF_BYPASS_NOTIFICATION, false);
if (this.isUserNotifiedOfCurrentPolicy || bypassNotification) {
this._log.trace("_showInfobar - User already notified or bypassing the policy.");
return;
}
this._log.trace("ensureUserNotified - User not notified, notifying now.");
if (this._notificationInProgress) {
this._log.trace("ensureUserNotified - User not notified, notification in progress.");
return false;
this._log.trace("_showInfobar - User not notified, notification already in progress.");
return;
}
this._log.trace("_showInfobar - User not notified, notifying now.");
this._notificationInProgress = true;
let request = new NotifyPolicyRequest(this._log);
Observers.notify("datareporting:notify-data-policy:request", request);
return false;
},
/**
@ -412,7 +412,7 @@ let TelemetryReportingPolicyImpl = {
this._startupNotificationTimerId = Policy.setShowInfobarTimeout(
// Calling |canUpload| eventually shows the infobar, if needed.
() => this.canUpload(), delay);
() => this._showInfobar(), delay);
// We performed at least a run, flip the firstRun preference.
Preferences.set(PREF_FIRST_RUN, false);
},

@ -113,6 +113,20 @@ function isDeletionPing(aPing) {
return isV4PingFormat(aPing) && (aPing.type == PING_TYPE_DELETION);
}
/**
* Save the provided ping as a pending ping. If it's a deletion ping, save it
* to a special location.
* @param {Object} aPing The ping to save.
* @return {Promise} A promise resolved when the ping is saved.
*/
function savePing(aPing) {
if (isDeletionPing(aPing)) {
return TelemetryStorage.saveDeletionPing(aPing);
} else {
return TelemetryStorage.savePendingPing(aPing);
}
}
function tomorrow(date) {
let d = new Date(date);
d.setDate(d.getDate() + 1);
@ -673,7 +687,7 @@ let TelemetrySendImpl = {
// Sending is disabled or throttled, add this to the persisted pending pings.
this._log.trace("submitPing - can't send ping now, persisting to disk - " +
"canSendNow: " + this.canSendNow);
return TelemetryStorage.savePendingPing(ping);
return savePing(ping);
}
// Let the scheduler trigger sending pings if possible.
@ -716,11 +730,7 @@ let TelemetrySendImpl = {
} catch (ex) {
this._log.info("sendPings - ping " + ping.id + " not sent, saving to disk", ex);
// Deletion pings must be saved to a special location.
if (isDeletionPing(ping)) {
yield TelemetryStorage.saveDeletionPing(ping);
} else {
yield TelemetryStorage.savePendingPing(ping);
}
yield savePing(ping);
} finally {
this._currentPings.delete(ping.id);
}
@ -1022,7 +1032,7 @@ let TelemetrySendImpl = {
_persistCurrentPings: Task.async(function*() {
for (let [id, ping] of this._currentPings) {
try {
yield TelemetryStorage.savePendingPing(ping);
yield savePing(ping);
this._log.trace("_persistCurrentPings - saved ping " + id);
} catch (ex) {
this._log.error("_persistCurrentPings - failed to save ping " + id, ex);

@ -47,6 +47,8 @@ skip-if = buildapp == 'mulet'
skip-if = buildapp == 'mulet'
[test_framerate_05.html]
skip-if = buildapp == 'mulet'
[test_framerate_06.html]
skip-if = buildapp == 'mulet'
[test_getProcess.html]
skip-if = buildapp == 'mulet'
[test_inspector-anonymous.html]

@ -0,0 +1,82 @@
<!DOCTYPE HTML>
<html>
<!--
Bug 1171489 - Tests if the framerate actor does not record timestamps from multiple frames.
-->
<head>
<meta charset="utf-8">
<title>Framerate actor test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script>
window.onload = function() {
SimpleTest.waitForExplicitFinish();
var {FramerateFront} = require("devtools/server/actors/framerate");
var {TargetFactory} = require("devtools/framework/target");
var url = document.getElementById("testContent").href;
attachURL(url, onTab);
function onTab(_, client, form, contentDoc) {
var contentWin = contentDoc.defaultView;
var chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
var selectedTab = chromeWin.gBrowser.selectedTab;
var target = TargetFactory.forTab(selectedTab);
var front = FramerateFront(client, form);
front.startRecording().then(() => {
window.setTimeout(() => {
// Wait for the iframe to be loaded again
window.addEventListener("message", function loaded (event) {
if (event.data === "ready") {
window.removeEventListener("message", loaded);
window.setTimeout(() => {
front.stopRecording().then(ticks => {
onRecordingStopped(client, ticks);
});
}, 1000);
}
});
contentWin.location.reload();
}, 1000);
});
}
function onRecordingStopped(client, ticks) {
var diffs = [];
info(`Got ${ticks.length} ticks.`);
for (var i = 1; i < ticks.length; i++) {
var prev = ticks[i - 1];
var curr = ticks[i];
diffs.push(curr - prev);
info(curr + " - " + (curr - prev));
}
// 1000 / 60 => 16.666... so we shouldn't get more than diffs of 16.66.. but
// when we get ticks from other frames they're usually at diffs of < 1. Sometimes
// ticks can still be less than 16ms even on one frame (usually following a very slow
// frame), so use a low number (2) to be our threshold
var THRESHOLD = 2;
ok(ticks.length >= 60, "we should have 2 seconds worth of ticks, atleast 60 ticks");
var belowThreshold = diffs.filter(v => v <= THRESHOLD);
ok(belowThreshold.length <= 10, "we should have very few frames less than the threshold");
client.close(() => {
DebuggerServer.destroy();
SimpleTest.finish()
});
}
}
</script>
</pre>
<a id="testContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
</body>
</html>

@ -92,6 +92,7 @@ let Framerate = exports.Framerate = Class({
*/
_onGlobalCreated: function (win) {
if (this._recording) {
this._contentWin.cancelAnimationFrame(this._rafID);
this._rafID = this._contentWin.requestAnimationFrame(this._onRefreshDriverTick);
}
}

@ -22,6 +22,9 @@
<!ENTITY removeall.label "Remove All">
<!ENTITY removeall.accesskey "A">
<!ENTITY addLogin.label "Add Login">
<!ENTITY addLogin.accesskey "L">
<!ENTITY import.label "Import…">
<!ENTITY import.accesskey "I">

@ -54,6 +54,8 @@ showPasswordsAccessKey=P
noMasterPasswordPrompt=Are you sure you wish to show your passwords?
removeAllPasswordsPrompt=Are you sure you wish to remove all passwords?
removeAllPasswordsTitle=Remove all passwords
removeLoginPrompt=Are you sure you wish to remove this login?
removeLoginTitle=Remove login
loginsSpielAll=Passwords for the following sites are stored on your computer:
loginsSpielFiltered=The following passwords match your search:
# LOCALIZATION NOTE (loginHostAge):
@ -63,3 +65,5 @@ loginHostAge=%1$S (%2$S)
# LOCALIZATION NOTE (noUsername):
# String is used on the context menu when a login doesn't have a username.
noUsername=No username
duplicateLoginTitle=Login already exists
duplicateLogin=A duplicate login already exists.

@ -8,6 +8,10 @@ Components.utils.import("resource://gre/modules/Extension.jsm");
let extension;
function install(data, reason)
{
}
function startup(data, reason)
{
extension = new Extension(data);
@ -18,3 +22,7 @@ function shutdown(data, reason)
{
extension.shutdown();
}
function uninstall(data, reason)
{
}

@ -121,7 +121,8 @@ const DIR_TRASH = "trash";
const FILE_DATABASE = "extensions.json";
const FILE_OLD_CACHE = "extensions.cache";
const FILE_INSTALL_MANIFEST = "install.rdf";
const FILE_RDF_MANIFEST = "install.rdf";
const FILE_WEB_MANIFEST = "manifest.json";
const FILE_XPI_ADDONS_LIST = "extensions.ini";
const KEY_PROFILEDIR = "ProfD";
@ -202,13 +203,21 @@ const TYPES = {
experiment: 128,
};
// Some add-on types that we track internally are presented as other types
// externally
const TYPE_ALIASES = {
"webextension": "extension",
};
const RESTARTLESS_TYPES = new Set([
"webextension",
"dictionary",
"experiment",
"locale",
]);
const SIGNED_TYPES = new Set([
"webextension",
"extension",
"experiment",
]);
@ -641,6 +650,65 @@ function createAddonDetails(id, aAddon) {
};
}
/**
* Converts an internal add-on type to the type presented through the API.
*
* @param aType
* The internal add-on type
* @return an external add-on type
*/
function getExternalType(aType) {
if (aType in TYPE_ALIASES)
return TYPE_ALIASES[aType];
return aType;
}
function getManifestFileForDir(aDir) {
let file = aDir.clone();
file.append(FILE_WEB_MANIFEST);
if (file.exists() && file.isFile())
return file;
file.leafName = FILE_RDF_MANIFEST;
if (file.exists() && file.isFile())
return file;
return null;
}
function getManifestEntryForZipReader(aZipReader) {
if (aZipReader.hasEntry(FILE_WEB_MANIFEST))
return FILE_WEB_MANIFEST;
if (aZipReader.hasEntry(FILE_RDF_MANIFEST))
return FILE_RDF_MANIFEST;
return null;
}
/**
* Converts a list of API types to a list of API types and any aliases for those
* types.
*
* @param aTypes
* An array of types or null for all types
* @return an array of types or null for all types
*/
function getAllAliasesForTypes(aTypes) {
if (!aTypes)
return null;
// Build a set of all requested types and their aliases
let typeset = new Set(aTypes);
for (let alias of Object.keys(TYPE_ALIASES)) {
// Ignore any requested internal types
typeset.delete(alias);
// Add any alias for the internal type
if (typeset.has(TYPE_ALIASES[alias]))
typeset.add(alias);
}
return [...typeset];
}
/**
* Converts an RDF literal, resource or integer into a string.
*
@ -673,6 +741,96 @@ function getRDFProperty(aDs, aResource, aProperty) {
return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
}
/**
* Reads an AddonInternal object from a manifest stream.
*
* @param aStream
* An open stream to read the manifest from
* @return an AddonInternal object
* @throws if the install manifest in the stream is corrupt or could not
* be read
*/
function loadManifestFromWebManifest(aStream) {
let decoder = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
let manifest = decoder.decodeFromStream(aStream, aStream.available());
function findProp(obj, current, properties) {
if (properties.length == 0)
return obj;
let field = properties[0];
current += "." + field;
if (!obj || !(field in obj)) {
throw new Error("Manifest file was missing required property " + current.substring(1));
}
return findProp(obj[field], current, properties.slice(1));
}
function getProp(path) {
return findProp(manifest, "", path.split("."));
}
function getOptionalProp(path, defValue = null) {
try {
return findProp(manifest, "", path.split("."));
}
catch (e) {
return defValue;
}
}
let mVersion = getProp("manifest_version");
if (mVersion != 2) {
throw new Error("Expected manifest_version to be 2 but was " + mVersion);
}
let addon = new AddonInternal();
addon.id = getProp("applications.gecko.id");
if (!gIDTest.test(addon.id))
throw new Error("Illegal add-on ID " + addon.id);
addon.version = getProp("version");
addon.type = "webextension";
addon.unpack = false;
addon.strictCompatibility = true;
addon.bootstrap = true;
addon.hasBinaryComponents = false;
addon.multiprocessCompatible = true;
addon.internalName = null;
addon.updateURL = null;
addon.updateKey = null;
addon.optionsURL = null;
addon.optionsType = null;
addon.aboutURL = null;
addon.iconURL = null;
addon.icon64URL = null;
addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
addon.defaultLocale = {
name: getProp("name"),
description: getOptionalProp("description"),
creator: null,
homepageURL: null,
developers: null,
translators: null,
contributors: null,
}
addon.targetApplications = [{
id: TOOLKIT_ID,
minVersion: "42a1",
maxVersion: "*",
}];
addon.locales = [];
addon.targetPlatforms = [];
addon.userDisabled = false;
addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED;
return addon;
}
/**
* Reads an AddonInternal object from an RDF stream.
*
@ -934,14 +1092,17 @@ function loadManifestFromRDF(aUri, aStream) {
addon.targetPlatforms = [];
}
return addon;
}
function defineSyncGUID(aAddon) {
// Load the storage service before NSS (nsIRandomGenerator),
// to avoid a SQLite initialization error (bug 717904).
let storage = Services.storage;
// Define .syncGUID as a lazy property which is also settable
Object.defineProperty(addon, "syncGUID", {
Object.defineProperty(aAddon, "syncGUID", {
get: () => {
// Generate random GUID used for Sync.
// This was lifted from util.js:makeGUID() from services-sync.
let rng = Cc["@mozilla.org/security/random-generator;1"].
@ -953,19 +1114,17 @@ function loadManifestFromRDF(aUri, aStream) {
let guid = btoa(byte_string).replace(/\+/g, '-')
.replace(/\//g, '_');
delete addon.syncGUID;
addon.syncGUID = guid;
delete aAddon.syncGUID;
aAddon.syncGUID = guid;
return guid;
},
set: (val) => {
delete addon.syncGUID;
addon.syncGUID = val;
delete aAddon.syncGUID;
aAddon.syncGUID = val;
},
configurable: true,
enumerable: true,
});
return addon;
}
/**
@ -993,11 +1152,22 @@ let loadManifestFromDir = Task.async(function* loadManifestFromDir(aDir) {
return size;
}
let file = aDir.clone();
file.append(FILE_INSTALL_MANIFEST);
if (!file.exists() || !file.isFile())
function loadFromRDF(aFile, aStream) {
let addon = loadManifestFromRDF(Services.io.newFileURI(aFile), aStream);
let file = aDir.clone();
file.append("chrome.manifest");
let chromeManifest = ChromeManifestParser.parseSync(Services.io.newFileURI(file));
addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
"binary-component");
return addon;
}
let file = getManifestFileForDir(aDir);
if (!file) {
throw new Error("Directory " + aDir.path + " does not contain a valid " +
"install manifest");
}
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
@ -1007,19 +1177,17 @@ let loadManifestFromDir = Task.async(function* loadManifestFromDir(aDir) {
bis.init(fis, 4096);
try {
let addon = loadManifestFromRDF(Services.io.newFileURI(file), bis);
let addon = file.leafName == FILE_WEB_MANIFEST ?
loadManifestFromWebManifest(bis) :
loadFromRDF(file, bis);
addon._sourceBundle = aDir.clone();
addon.size = getFileSize(aDir);
file = aDir.clone();
file.append("chrome.manifest");
let chromeManifest = ChromeManifestParser.parseSync(Services.io.newFileURI(file));
addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
"binary-component");
addon.signedState = yield verifyDirSignedState(aDir, addon);
addon.appDisabled = !isUsableAddon(addon);
defineSyncGUID(addon);
return addon;
}
finally {
@ -1037,20 +1205,9 @@ let loadManifestFromDir = Task.async(function* loadManifestFromDir(aDir) {
* @throws if the XPI file does not contain a valid install manifest
*/
let loadManifestFromZipReader = Task.async(function* loadManifestFromZipReader(aZipReader) {
let zis = aZipReader.getInputStream(FILE_INSTALL_MANIFEST);
let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
createInstance(Ci.nsIBufferedInputStream);
bis.init(zis, 4096);
try {
let uri = buildJarURI(aZipReader.file, FILE_INSTALL_MANIFEST);
let addon = loadManifestFromRDF(uri, bis);
addon._sourceBundle = aZipReader.file;
addon.size = 0;
let entries = aZipReader.findEntries(null);
while (entries.hasMore())
addon.size += aZipReader.getEntry(entries.getNext()).realSize;
function loadFromRDF(aStream) {
let uri = buildJarURI(aZipReader.file, FILE_RDF_MANIFEST);
let addon = loadManifestFromRDF(uri, aStream);
// Binary components can only be loaded from unpacked addons.
if (addon.unpack) {
@ -1062,9 +1219,37 @@ let loadManifestFromZipReader = Task.async(function* loadManifestFromZipReader(a
addon.hasBinaryComponents = false;
}
addon.signedState = yield verifyZipSignedState(aZipReader.file, addon);
return addon;
}
let entry = getManifestEntryForZipReader(aZipReader);
if (!entry) {
throw new Error("File " + aZipReader.file.path + " does not contain a valid " +
"install manifest");
}
let zis = aZipReader.getInputStream(entry);
let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
createInstance(Ci.nsIBufferedInputStream);
bis.init(zis, 4096);
try {
let addon = entry == FILE_WEB_MANIFEST ?
loadManifestFromWebManifest(bis) :
loadFromRDF(bis);
addon._sourceBundle = aZipReader.file;
addon.size = 0;
let entries = aZipReader.findEntries(null);
while (entries.hasMore())
addon.size += aZipReader.getEntry(entries.getNext()).realSize;
addon.signedState = yield verifyZipSignedState(aZipReader.file, addon);
addon.appDisabled = !isUsableAddon(addon);
defineSyncGUID(addon);
return addon;
}
finally {
@ -1640,15 +1825,12 @@ XPIState.prototype = {
changed = true;
}
}
// if the add-on is disabled, modified time is the install.rdf time, if any.
// If {path}/install.rdf doesn't exist, we assume this is a packed .xpi and use
// if the add-on is disabled, modified time is the install manifest time, if
// any. If no manifest exists, we assume this is a packed .xpi and use
// the time stamp of {path}
try {
// Get the install.rdf update time, if any.
// XXX This will eventually also need to check for package.json or whatever
// the new manifest is named.
let maniFile = aFile.clone();
maniFile.append(FILE_INSTALL_MANIFEST);
// Get the install manifest update time, if any.
let maniFile = getManifestFileForDir(aFile);
if (!(aId in XPIProvider._mostRecentlyModifiedFile)) {
XPIProvider._mostRecentlyModifiedFile[aId] = maniFile.leafName;
}
@ -2660,12 +2842,11 @@ this.XPIProvider = {
if (isDir) {
// Check if the directory contains an install manifest.
let manifest = stageDirEntry.clone();
manifest.append(FILE_INSTALL_MANIFEST);
let manifest = getManifestFileForDir(stageDirEntry);
// If the install manifest doesn't exist uninstall this add-on in this
// install location.
if (!manifest.exists()) {
if (!manifest) {
logger.debug("Processing uninstall of " + id + " in " + aLocation.name);
try {
aLocation.uninstallAddon(id);
@ -3959,7 +4140,9 @@ this.XPIProvider = {
* A callback to pass an array of Addons to
*/
getAddonsByTypes: function XPI_getAddonsByTypes(aTypes, aCallback) {
XPIDatabase.getVisibleAddons(aTypes, function getAddonsByTypes_getVisibleAddons(aAddons) {
let typesToGet = getAllAliasesForTypes(aTypes);
XPIDatabase.getVisibleAddons(typesToGet, function getAddonsByTypes_getVisibleAddons(aAddons) {
aCallback([createWrapper(a) for each (a in aAddons)]);
});
},
@ -3988,7 +4171,9 @@ this.XPIProvider = {
*/
getAddonsWithOperationsByTypes:
function XPI_getAddonsWithOperationsByTypes(aTypes, aCallback) {
XPIDatabase.getVisibleAddonsWithPendingOperations(aTypes,
let typesToGet = getAllAliasesForTypes(aTypes);
XPIDatabase.getVisibleAddonsWithPendingOperations(typesToGet,
function getAddonsWithOpsByTypes_getVisibleAddonsWithPendingOps(aAddons) {
let results = [createWrapper(a) for each (a in aAddons)];
XPIProvider.installs.forEach(function(aInstall) {
@ -4012,7 +4197,7 @@ this.XPIProvider = {
getInstallsByTypes: function XPI_getInstallsByTypes(aTypes, aCallback) {
let results = [];
this.installs.forEach(function(aInstall) {
if (!aTypes || aTypes.indexOf(aInstall.type) >= 0)
if (!aTypes || aTypes.indexOf(getExternalType(aInstall.type)) >= 0)
results.push(aInstall.wrapper);
});
aCallback(results);
@ -4451,6 +4636,8 @@ this.XPIProvider = {
let uri = getURIForResourceInFile(aFile, "bootstrap.js").spec;
if (aType == "dictionary")
uri = "resource://gre/modules/addons/SpellCheckDictionaryBootstrap.js"
else if (aType == "webextension")
uri = "resource://gre/modules/addons/WebExtensionBootstrap.js"
this.bootstrapScopes[aId] =
new Cu.Sandbox(principal, { sandboxName: uri,
@ -6155,11 +6342,13 @@ function AddonInstallWrapper(aInstall) {
});
#endif
["name", "type", "version", "icons", "releaseNotesURI", "file", "state", "error",
["name", "version", "icons", "releaseNotesURI", "file", "state", "error",
"progress", "maxProgress", "certificate", "certName"].forEach(function(aProp) {
this.__defineGetter__(aProp, function AIW_propertyGetter() aInstall[aProp]);
}, this);
this.__defineGetter__("type", () => getExternalType(aInstall.type));
this.__defineGetter__("iconURL", function AIW_iconURL() aInstall.icons[32]);
this.__defineGetter__("existingAddon", function AIW_existingAddonGetter() {
@ -6742,7 +6931,7 @@ function AddonWrapper(aAddon) {
return [objValue, false];
}
["id", "syncGUID", "version", "type", "isCompatible", "isPlatformCompatible",
["id", "syncGUID", "version", "isCompatible", "isPlatformCompatible",
"providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
"softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents",
"strictCompatibility", "compatibilityOverrides", "updateURL",
@ -6750,6 +6939,8 @@ function AddonWrapper(aAddon) {
this.__defineGetter__(aProp, function AddonWrapper_propertyGetter() aAddon[aProp]);
}, this);
this.__defineGetter__("type", () => getExternalType(aAddon.type));
["fullDescription", "developerComments", "eula", "supportURL",
"contributionURL", "contributionAmount", "averageRating", "reviewCount",
"reviewURL", "totalDownloads", "weeklyDownloads", "dailyUsers",

@ -13,6 +13,7 @@ EXTRA_JS_MODULES.addons += [
'GMPProvider.jsm',
'LightweightThemeImageOptimizer.jsm',
'SpellCheckDictionaryBootstrap.js',
'WebExtensionBootstrap.js',
]
# Don't ship unused providers on Android

@ -0,0 +1,10 @@
{
"name": "Web Extension Name",
"version": "1.0",
"manifest_version": 2,
"applications": {
"gecko": {
"id": "webextension1@tests.mozilla.org"
}
}
}

@ -769,6 +769,62 @@ function writeInstallRDFForExtension(aData, aDir, aId, aExtraFile) {
return writeInstallRDFToXPI(aData, aDir, aId, aExtraFile);
}
/**
* Writes a manifest.json manifest into an extension using the properties passed
* in a JS object.
*
* @param aManifest
* The data to write
* @param aDir
* The install directory to add the extension to
* @param aId
* An optional string to override the default installation aId
* @return A file pointing to where the extension was installed
*/
function writeWebManifestForExtension(aData, aDir, aId = undefined) {
if (!aId)
aId = aData.applications.gecko.id;
if (TEST_UNPACKED) {
let dir = aDir.clone();
dir.append(aId);
if (!dir.exists())
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let file = dir.clone();
file.append("manifest.json");
if (file.exists())
file.remove(true);
let data = JSON.stringify(aData);
let fos = AM_Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(AM_Ci.nsIFileOutputStream);
fos.init(file,
FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE,
FileUtils.PERMS_FILE, 0);
fos.write(data, data.length);
fos.close();
return dir;
}
else {
let file = aDir.clone();
file.append(aId + ".xpi");
let stream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(AM_Ci.nsIStringInputStream);
stream.setData(JSON.stringify(aData), -1);
let zipW = AM_Cc["@mozilla.org/zipwriter;1"].
createInstance(AM_Ci.nsIZipWriter);
zipW.open(file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE);
zipW.addEntryStream("manifest.json", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
stream, false);
zipW.close();
return file;
}
}
/**
* Writes an install.rdf manifest into a packed extension using the properties passed
* in a JS object. The objects should contain a property for each property to

@ -0,0 +1,189 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
const ID = "webextension1@tests.mozilla.org";
const profileDir = gProfD.clone();
profileDir.append("extensions");
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
startupManager();
const { GlobalManager, Management } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
function promiseAddonStartup() {
return new Promise(resolve => {
let listener = (extension) => {
Management.off("startup", listener);
resolve(extension);
}
Management.on("startup", listener);
});
}
add_task(function*() {
do_check_eq(GlobalManager.count, 0);
do_check_false(GlobalManager.extensionMap.has(ID));
yield Promise.all([
promiseInstallAllFiles([do_get_addon("webextension_1")], true),
promiseAddonStartup()
]);
do_check_eq(GlobalManager.count, 1);
do_check_true(GlobalManager.extensionMap.has(ID));
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_eq(addon.version, "1.0");
do_check_eq(addon.name, "Web Extension Name");
do_check_true(addon.isCompatible);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
// Should persist through a restart
yield promiseShutdownManager();
do_check_eq(GlobalManager.count, 0);
do_check_false(GlobalManager.extensionMap.has(ID));
startupManager();
yield promiseAddonStartup();
do_check_eq(GlobalManager.count, 1);
do_check_true(GlobalManager.extensionMap.has(ID));
addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_eq(addon.version, "1.0");
do_check_eq(addon.name, "Web Extension Name");
do_check_true(addon.isCompatible);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
let file = getFileForAddon(profileDir, ID);
do_check_true(file.exists());
addon.userDisabled = true;
do_check_eq(GlobalManager.count, 0);
do_check_false(GlobalManager.extensionMap.has(ID));
addon.userDisabled = false;
yield promiseAddonStartup();
do_check_eq(GlobalManager.count, 1);
do_check_true(GlobalManager.extensionMap.has(ID));
addon.uninstall();
do_check_eq(GlobalManager.count, 0);
do_check_false(GlobalManager.extensionMap.has(ID));
yield promiseShutdownManager();
});
// Writing the manifest direct to the profile should work
add_task(function*() {
writeWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
applications: {
gecko: {
id: ID
}
}
}, profileDir);
startupManager();
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_eq(addon.version, "1.0");
do_check_eq(addon.name, "Web Extension Name");
do_check_true(addon.isCompatible);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
let file = getFileForAddon(profileDir, ID);
do_check_true(file.exists());
addon.uninstall();
yield promiseRestartManager();
});
// Missing ID should cause a failure
add_task(function*() {
writeWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
}, profileDir, ID);
yield promiseRestartManager();
let addon = yield promiseAddonByID(ID);
do_check_eq(addon, null);
let file = getFileForAddon(profileDir, ID);
do_check_false(file.exists());
yield promiseRestartManager();
});
// Missing version should cause a failure
add_task(function*() {
writeWebManifestForExtension({
name: "Web Extension Name",
manifest_version: 2,
applications: {
gecko: {
id: ID
}
}
}, profileDir);
yield promiseRestartManager();
let addon = yield promiseAddonByID(ID);
do_check_eq(addon, null);
let file = getFileForAddon(profileDir, ID);
do_check_false(file.exists());
yield promiseRestartManager();
});
// Incorrect manifest version should cause a failure
add_task(function*() {
writeWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 1,
applications: {
gecko: {
id: ID
}
}
}, profileDir);
yield promiseRestartManager();
let addon = yield promiseAddonByID(ID);
do_check_eq(addon, null);
let file = getFileForAddon(profileDir, ID);
do_check_false(file.exists());
yield promiseRestartManager();
});

@ -285,4 +285,5 @@ run-sequentially = Uses global XCurProcD dir.
[test_overrideblocklist.js]
run-sequentially = Uses global XCurProcD dir.
[test_sourceURI.js]
[test_webextension.js]
[test_bootstrap_globals.js]

@ -26,4 +26,5 @@ skip-if = appname != "firefox"
[test_XPIcancel.js]
[test_XPIStates.js]
[include:xpcshell-shared.ini]