diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
index d7fb454745c0..fb1109df5947 100644
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1015,6 +1015,8 @@ pref("security.sandbox.content.level", 1);
// This setting may not be required anymore once we decide to permanently
// enable the content sandbox.
pref("security.sandbox.content.level", 2);
+pref("security.sandbox.content.write_path_whitelist", "");
+pref("security.sandbox.content.syscall_whitelist", "");
#endif
#if defined(XP_MACOSX) || defined(XP_WIN)
diff --git a/browser/components/preferences/SiteDataManager.jsm b/browser/components/preferences/SiteDataManager.jsm
index a86aafef0168..6c8cf1e06f4e 100644
--- a/browser/components/preferences/SiteDataManager.jsm
+++ b/browser/components/preferences/SiteDataManager.jsm
@@ -8,6 +8,8 @@ Cu.import("resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OfflineAppCacheHelper",
"resource:///modules/offlineAppCache.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
+ "resource://gre/modules/ContextualIdentityService.jsm");
this.EXPORTED_SYMBOLS = [
"SiteDataManager"
@@ -29,6 +31,7 @@ this.SiteDataManager = {
// - quotaUsage: the usage of indexedDB and localStorage.
// - appCacheList: an array of app cache; instances of nsIApplicationCache
// - diskCacheList: an array. Each element is object holding metadata of http cache:
+ // - uri: the uri of that http cache
// - dataSize: that http cache size
// - idEnhance: the id extension of that http cache
_sites: new Map(),
@@ -53,7 +56,7 @@ this.SiteDataManager = {
status = Services.perms.testExactPermissionFromPrincipal(perm.principal, "persistent-storage");
if (status === Ci.nsIPermissionManager.ALLOW_ACTION ||
status === Ci.nsIPermissionManager.DENY_ACTION) {
- this._sites.set(perm.principal.origin, {
+ this._sites.set(perm.principal.URI.spec, {
perm,
status,
quotaUsage: 0,
@@ -125,6 +128,7 @@ this.SiteDataManager = {
for (let site of sites.values()) {
if (site.perm.matchesURI(uri, true)) {
site.diskCacheList.push({
+ uri,
dataSize,
idEnhance
});
@@ -161,25 +165,6 @@ this.SiteDataManager = {
});
},
- _removePermission(site) {
- Services.perms.removePermission(site.perm);
- },
-
- _removeQuotaUsage(site) {
- this._qms.clearStoragesForPrincipal(site.perm.principal, null, true);
- },
-
- removeAll() {
- for (let site of this._sites.values()) {
- this._removePermission(site);
- this._removeQuotaUsage(site);
- }
- Services.cache2.clear();
- Services.cookies.removeAll();
- OfflineAppCacheHelper.clear();
- this.updateSites();
- },
-
getSites() {
return Promise.all([this._updateQuotaPromise, this._updateDiskCachePromise])
.then(() => {
@@ -201,5 +186,70 @@ this.SiteDataManager = {
}
return list;
});
+ },
+
+ _removePermission(site) {
+ Services.perms.removePermission(site.perm);
+ },
+
+ _removeQuotaUsage(site) {
+ this._qms.clearStoragesForPrincipal(site.perm.principal, null, true);
+ },
+
+ _removeDiskCache(site) {
+ for (let cache of site.diskCacheList) {
+ this._diskCache.asyncDoomURI(cache.uri, cache.idEnhance, null);
+ }
+ },
+
+ _removeAppCache(site) {
+ for (let cache of site.appCacheList) {
+ cache.discard();
+ }
+ },
+
+ _removeCookie(site) {
+ let host = site.perm.principal.URI.host;
+ let e = Services.cookies.getCookiesFromHost(host, {});
+ while (e.hasMoreElements()) {
+ let cookie = e.getNext();
+ if (cookie instanceof Components.interfaces.nsICookie) {
+ if (this.isPrivateCookie(cookie)) {
+ continue;
+ }
+ Services.cookies.remove(
+ cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
+ }
+ }
+ },
+
+ remove(uris) {
+ for (let uri of uris) {
+ let site = this._sites.get(uri.spec);
+ if (site) {
+ this._removePermission(site);
+ this._removeQuotaUsage(site);
+ this._removeDiskCache(site);
+ this._removeAppCache(site);
+ this._removeCookie(site);
+ }
+ }
+ this.updateSites();
+ },
+
+ removeAll() {
+ for (let site of this._sites.values()) {
+ this._removePermission(site);
+ this._removeQuotaUsage(site);
+ }
+ Services.cache2.clear();
+ Services.cookies.removeAll();
+ OfflineAppCacheHelper.clear();
+ this.updateSites();
+ },
+
+ isPrivateCookie(cookie) {
+ let { userContextId } = cookie.originAttributes;
+ return userContextId && !ContextualIdentityService.getIdentityFromId(userContextId).public;
}
};
diff --git a/browser/components/preferences/cookies.js b/browser/components/preferences/cookies.js
index 778efaefc12b..29332dcb3d66 100644
--- a/browser/components/preferences/cookies.js
+++ b/browser/components/preferences/cookies.js
@@ -10,6 +10,8 @@ Components.utils.import("resource://gre/modules/PluralForm.jsm");
Components.utils.import("resource://gre/modules/Services.jsm")
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SiteDataManager",
+ "resource:///modules/SiteDataManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
"resource://gre/modules/ContextualIdentityService.jsm");
@@ -76,21 +78,12 @@ var gCookiesWindow = {
aCookieB.originAttributes);
},
- _isPrivateCookie(aCookie) {
- let { userContextId } = aCookie.originAttributes;
- if (!userContextId) {
- // Default identity is public.
- return false;
- }
- return !ContextualIdentityService.getIdentityFromId(userContextId).public;
- },
-
observe(aCookie, aTopic, aData) {
if (aTopic != "cookie-changed")
return;
if (aCookie instanceof Components.interfaces.nsICookie) {
- if (this._isPrivateCookie(aCookie)) {
+ if (SiteDataManager.isPrivateCookie(aCookie)) {
return;
}
@@ -484,7 +477,7 @@ var gCookiesWindow = {
while (e.hasMoreElements()) {
var cookie = e.getNext();
if (cookie && cookie instanceof Components.interfaces.nsICookie) {
- if (this._isPrivateCookie(cookie)) {
+ if (SiteDataManager.isPrivateCookie(cookie)) {
continue;
}
diff --git a/browser/components/preferences/in-content/sync.xul b/browser/components/preferences/in-content/sync.xul
old mode 100644
new mode 100755
diff --git a/browser/components/preferences/in-content/tests/browser_advanced_siteData.js b/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
index ea9e73186075..99e8743744b6 100644
--- a/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
+++ b/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
@@ -115,6 +115,12 @@ function addPersistentStoragePerm(origin) {
Services.perms.addFromPrincipal(principal, "persistent-storage", Ci.nsIPermissionManager.ALLOW_ACTION);
}
+function removePersistentStoragePerm(origin) {
+ let uri = NetUtil.newURI(origin);
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+ Services.perms.removeFromPrincipal(principal, "persistent-storage");
+}
+
function getPersistentStoragePermStatus(origin) {
let uri = NetUtil.newURI(origin);
let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
@@ -144,6 +150,33 @@ function getCacheUsage() {
});
}
+function openSettingsDialog() {
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let settingsBtn = doc.getElementById("siteDataSettings");
+ let dialogOverlay = doc.getElementById("dialogOverlay");
+ let dialogLoadPromise = promiseLoadSubDialog("chrome://browser/content/preferences/siteDataSettings.xul");
+ let dialogInitPromise = TestUtils.topicObserved("sitedata-settings-init", () => true);
+ let fullyLoadPromise = Promise.all([ dialogLoadPromise, dialogInitPromise ]).then(() => {
+ is(dialogOverlay.style.visibility, "visible", "The Settings dialog should be visible");
+ });
+ settingsBtn.doCommand();
+ return fullyLoadPromise;
+}
+
+function promiseSettingsDialogClose() {
+ return new Promise(resolve => {
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let dialogOverlay = doc.getElementById("dialogOverlay");
+ let win = content.gSubDialog._frame.contentWindow;
+ win.addEventListener("unload", function unload() {
+ if (win.document.documentURI === "chrome://browser/content/preferences/siteDataSettings.xul") {
+ isnot(dialogOverlay.style.visibility, "visible", "The Settings dialog should be hidden");
+ resolve();
+ }
+ }, { once: true });
+ });
+}
+
function promiseSitesUpdated() {
return TestUtils.topicObserved("sitedatamanager:sites-updated", () => true);
}
@@ -237,16 +270,9 @@ add_task(function* () {
let updatePromise = promiseSitesUpdated();
yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
yield updatePromise;
+ yield openSettingsDialog();
- // Open the siteDataSettings subdialog
let doc = gBrowser.selectedBrowser.contentDocument;
- let settingsBtn = doc.getElementById("siteDataSettings");
- let dialogOverlay = doc.getElementById("dialogOverlay");
- let dialogPromise = promiseLoadSubDialog("chrome://browser/content/preferences/siteDataSettings.xul");
- settingsBtn.doCommand();
- yield dialogPromise;
- is(dialogOverlay.style.visibility, "visible", "The dialog should be visible");
-
let dialogFrame = doc.getElementById("dialogFrame");
let frameDoc = dialogFrame.contentDocument;
let hostCol = frameDoc.getElementById("hostCol");
@@ -335,16 +361,9 @@ add_task(function* () {
let updatePromise = promiseSitesUpdated();
yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
yield updatePromise;
+ yield openSettingsDialog();
- // Open the siteDataSettings subdialog
let doc = gBrowser.selectedBrowser.contentDocument;
- let settingsBtn = doc.getElementById("siteDataSettings");
- let dialogOverlay = doc.getElementById("dialogOverlay");
- let dialogPromise = promiseLoadSubDialog("chrome://browser/content/preferences/siteDataSettings.xul");
- settingsBtn.doCommand();
- yield dialogPromise;
- is(dialogOverlay.style.visibility, "visible", "The dialog should be visible");
-
let frameDoc = doc.getElementById("dialogFrame").contentDocument;
let searchBox = frameDoc.getElementById("searchBox");
let mockOrigins = Array.from(mockSiteDataManager.sites.keys());
@@ -374,3 +393,202 @@ add_task(function* () {
});
}
});
+
+// Test selecting and removing all sites one by one
+add_task(function* () {
+ yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
+ let fakeOrigins = [
+ "https://news.foo.com/",
+ "https://mails.bar.com/",
+ "https://videos.xyz.com/",
+ "https://books.foo.com/",
+ "https://account.bar.com/",
+ "https://shopping.xyz.com/"
+ ];
+ fakeOrigins.forEach(origin => addPersistentStoragePerm(origin));
+
+ let updatePromise = promiseSitesUpdated();
+ yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
+ yield updatePromise;
+ yield openSettingsDialog();
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let frameDoc = null;
+ let saveBtn = null;
+ let cancelBtn = null;
+ let settingsDialogClosePromise = null;
+
+ // Test the initial state
+ assertAllSitesListed();
+
+ // Test the "Cancel" button
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ frameDoc = doc.getElementById("dialogFrame").contentDocument;
+ cancelBtn = frameDoc.getElementById("cancel");
+ removeAllSitesOneByOne();
+ assertAllSitesNotListed();
+ cancelBtn.doCommand();
+ yield settingsDialogClosePromise;
+ yield openSettingsDialog();
+ assertAllSitesListed();
+
+ // Test the "Save Changes" button but cancelling save
+ let cancelPromise = promiseAlertDialogOpen("cancel");
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ frameDoc = doc.getElementById("dialogFrame").contentDocument;
+ saveBtn = frameDoc.getElementById("save");
+ removeAllSitesOneByOne();
+ assertAllSitesNotListed();
+ saveBtn.doCommand();
+ yield cancelPromise;
+ yield settingsDialogClosePromise;
+ yield openSettingsDialog();
+ assertAllSitesListed();
+
+ // Test the "Save Changes" button and accepting save
+ let acceptPromise = promiseAlertDialogOpen("accept");
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ updatePromise = promiseSitesUpdated();
+ frameDoc = doc.getElementById("dialogFrame").contentDocument;
+ saveBtn = frameDoc.getElementById("save");
+ removeAllSitesOneByOne();
+ assertAllSitesNotListed();
+ saveBtn.doCommand();
+ yield acceptPromise;
+ yield settingsDialogClosePromise;
+ yield updatePromise;
+ yield openSettingsDialog();
+ assertAllSitesNotListed();
+
+ // Always clean up the fake origins
+ fakeOrigins.forEach(origin => removePersistentStoragePerm(origin));
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ function removeAllSitesOneByOne() {
+ frameDoc = doc.getElementById("dialogFrame").contentDocument;
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ let sitesList = frameDoc.getElementById("sitesList");
+ let sites = sitesList.getElementsByTagName("richlistitem");
+ for (let i = sites.length - 1; i >= 0; --i) {
+ sites[i].click();
+ removeBtn.doCommand();
+ }
+ }
+
+ function assertAllSitesListed() {
+ frameDoc = doc.getElementById("dialogFrame").contentDocument;
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ let sitesList = frameDoc.getElementById("sitesList");
+ let sites = sitesList.getElementsByTagName("richlistitem");
+ is(sites.length, fakeOrigins.length, "Should list all sites");
+ is(removeBtn.disabled, false, "Should enable the removeSelected button");
+ }
+
+ function assertAllSitesNotListed() {
+ frameDoc = doc.getElementById("dialogFrame").contentDocument;
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ let sitesList = frameDoc.getElementById("sitesList");
+ let sites = sitesList.getElementsByTagName("richlistitem");
+ is(sites.length, 0, "Should not list all sites");
+ is(removeBtn.disabled, true, "Should disable the removeSelected button");
+ }
+});
+
+// Test selecting and removing partial sites
+add_task(function* () {
+ yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
+ let fakeOrigins = [
+ "https://news.foo.com/",
+ "https://mails.bar.com/",
+ "https://videos.xyz.com/",
+ "https://books.foo.com/",
+ "https://account.bar.com/",
+ "https://shopping.xyz.com/"
+ ];
+ fakeOrigins.forEach(origin => addPersistentStoragePerm(origin));
+
+ let updatePromise = promiseSitesUpdated();
+ yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
+ yield updatePromise;
+ yield openSettingsDialog();
+
+ const removeDialogURL = "chrome://browser/content/preferences/siteDataRemoveSelected.xul";
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let frameDoc = null;
+ let saveBtn = null;
+ let cancelBtn = null;
+ let removeDialogOpenPromise = null;
+ let settingsDialogClosePromise = null;
+
+ // Test the initial state
+ assertSitesListed(fakeOrigins);
+
+ // Test the "Cancel" button
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ frameDoc = doc.getElementById("dialogFrame").contentDocument;
+ cancelBtn = frameDoc.getElementById("cancel");
+ removeSelectedSite(fakeOrigins.slice(0, 4));
+ assertSitesListed(fakeOrigins.slice(4));
+ cancelBtn.doCommand();
+ yield settingsDialogClosePromise;
+ yield openSettingsDialog();
+ assertSitesListed(fakeOrigins);
+
+ // Test the "Save Changes" button but canceling save
+ removeDialogOpenPromise = promiseWindowDialogOpen("cancel", removeDialogURL);
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ frameDoc = doc.getElementById("dialogFrame").contentDocument;
+ saveBtn = frameDoc.getElementById("save");
+ removeSelectedSite(fakeOrigins.slice(0, 4));
+ assertSitesListed(fakeOrigins.slice(4));
+ saveBtn.doCommand();
+ yield removeDialogOpenPromise;
+ yield settingsDialogClosePromise;
+ yield openSettingsDialog();
+ assertSitesListed(fakeOrigins);
+
+ // Test the "Save Changes" button and accepting save
+ removeDialogOpenPromise = promiseWindowDialogOpen("accept", removeDialogURL);
+ settingsDialogClosePromise = promiseSettingsDialogClose();
+ frameDoc = doc.getElementById("dialogFrame").contentDocument;
+ saveBtn = frameDoc.getElementById("save");
+ removeSelectedSite(fakeOrigins.slice(0, 4));
+ assertSitesListed(fakeOrigins.slice(4));
+ saveBtn.doCommand();
+ yield removeDialogOpenPromise;
+ yield settingsDialogClosePromise;
+ yield openSettingsDialog();
+ assertSitesListed(fakeOrigins.slice(4));
+
+ // Always clean up the fake origins
+ fakeOrigins.forEach(origin => removePersistentStoragePerm(origin));
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ function removeSelectedSite(origins) {
+ frameDoc = doc.getElementById("dialogFrame").contentDocument;
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ let sitesList = frameDoc.getElementById("sitesList");
+ origins.forEach(origin => {
+ let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
+ if (site) {
+ site.click();
+ removeBtn.doCommand();
+ } else {
+ ok(false, `Should not select and remove inexisted site of ${origin}`);
+ }
+ });
+ }
+
+ function assertSitesListed(origins) {
+ frameDoc = doc.getElementById("dialogFrame").contentDocument;
+ let removeBtn = frameDoc.getElementById("removeSelected");
+ let sitesList = frameDoc.getElementById("sitesList");
+ let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
+ is(totalSitesNumber, origins.length, "Should list the right sites number");
+ origins.forEach(origin => {
+ let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
+ ok(!!site, `Should list the site of ${origin}`);
+ });
+ is(removeBtn.disabled, false, "Should enable the removeSelected button");
+ }
+});
diff --git a/browser/components/preferences/in-content/tests/head.js b/browser/components/preferences/in-content/tests/head.js
index 06e7fd1cfc53..e1bcd164b439 100644
--- a/browser/components/preferences/in-content/tests/head.js
+++ b/browser/components/preferences/in-content/tests/head.js
@@ -161,12 +161,12 @@ function waitForCondition(aConditionFn, aMaxTries = 50, aCheckInterval = 100) {
});
}
-function promiseAlertDialogOpen(buttonAction) {
+function promiseWindowDialogOpen(buttonAction, url) {
return new Promise(resolve => {
Services.ww.registerNotification(function onOpen(subj, topic, data) {
if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
- subj.addEventListener("load", function() {
- if (subj.document.documentURI == "chrome://global/content/commonDialog.xul") {
+ subj.addEventListener("load", function onLoad() {
+ if (subj.document.documentURI == url) {
Services.ww.unregisterNotification(onOpen);
let doc = subj.document.documentElement;
doc.getButton(buttonAction).click();
@@ -177,3 +177,7 @@ function promiseAlertDialogOpen(buttonAction) {
});
});
}
+
+function promiseAlertDialogOpen(buttonAction) {
+ return promiseWindowDialogOpen(buttonAction, "chrome://global/content/commonDialog.xul");
+}
diff --git a/browser/components/preferences/jar.mn b/browser/components/preferences/jar.mn
index fdf5bd0880b1..4436d05eb2f1 100644
--- a/browser/components/preferences/jar.mn
+++ b/browser/components/preferences/jar.mn
@@ -30,6 +30,8 @@ browser.jar:
content/browser/preferences/siteDataSettings.xul
content/browser/preferences/siteDataSettings.js
content/browser/preferences/siteDataSettings.css
+* content/browser/preferences/siteDataRemoveSelected.xul
+ content/browser/preferences/siteDataRemoveSelected.js
content/browser/preferences/siteListItem.xml
content/browser/preferences/translation.xul
content/browser/preferences/translation.js
diff --git a/browser/components/preferences/moz.build b/browser/components/preferences/moz.build
index c45daebe1e52..49efefa37306 100644
--- a/browser/components/preferences/moz.build
+++ b/browser/components/preferences/moz.build
@@ -19,7 +19,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
JAR_MANIFESTS += ['jar.mn']
EXTRA_JS_MODULES += [
- 'SiteDataManager.jsm',
+ 'SiteDataManager.jsm'
]
with Files('**'):
diff --git a/browser/components/preferences/siteDataRemoveSelected.js b/browser/components/preferences/siteDataRemoveSelected.js
new file mode 100644
index 000000000000..8aac90b8c324
--- /dev/null
+++ b/browser/components/preferences/siteDataRemoveSelected.js
@@ -0,0 +1,197 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+const { utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+"use strict";
+
+let gSiteDataRemoveSelected = {
+
+ _tree: null,
+
+ init() {
+ // Organize items for the tree from the argument
+ let hostsTable = window.arguments[0].hostsTable;
+ let visibleItems = [];
+ let itemsTable = new Map();
+ for (let [ baseDomain, hosts ] of hostsTable) {
+ // In the beginning, only display base domains in the topmost level.
+ visibleItems.push({
+ level: 0,
+ opened: false,
+ host: baseDomain
+ });
+ // Other hosts are in the second level.
+ let items = hosts.map(host => {
+ return { host, level: 1 };
+ });
+ items.sort(sortByHost);
+ itemsTable.set(baseDomain, items);
+ }
+ visibleItems.sort(sortByHost);
+ this._view.itemsTable = itemsTable;
+ this._view.visibleItems = visibleItems;
+ this._tree = document.getElementById("sitesTree");
+ this._tree.view = this._view;
+
+ function sortByHost(a, b) {
+ let aHost = a.host.toLowerCase();
+ let bHost = b.host.toLowerCase();
+ return aHost.localeCompare(bHost);
+ }
+ },
+
+ ondialogaccept() {
+ window.arguments[0].allowed = true;
+ },
+
+ ondialogcancel() {
+ window.arguments[0].allowed = false;
+ },
+
+ _view: {
+ _selection: null,
+
+ itemsTable: null,
+
+ visibleItems: null,
+
+ get rowCount() {
+ return this.visibleItems.length;
+ },
+
+ getCellText(index, column) {
+ let item = this.visibleItems[index];
+ return item ? item.host : "";
+ },
+
+ isContainer(index) {
+ let item = this.visibleItems[index];
+ if (item && item.level === 0) {
+ return true;
+ }
+ return false;
+ },
+
+ isContainerEmpty() {
+ return false;
+ },
+
+ isContainerOpen(index) {
+ let item = this.visibleItems[index];
+ if (item && item.level === 0) {
+ return item.opened;
+ }
+ return false;
+ },
+
+ getLevel(index) {
+ let item = this.visibleItems[index];
+ return item ? item.level : 0;
+ },
+
+ hasNextSibling(index, afterIndex) {
+ let item = this.visibleItems[index];
+ if (item) {
+ let thisLV = this.getLevel(index);
+ for (let i = afterIndex + 1; i < this.rowCount; ++i) {
+ let nextLV = this.getLevel(i);
+ if (nextLV == thisLV) {
+ return true;
+ }
+ if (nextLV < thisLV) {
+ break;
+ }
+ }
+ }
+ return false;
+ },
+
+ getParentIndex(index) {
+ if (!this.isContainer(index)) {
+ for (let i = index - 1; i >= 0; --i) {
+ if (this.isContainer(i)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ },
+
+ toggleOpenState(index) {
+ let item = this.visibleItems[index];
+ if (!this.isContainer(index)) {
+ return;
+ }
+
+ if (item.opened) {
+ item.opened = false;
+
+ let deleteCount = 0;
+ for (let i = index + 1; i < this.visibleItems.length; ++i) {
+ if (!this.isContainer(i)) {
+ ++deleteCount;
+ } else {
+ break;
+ }
+ }
+
+ if (deleteCount) {
+ this.visibleItems.splice(index + 1, deleteCount);
+ this.treeBox.rowCountChanged(index + 1, -deleteCount);
+ }
+ } else {
+ item.opened = true;
+
+ let childItems = this.itemsTable.get(item.host);
+ for (let i = 0; i < childItems.length; ++i) {
+ this.visibleItems.splice(index + i + 1, 0, childItems[i]);
+ }
+ this.treeBox.rowCountChanged(index + 1, childItems.length);
+ }
+ this.treeBox.invalidateRow(index);
+ },
+
+ get selection() {
+ return this._selection;
+ },
+ set selection(v) {
+ this._selection = v;
+ return v;
+ },
+ setTree(treeBox) {
+ this.treeBox = treeBox;
+ },
+ isSeparator(index) {
+ return false;
+ },
+ isSorted(index) {
+ return false;
+ },
+ canDrop() {
+ return false;
+ },
+ drop() {},
+ getRowProperties() {},
+ getCellProperties() {},
+ getColumnProperties() {},
+ hasPreviousSibling(index) {},
+ getImageSrc() {},
+ getProgressMode() {},
+ getCellValue() {},
+ cycleHeader() {},
+ selectionChanged() {},
+ cycleCell() {},
+ isEditable() {},
+ isSelectable() {},
+ setCellValue() {},
+ setCellText() {},
+ performAction() {},
+ performActionOnRow() {},
+ performActionOnCell() {}
+ }
+};
diff --git a/browser/components/preferences/siteDataRemoveSelected.xul b/browser/components/preferences/siteDataRemoveSelected.xul
new file mode 100644
index 000000000000..a3996560b15e
--- /dev/null
+++ b/browser/components/preferences/siteDataRemoveSelected.xul
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/components/preferences/siteDataSettings.css b/browser/components/preferences/siteDataSettings.css
index 7966d0a8f319..fd69ff3b8ca5 100644
--- a/browser/components/preferences/siteDataSettings.css
+++ b/browser/components/preferences/siteDataSettings.css
@@ -2,18 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-#searchBoxContainer {
- -moz-box-align: center;
-}
-
-#sitesList {
- min-height: 20em;
-}
-
#sitesList > richlistitem {
-moz-binding: url("chrome://browser/content/preferences/siteListItem.xml#siteListItem");
}
-.item-box {
- padding: 5px 8px;
+#SiteDataRemoveSelectedDialog {
+ -moz-binding: url("chrome://global/content/bindings/dialog.xml#dialog");
}
diff --git a/browser/components/preferences/siteDataSettings.js b/browser/components/preferences/siteDataSettings.js
index 40f15f4fc18d..084d35bf49b2 100644
--- a/browser/components/preferences/siteDataSettings.js
+++ b/browser/components/preferences/siteDataSettings.js
@@ -20,6 +20,8 @@ let gSiteDataSettings = {
// - uri: uri of site; instance of nsIURI
// - status: persistent-storage permission status
// - usage: disk usage which site uses
+ // - userAction: "remove" or "update-permission"; the action user wants to take.
+ // If not specified, means no action to take
_sites: null,
_list: null,
@@ -38,12 +40,23 @@ let gSiteDataSettings = {
let sortCol = document.getElementById("hostCol");
this._sortSites(this._sites, sortCol);
this._buildSitesList(this._sites);
+ this._updateButtonsState();
+ Services.obs.notifyObservers(null, "sitedata-settings-init", null);
});
setEventListener("hostCol", "click", this.onClickTreeCol);
setEventListener("usageCol", "click", this.onClickTreeCol);
setEventListener("statusCol", "click", this.onClickTreeCol);
setEventListener("searchBox", "command", this.onCommandSearch);
+ setEventListener("cancel", "command", this.close);
+ setEventListener("save", "command", this.saveChanges);
+ setEventListener("removeSelected", "command", this.removeSelected);
+ },
+
+ _updateButtonsState() {
+ let items = this._list.getElementsByTagName("richlistitem");
+ let removeBtn = document.getElementById("removeSelected");
+ removeBtn.disabled = !(items.length > 0);
},
/**
@@ -110,6 +123,10 @@ let gSiteDataSettings = {
continue;
}
+ if (data.userAction === "remove") {
+ continue;
+ }
+
let statusStrId = data.status === Ci.nsIPermissionManager.ALLOW_ACTION ? "important" : "default";
let size = DownloadUtils.convertByteUnits(data.usage);
let item = document.createElement("richlistitem");
@@ -128,5 +145,95 @@ let gSiteDataSettings = {
onCommandSearch() {
this._buildSitesList(this._sites);
+ },
+
+ removeSelected() {
+ let selected = this._list.selectedItem;
+ if (selected) {
+ let origin = selected.getAttribute("data-origin");
+ for (let site of this._sites) {
+ if (site.uri.spec === origin) {
+ site.userAction = "remove";
+ break;
+ }
+ }
+ this._list.removeChild(selected);
+ this._updateButtonsState();
+ }
+ },
+
+ saveChanges() {
+ let allowed = true;
+
+ // Confirm user really wants to remove site data starts
+ let removals = [];
+ this._sites = this._sites.filter(site => {
+ if (site.userAction === "remove") {
+ removals.push(site.uri);
+ return false;
+ }
+ return true;
+ });
+
+ if (removals.length > 0) {
+ if (this._sites.length == 0) {
+ // User selects all sites so equivalent to clearing all data
+ let flags =
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1 +
+ Services.prompt.BUTTON_POS_0_DEFAULT;
+ let prefStrBundle = document.getElementById("bundlePreferences");
+ let title = prefStrBundle.getString("clearSiteDataPromptTitle");
+ let text = prefStrBundle.getString("clearSiteDataPromptText");
+ let btn0Label = prefStrBundle.getString("clearSiteDataNow");
+ let result = Services.prompt.confirmEx(window, title, text, flags, btn0Label, null, null, null, {});
+ allowed = result == 0;
+ if (allowed) {
+ SiteDataManager.removeAll();
+ }
+ } else {
+ // User only removes partial sites.
+ // We will remove cookies based on base domain, say, user selects "news.foo.com" to remove.
+ // The cookies under "music.foo.com" will be removed together.
+ // We have to prmopt user about this action.
+ let hostsTable = new Map();
+ // Group removed sites by base domain
+ for (let uri of removals) {
+ let baseDomain = Services.eTLD.getBaseDomain(uri);
+ let hosts = hostsTable.get(baseDomain);
+ if (!hosts) {
+ hosts = [];
+ hostsTable.set(baseDomain, hosts);
+ }
+ hosts.push(uri.host);
+ }
+ // Pick out sites with the same base domain as removed sites
+ for (let site of this._sites) {
+ let baseDomain = Services.eTLD.getBaseDomain(site.uri);
+ let hosts = hostsTable.get(baseDomain);
+ if (hosts) {
+ hosts.push(site.uri.host);
+ }
+ }
+
+ let args = {
+ hostsTable,
+ allowed: false
+ };
+ let features = "centerscreen,chrome,modal,resizable=no";
+ window.openDialog("chrome://browser/content/preferences/siteDataRemoveSelected.xul", "", features, args);
+ allowed = args.allowed;
+ if (allowed) {
+ SiteDataManager.remove(removals);
+ }
+ }
+ }
+ // Confirm user really wants to remove site data ends
+
+ this.close();
+ },
+
+ close() {
+ window.close();
}
};
diff --git a/browser/components/preferences/siteDataSettings.xul b/browser/components/preferences/siteDataSettings.xul
index 3afc450c51dc..6dd4904c7bbc 100644
--- a/browser/components/preferences/siteDataSettings.xul
+++ b/browser/components/preferences/siteDataSettings.xul
@@ -7,6 +7,7 @@
+
@@ -41,4 +42,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/extensions/webcompat-reporter/locales/en-US/webcompat.properties b/browser/extensions/webcompat-reporter/locales/en-US/webcompat.properties
index 412e6389a721..2ca2c3233fc2 100644
--- a/browser/extensions/webcompat-reporter/locales/en-US/webcompat.properties
+++ b/browser/extensions/webcompat-reporter/locales/en-US/webcompat.properties
@@ -4,7 +4,8 @@
# LOCALIZATION NOTE(wc-reporter.label): This string will be used in the
# Firefox menu panel below its button. Localized length should be considered.
-wc-reporter.label=Report Site Issue
+# \u00ad is included at the beginning of the string to disable auto-hyphens.
+wc-reporter.label=\u00adReport Site Issue
# LOCALIZATION NOTE(wc-reporter.tooltip): A site compatibility issue is
# a website bug that exists in one browser (Firefox), but not another.
wc-reporter.tooltip=Report a site compatibility issue
diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties
index 3d839f8a0964..1abb642f44e9 100644
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -125,12 +125,11 @@ webextPerms.hostDescription.tooManySites=Access your data on #1 other site;Acces
# %2$S is replaced with the localized name of the application.
addonPostInstall.message1=%1$S has been added to %2$S.
-# LOCALIZATION NOTE (addonPostInstall.message2)
-# %1$S is replaced with the localized name of the extension.
-# %2$S is replaced with the icon for the add-ons menu.
-# %3$S is replaced with the icon for the toolbar menu.
+# LOCALIZATION NOTE (addonPostInstall.messageDetail)
+# %1$S is replaced with the icon for the add-ons menu.
+# %2$S is replaced with the icon for the toolbar menu.
# Note, this string will be used as raw markup. Avoid characters like <, >, &
-addonPostInstall.message2=Manage %1$S by clicking %2$S in the %3$S menu.
+addonPostInstall.messageDetail=Manage your add-ons by clicking %1$S in the %2$S menu.
addonPostInstall.okay.label=OK
addonPostInstall.okay.key=O
diff --git a/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd b/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
index 919aa1b6519a..0fa896259aa2 100644
--- a/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
@@ -9,3 +9,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/browser/modules/ExtensionsUI.jsm b/browser/modules/ExtensionsUI.jsm
index 46368c83efae..e2127dd2c254 100644
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -331,8 +331,8 @@ this.ExtensionsUI = {
let bundle = win.gNavigatorBundle;
let msg1 = bundle.getFormattedString("addonPostInstall.message1",
[addonLabel, appName]);
- let msg2 = bundle.getFormattedString("addonPostInstall.message2",
- [addonLabel, addonIcon, toolbarIcon]);
+ let msg2 = bundle.getFormattedString("addonPostInstall.messageDetail",
+ [addonIcon, toolbarIcon]);
return new Promise(resolve => {
let action = {
diff --git a/browser/themes/shared/fxa/sync-illustration.svg b/browser/themes/shared/fxa/sync-illustration.svg
old mode 100644
new mode 100755
diff --git a/browser/themes/shared/incontentprefs/siteDataSettings.css b/browser/themes/shared/incontentprefs/siteDataSettings.css
new file mode 100644
index 000000000000..d5adbc628315
--- /dev/null
+++ b/browser/themes/shared/incontentprefs/siteDataSettings.css
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Site Data - Settings dialog
+ */
+#sitesList {
+ min-height: 20em;
+}
+
+.item-box {
+ padding: 5px 8px;
+}
+
+/**
+ * Confirmation dialog of removing sites selected
+ */
+#SiteDataRemoveSelectedDialog {
+ padding: 16px;
+}
+
+#contentContainer {
+ font-size: 1.2em;
+ margin-bottom: 10px;
+}
+
+.question-icon {
+ margin: 6px;
+}
+
+#removing-label {
+ font-weight: bold;
+}
+
+#sitesTree {
+ height: 15em;
+}
diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn
index 095914a72538..1bf78e17ab6b 100644
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -75,6 +75,7 @@
skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
skin/classic/browser/preferences/in-content/icons.svg (../shared/incontentprefs/icons.svg)
skin/classic/browser/preferences/in-content/search.css (../shared/incontentprefs/search.css)
+ skin/classic/browser/preferences/in-content/siteDataSettings.css (../shared/incontentprefs/siteDataSettings.css)
* skin/classic/browser/preferences/in-content/containers.css (../shared/incontentprefs/containers.css)
* skin/classic/browser/preferences/containers.css (../shared/preferences/containers.css)
skin/classic/browser/fxa/default-avatar.svg (../shared/fxa/default-avatar.svg)
diff --git a/devtools/client/definitions.js b/devtools/client/definitions.js
index d8135508dbf1..7846e11f6645 100644
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -24,6 +24,11 @@ loader.lazyGetter(this, "StoragePanel", () => require("devtools/client/storage/p
loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/client/scratchpad/scratchpad-panel").ScratchpadPanel);
loader.lazyGetter(this, "DomPanel", () => require("devtools/client/dom/dom-panel").DomPanel);
+// Other dependencies
+loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
+loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
+loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
+
const {LocalizationHelper} = require("devtools/shared/l10n");
const L10N = new LocalizationHelper("devtools/client/locales/startup.properties");
@@ -470,26 +475,93 @@ exports.defaultThemes = [
// addons that have manually inserted toolbarbuttons into DOM.
// (By default, supported target is only local tab)
exports.ToolboxButtons = [
- { id: "command-button-pick",
- isTargetSupported: target => {
- return target.activeTab && target.activeTab.traits.frames;
- }
- },
- { id: "command-button-frames",
- isTargetSupported: target => {
- return target.activeTab && target.activeTab.traits.frames;
- }
- },
{ id: "command-button-splitconsole",
- isTargetSupported: target => !target.isAddon },
- { id: "command-button-responsive" },
- { id: "command-button-paintflashing" },
- { id: "command-button-scratchpad" },
- { id: "command-button-screenshot" },
- { id: "command-button-rulers" },
- { id: "command-button-measure" },
- { id: "command-button-noautohide",
- isTargetSupported: target => target.chrome },
+ description: l10n("toolbox.buttons.splitconsole"),
+ isTargetSupported: target => !target.isAddon,
+ onClick(event, toolbox) {
+ toolbox.toggleSplitConsole();
+ },
+ isChecked(toolbox) {
+ return toolbox.splitConsole;
+ },
+ setup(toolbox, onChange) {
+ toolbox.on("split-console", onChange);
+ },
+ teardown(toolbox, onChange) {
+ toolbox.off("split-console", onChange);
+ }
+ },
+ { id: "command-button-paintflashing",
+ description: l10n("toolbox.buttons.paintflashing"),
+ isTargetSupported: target => target.isLocalTab,
+ onClick(event, toolbox) {
+ CommandUtils.executeOnTarget(toolbox.target, "paintflashing toggle");
+ },
+ autoToggle: true
+ },
+ { id: "command-button-scratchpad",
+ description: l10n("toolbox.buttons.scratchpad"),
+ isTargetSupported: target => target.isLocalTab,
+ onClick(event, toolbox) {
+ ScratchpadManager.openScratchpad();
+ }
+ },
+ { id: "command-button-responsive",
+ description: l10n("toolbox.buttons.responsive",
+ osString == "Darwin" ? "Cmd+Opt+M" : "Ctrl+Shift+M"),
+ isTargetSupported: target => target.isLocalTab,
+ onClick(event, toolbox) {
+ let browserWindow = toolbox.win.top;
+ ResponsiveUIManager.handleGcliCommand(browserWindow,
+ browserWindow.gBrowser.selectedTab,
+ "resize toggle",
+ null);
+ },
+ isChecked(toolbox) {
+ if (!toolbox.target.tab) {
+ return false;
+ }
+ return ResponsiveUIManager.isActiveForTab(toolbox.target.tab);
+ },
+ setup(toolbox, onChange) {
+ ResponsiveUIManager.on("on", onChange);
+ ResponsiveUIManager.on("off", onChange);
+ },
+ teardown(toolbox, onChange) {
+ ResponsiveUIManager.off("on", onChange);
+ ResponsiveUIManager.off("off", onChange);
+ }
+ },
+ { id: "command-button-screenshot",
+ description: l10n("toolbox.buttons.screenshot"),
+ isTargetSupported: target => target.isLocalTab,
+ onClick(event, toolbox) {
+ // Special case for screenshot button to check for clipboard preference
+ const clipboardEnabled = Services.prefs
+ .getBoolPref("devtools.screenshot.clipboard.enabled");
+ let args = "--fullpage --file";
+ if (clipboardEnabled) {
+ args += " --clipboard";
+ }
+ CommandUtils.executeOnTarget(toolbox.target, "screenshot " + args);
+ }
+ },
+ { id: "command-button-rulers",
+ description: l10n("toolbox.buttons.rulers"),
+ isTargetSupported: target => target.isLocalTab,
+ onClick(event, toolbox) {
+ CommandUtils.executeOnTarget(toolbox.target, "rulers");
+ },
+ autoToggle: true
+ },
+ { id: "command-button-measure",
+ description: l10n("toolbox.buttons.measure"),
+ isTargetSupported: target => target.isLocalTab,
+ onClick(event, toolbox) {
+ CommandUtils.executeOnTarget(toolbox.target, "measure");
+ },
+ autoToggle: true
+ },
];
/**
diff --git a/devtools/client/framework/toolbox.js b/devtools/client/framework/toolbox.js
index 2ac4e6eb261e..e5ee01b75a21 100644
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -558,23 +558,54 @@ Toolbox.prototype = {
* @property {String} description - The value that will display as a tooltip and in
* the options panel for enabling/disabling.
* @property {Function} onClick - The function to run when the button is activated by
- * click or keyboard shortcut.
+ * click or keyboard shortcut. First argument will be the 'click'
+ * event, and second argument is the toolbox instance.
* @property {Boolean} isInStartContainer - Buttons can either be placed at the start
* of the toolbar, or at the end.
+ * @property {Function} setup - Function run immediately to listen for events changing
+ * whenever the button is checked or unchecked. The toolbox object
+ * is passed as first argument and a callback is passed as second
+ * argument, to be called whenever the checked state changes.
+ * @property {Function} teardown - Function run on toolbox close to let a chance to
+ * unregister listeners set when `setup` was called and avoid
+ * memory leaks. The same arguments than `setup` function are
+ * passed to `teardown`.
+ * @property {Function} isTargetSupported - Function to automatically enable/disable
+ * the button based on the target. If the target don't support
+ * the button feature, this method should return false.
+ * @property {Function} isChecked - Optional function called to known if the button
+ * is toggled or not. The function should return true when
+ * the button should be displayed as toggled on.
+ * @property {Boolean} autoToggle - If true, the checked state is going to be
+ * automatically toggled on click.
*/
_createButtonState: function (options) {
- let isChecked = false;
- const {id, className, description, onClick, isInStartContainer} = options;
+ let isCheckedValue = false;
+ const { id, className, description, onClick, isInStartContainer, setup, teardown,
+ isTargetSupported, isChecked, autoToggle } = options;
+ const toolbox = this;
const button = {
id,
className,
description,
- onClick,
+ onClick(event) {
+ if (typeof onClick == "function") {
+ onClick(event, toolbox);
+ }
+ if (autoToggle) {
+ button.isChecked = !button.isChecked;
+ }
+ },
+ isTargetSupported,
get isChecked() {
- return isChecked;
+ if (typeof isChecked == "function") {
+ return isChecked(toolbox);
+ }
+ return isCheckedValue;
},
set isChecked(value) {
- isChecked = value;
+ // Note that if options.isChecked is given, this is ignored
+ isCheckedValue = value;
this.emit("updatechecked");
},
// The preference for having this button visible.
@@ -583,6 +614,17 @@ Toolbox.prototype = {
// holding buttons. By default the buttons are placed in the end container.
isInStartContainer: !!isInStartContainer
};
+ if (typeof setup == "function") {
+ let onChange = () => {
+ button.emit("updatechecked");
+ };
+ setup(this, onChange);
+ // Save a reference to the cleanup method that will unregister the onChange
+ // callback. Immediately bind the function argument so that we don't have to
+ // also save a reference to them.
+ button.teardown = teardown.bind(options, this, onChange);
+ }
+ button.isVisible = this._commandIsVisible(button);
EventEmitter.decorate(button);
@@ -1030,7 +1072,7 @@ Toolbox.prototype = {
},
/**
- * Add buttons to the UI as specified in the devtools.toolbox.toolbarSpec pref
+ * Add buttons to the UI as specified in devtools/client/definitions.js
*/
_buildButtons: Task.async(function* () {
// Beyond the normal preference filtering
@@ -1040,27 +1082,9 @@ Toolbox.prototype = {
yield this._buildNoAutoHideButton()
];
- // Build buttons from the GCLI commands only if the GCLI actor is supported and the
- // target isn't chrome.
- if (this.target.hasActor("gcli") && !this.target.chrome) {
- const options = {
- environment: CommandUtils.createEnvironment(this, "_target")
- };
-
- this._requisition = yield CommandUtils.createRequisition(this.target, options);
- const spec = this.getToolbarSpec();
- const commandButtons = yield CommandUtils.createCommandButtons(
- spec, this.target, this.doc, this._requisition, this._createButtonState);
- this.toolbarButtons = [...this.toolbarButtons, ...commandButtons];
- }
-
- // Mutate the objects here with their visibility.
- this.toolbarButtons.forEach(command => {
- const definition = ToolboxButtons.find(t => t.id === command.id);
- command.isTargetSupported = definition.isTargetSupported
- ? definition.isTargetSupported
- : target => target.isLocalTab;
- command.isVisible = this._commandIsVisible(command.id);
+ ToolboxButtons.forEach(definition => {
+ let button = this._createButtonState(definition);
+ this.toolbarButtons.push(button);
});
this.component.setToolboxButtons(this.toolbarButtons);
@@ -1073,7 +1097,10 @@ Toolbox.prototype = {
this.frameButton = this._createButtonState({
id: "command-button-frames",
description: L10N.getStr("toolbox.frames.tooltip"),
- onClick: this.showFramesMenu
+ onClick: this.showFramesMenu,
+ isTargetSupported: target => {
+ return target.activeTab && target.activeTab.traits.frames;
+ }
});
return this.frameButton;
@@ -1087,7 +1114,8 @@ Toolbox.prototype = {
this.autohideButton = this._createButtonState({
id: "command-button-noautohide",
description: L10N.getStr("toolbox.noautohide.tooltip"),
- onClick: this._toggleNoAutohide
+ onClick: this._toggleNoAutohide,
+ isTargetSupported: target => target.chrome
});
this._isDisableAutohideEnabled().then(enabled => {
@@ -1138,7 +1166,10 @@ Toolbox.prototype = {
id: "command-button-pick",
description: L10N.getStr("pickButton.tooltip"),
onClick: this._onPickerClick,
- isInStartContainer: true
+ isInStartContainer: true,
+ isTargetSupported: target => {
+ return target.activeTab && target.activeTab.traits.frames;
+ }
});
return this.pickerButton;
@@ -1178,16 +1209,7 @@ Toolbox.prototype = {
*/
getToolbarSpec: function () {
let spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
- // Special case for screenshot command button to check for clipboard preference
- const clipboardEnabled = Services.prefs
- .getBoolPref("devtools.screenshot.clipboard.enabled");
- if (clipboardEnabled) {
- for (let i = 0; i < spec.length; i++) {
- if (spec[i] == "screenshot --fullpage --file") {
- spec[i] += " --clipboard";
- }
- }
- }
+
return spec;
},
@@ -1199,8 +1221,8 @@ Toolbox.prototype = {
* Update the visibility of the buttons.
*/
updateToolboxButtonsVisibility() {
- this.toolbarButtons.forEach(command => {
- command.isVisible = this._commandIsVisible(command.id);
+ this.toolbarButtons.forEach(button => {
+ button.isVisible = this._commandIsVisible(button);
});
this.component.setToolboxButtons(this.toolbarButtons);
},
@@ -1208,11 +1230,11 @@ Toolbox.prototype = {
/**
* Ensure the visibility of each toolbox button matches the preference value.
*/
- _commandIsVisible: function (id) {
+ _commandIsVisible: function (button) {
const {
isTargetSupported,
visibilityswitch
- } = this.toolbarButtons.find(btn => btn.id === id);
+ } = button;
let visible = true;
try {
@@ -2318,12 +2340,17 @@ Toolbox.prototype = {
detachThread(this._threadClient);
this._threadClient = null;
+ // Unregister buttons listeners
+ this.toolbarButtons.forEach(button => {
+ if (typeof button.teardown == "function") {
+ // teardown arguments have already been bound in _createButtonState
+ button.teardown();
+ }
+ });
+
// We need to grab a reference to win before this._host is destroyed.
let win = this.win;
- if (this._requisition) {
- CommandUtils.destroyRequisition(this._requisition, this.target);
- }
this._telemetry.toolClosed("toolbox");
this._telemetry.destroy();
diff --git a/devtools/client/inspector/inspector.js b/devtools/client/inspector/inspector.js
index 2ed785a3ac52..44a8190f78c5 100644
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1755,14 +1755,11 @@ Inspector.prototype = {
const command = Services.prefs.getBoolPref("devtools.screenshot.clipboard.enabled") ?
"screenshot --file --clipboard --selector" :
"screenshot --file --selector";
- CommandUtils.createRequisition(this._target, {
- environment: CommandUtils.createEnvironment(this, "_target")
- }).then(requisition => {
- // Bug 1180314 - CssSelector might contain white space so need to make sure it is
- // passed to screenshot as a single parameter. More work *might* be needed if
- // CssSelector could contain escaped single- or double-quotes, backslashes, etc.
- requisition.updateExec(`${command} '${this.selectionCssSelector}'`);
- });
+ // Bug 1180314 - CssSelector might contain white space so need to make sure it is
+ // passed to screenshot as a single parameter. More work *might* be needed if
+ // CssSelector could contain escaped single- or double-quotes, backslashes, etc.
+ CommandUtils.executeOnTarget(this._target,
+ `${command} '${this.selectionCssSelector}'`);
},
/**
diff --git a/devtools/client/locales/en-US/startup.properties b/devtools/client/locales/en-US/startup.properties
index 4486ac98abf0..5d7bfe765c9e 100644
--- a/devtools/client/locales/en-US/startup.properties
+++ b/devtools/client/locales/en-US/startup.properties
@@ -260,3 +260,40 @@ dom.accesskey=D
# displayed inside the developer tools window.
# Keyboard shortcut for DOM panel will be shown inside the brackets.
dom.tooltip=DOM (%S)
+
+# LOCALIZATION NOTE (toolbox.buttons.splitconsole):
+# This is the tooltip of the button in the toolbox toolbar used to toggle
+# the split console.
+# Keyboard shortcut will be shown inside brackets.
+toolbox.buttons.splitconsole = Toggle split console (%S)
+
+# LOCALIZATION NOTE (toolbox.buttons.responsive):
+# This is the tooltip of the button in the toolbox toolbar that toggles
+# the Responsive mode.
+# Keyboard shortcut will be shown inside brackets.
+toolbox.buttons.responsive = Responsive Design Mode (%S)
+
+# LOCALIZATION NOTE (toolbox.buttons.paintflashing):
+# This is the tooltip of the paintflashing button in the toolbox toolbar
+# that toggles paintflashing.
+toolbox.buttons.paintflashing = Toggle paint flashing
+
+# LOCALIZATION NOTE (toolbox.buttons.scratchpad):
+# This is the tooltip of the button in the toolbox toolbar that opens
+# the scratchpad window
+toolbox.buttons.scratchpad = Scratchpad
+
+# LOCALIZATION NOTE (toolbox.buttons.screenshot):
+# This is the tooltip of the button in the toolbox toolbar that allows you to
+# take a screenshot of the entire page
+toolbox.buttons.screenshot = Take a screenshot of the entire page
+
+# LOCALIZATION NOTE (toolbox.buttons.rulers):
+# This is the tooltip of the button in the toolbox toolbar that toggles the
+# rulers in the page
+toolbox.buttons.rulers = Toggle rulers for the page
+
+# LOCALIZATION NOTE (toolbox.buttons.measure):
+# This is the tooltip of the button in the toolbox toolbar that toggles the
+# measuring tools
+toolbox.buttons.measure = Measure a portion of the page
diff --git a/devtools/client/menus.js b/devtools/client/menus.js
index 1aee8509561b..3b43cd5f2e7b 100644
--- a/devtools/client/menus.js
+++ b/devtools/client/menus.js
@@ -148,11 +148,7 @@ exports.menuitems = [
let window = event.target.ownerDocument.defaultView;
let target = TargetFactory.forTab(window.gBrowser.selectedTab);
- CommandUtils.createRequisition(target, {
- environment: CommandUtils.createEnvironment({target})
- }).then(requisition => {
- requisition.updateExec("eyedropper --frommenu");
- }, e => console.error(e));
+ CommandUtils.executeOnTarget(target, "eyedropper --frommenu");
},
checkbox: true
},
diff --git a/devtools/client/preferences/devtools.js b/devtools/client/preferences/devtools.js
index 6b17841ffc43..87473eb53c6e 100644
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -30,7 +30,6 @@ pref("devtools.toolbox.sidebar.width", 500);
pref("devtools.toolbox.host", "bottom");
pref("devtools.toolbox.previousHost", "side");
pref("devtools.toolbox.selectedTool", "webconsole");
-pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","scratchpad","resize toggle","screenshot --fullpage --file", "rulers", "measure"]');
pref("devtools.toolbox.sideEnabled", true);
pref("devtools.toolbox.zoomValue", "1");
pref("devtools.toolbox.splitconsoleEnabled", false);
diff --git a/devtools/client/responsive.html/browser/swap.js b/devtools/client/responsive.html/browser/swap.js
index e95371dba7b1..99ce6ebcbfb0 100644
--- a/devtools/client/responsive.html/browser/swap.js
+++ b/devtools/client/responsive.html/browser/swap.js
@@ -4,6 +4,7 @@
"use strict";
+const { Ci } = require("chrome");
const promise = require("promise");
const { Task } = require("devtools/shared/task");
const { tunnelToInnerBrowser } = require("./tunnel");
@@ -58,11 +59,16 @@ function swapToInnerBrowser({ tab, containerURL, getInnerBrowser }) {
freezeNavigationState(tab);
// 1. Create a temporary, hidden tab to load the tool UI.
- let containerTab = gBrowser.addTab(containerURL, {
+ let containerTab = gBrowser.addTab("about:blank", {
skipAnimation: true,
+ forceNotRemote: true,
});
gBrowser.hideTab(containerTab);
let containerBrowser = containerTab.linkedBrowser;
+ // Prevent the `containerURL` from ending up in the tab's history.
+ containerBrowser.loadURIWithFlags(containerURL, {
+ flags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY,
+ });
// Copy tab listener state flags to container tab. Each tab gets its own tab
// listener and state flags which cache document loading progress. The state flags
diff --git a/devtools/client/responsive.html/browser/tunnel.js b/devtools/client/responsive.html/browser/tunnel.js
index 9492afe0b8b8..6d42f05624b2 100644
--- a/devtools/client/responsive.html/browser/tunnel.js
+++ b/devtools/client/responsive.html/browser/tunnel.js
@@ -7,7 +7,6 @@
const { Ci } = require("chrome");
const Services = require("Services");
const { Task } = require("devtools/shared/task");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { BrowserElementWebNavigation } = require("./web-navigation");
const { getStack } = require("devtools/shared/platform/stack");
@@ -127,7 +126,8 @@ function tunnelToInnerBrowser(outer, inner) {
// This means the key that matches the content is on the inner browser. Since we
// want the browser UI to believe the page content is part of the outer browser, we
// copy the content's `permanentKey` up to the outer browser.
- copyPermanentKey(outer, inner);
+ debug("Copy inner permanentKey to outer browser");
+ outer.permanentKey = inner.permanentKey;
// Replace the outer browser's native messageManager with a message manager tunnel
// which we can use to route messages of interest to the inner browser instead.
@@ -304,33 +304,6 @@ function tunnelToInnerBrowser(outer, inner) {
exports.tunnelToInnerBrowser = tunnelToInnerBrowser;
-function copyPermanentKey(outer, inner) {
- // When we're in the process of swapping content around, we end up receiving a
- // SessionStore:update message which lists the container page that is loaded into the
- // outer browser (that we're hiding the inner browser within) as part of its history.
- // We want SessionStore's view of the history for our tab to only have the page content
- // of the inner browser, so we want to hide this message from SessionStore, but we have
- // no direct mechanism to do so. As a workaround, we wait until the one errant message
- // has gone by, and then we copy the permanentKey after that, since the permanentKey is
- // what SessionStore uses to identify each browser.
- let outerMM = outer[FRAME_LOADER].messageManager;
- let onHistoryEntry = message => {
- let data = message.data.data;
- let history = data.history || data.historychange;
- if (!history || !history.entries) {
- // Wait for a message that contains history data
- return;
- }
- outerMM.removeMessageListener("SessionStore:update", onHistoryEntry);
- debug("Got session update for outer browser");
- DevToolsUtils.executeSoon(() => {
- debug("Copy inner permanentKey to outer browser");
- outer.permanentKey = inner.permanentKey;
- });
- };
- outerMM.addMessageListener("SessionStore:update", onHistoryEntry);
-}
-
/**
* This module allows specific messages of interest to be directed from the
* outer browser to the inner browser (and vice versa) in a targetted fashion
diff --git a/devtools/client/responsive.html/test/browser/browser_tab_remoteness_change.js b/devtools/client/responsive.html/test/browser/browser_tab_remoteness_change.js
index 714b842bfd30..7ce32ff2848a 100644
--- a/devtools/client/responsive.html/test/browser/browser_tab_remoteness_change.js
+++ b/devtools/client/responsive.html/test/browser/browser_tab_remoteness_change.js
@@ -41,4 +41,5 @@ add_task(function* () {
is(ui.destroyed, true, "RDM closed synchronously");
yield clientClosed;
+ yield removeTab(tab);
});
diff --git a/devtools/client/responsive.html/test/browser/head.js b/devtools/client/responsive.html/test/browser/head.js
index a30f53f9e111..aa1398c3383c 100644
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -365,8 +365,11 @@ function addDeviceForTest(device) {
function waitForClientClose(ui) {
return new Promise(resolve => {
- info("RDM's debugger client is now closed");
- ui.client.addOneTimeListener("closed", resolve);
+ info("Waiting for RDM debugger client to close");
+ ui.client.addOneTimeListener("closed", () => {
+ info("RDM's debugger client is now closed");
+ resolve();
+ });
});
}
diff --git a/devtools/client/shared/developer-toolbar.js b/devtools/client/shared/developer-toolbar.js
index ab5ebd5a9d10..ae7a7a32b35a 100644
--- a/devtools/client/shared/developer-toolbar.js
+++ b/devtools/client/shared/developer-toolbar.js
@@ -35,6 +35,28 @@ loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
* A collection of utilities to help working with commands
*/
var CommandUtils = {
+ /**
+ * Caches requisitions created when calling executeOnTarget:
+ * Target => Requisition Promise
+ */
+ _requisitions: new WeakMap(),
+
+ /**
+ * Utility to execute a command string on a given target
+ */
+ executeOnTarget: Task.async(function* (target, command) {
+ let requisitionPromise = this._requisitions.get(target);
+ if (!requisitionPromise) {
+ requisitionPromise = this.createRequisition(target, {
+ environment: CommandUtils.createEnvironment({ target }, "target")
+ });
+ // Store the promise to avoid races by storing the promise immediately
+ this._requisitions.set(target, requisitionPromise);
+ }
+ let requisition = yield requisitionPromise;
+ requisition.updateExec(command);
+ }),
+
/**
* Utility to ensure that things are loaded in the correct order
*/
diff --git a/devtools/client/shared/test/browser_telemetry_button_paintflashing.js b/devtools/client/shared/test/browser_telemetry_button_paintflashing.js
index 1e3e78de09a8..6da63d978242 100644
--- a/devtools/client/shared/test/browser_telemetry_button_paintflashing.js
+++ b/devtools/client/shared/test/browser_telemetry_button_paintflashing.js
@@ -44,16 +44,13 @@ function* delayedClicks(toolbox, node, clicks) {
setTimeout(() => resolve(), TOOL_DELAY);
});
- // this event will fire once the command execution starts and
- // the output object is created
- let clicked = toolbox._requisition.commandOutputManager.onOutput.once();
+ let PaintFlashingCmd = require("devtools/shared/gcli/commands/paintflashing");
+ let clicked = PaintFlashingCmd.eventEmitter.once("changed");
info("Clicking button " + node.id);
node.click();
- let outputEvent = yield clicked;
- // promise gets resolved once execution finishes and output is ready
- yield outputEvent.output.promise;
+ yield clicked;
}
}
diff --git a/devtools/client/themes/webconsole.css b/devtools/client/themes/webconsole.css
index 2b89f2877ea9..361c2ef5917b 100644
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -744,6 +744,10 @@ a.learn-more-link.webconsole-learn-more-link {
margin-inline-end: 5px;
}
+.network .message-flex-body > .message-body {
+ display: flex;
+}
+
.webconsole-output-wrapper .message .indent {
display: inline-block;
border-inline-end: solid 1px var(--theme-splitter-color);
@@ -757,7 +761,7 @@ a.learn-more-link.webconsole-learn-more-link {
.message.startGroup .icon,
.message.startGroupCollapsed .icon {
- display: none;
+ display: none;
}
/* console.table() */
diff --git a/devtools/client/webconsole/new-console-output/actions/messages.js b/devtools/client/webconsole/new-console-output/actions/messages.js
index 4aec73d99d93..8dba35ac088e 100644
--- a/devtools/client/webconsole/new-console-output/actions/messages.js
+++ b/devtools/client/webconsole/new-console-output/actions/messages.js
@@ -13,6 +13,7 @@ const { IdGenerator } = require("devtools/client/webconsole/new-console-output/u
const { batchActions } = require("devtools/client/webconsole/new-console-output/actions/enhancers");
const {
MESSAGE_ADD,
+ NETWORK_MESSAGE_UPDATE,
MESSAGES_CLEAR,
MESSAGE_OPEN,
MESSAGE_CLOSE,
@@ -90,10 +91,25 @@ function messageTableDataReceive(id, data) {
};
}
+function networkMessageUpdate(packet, idGenerator = null) {
+ if (idGenerator == null) {
+ idGenerator = defaultIdGenerator;
+ }
+
+ let message = prepareMessage(packet, idGenerator);
+
+ return {
+ type: NETWORK_MESSAGE_UPDATE,
+ message,
+ };
+}
+
module.exports = {
messageAdd,
messagesClear,
messageOpen,
messageClose,
messageTableDataGet,
+ networkMessageUpdate,
};
+
diff --git a/devtools/client/webconsole/new-console-output/components/message-container.js b/devtools/client/webconsole/new-console-output/components/message-container.js
index 0d80b076a425..d7e36b4a5681 100644
--- a/devtools/client/webconsole/new-console-output/components/message-container.js
+++ b/devtools/client/webconsole/new-console-output/components/message-container.js
@@ -50,7 +50,10 @@ const MessageContainer = createClass({
const repeatChanged = this.props.message.repeat !== nextProps.message.repeat;
const openChanged = this.props.open !== nextProps.open;
const tableDataChanged = this.props.tableData !== nextProps.tableData;
- return repeatChanged || openChanged || tableDataChanged;
+ const responseChanged = this.props.message.response !== nextProps.message.response;
+ const totalTimeChanged = this.props.message.totalTime !== nextProps.message.totalTime;
+ return repeatChanged || openChanged || tableDataChanged || responseChanged ||
+ totalTimeChanged;
},
render() {
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
index b9c24b3d67ba..0a1cdee28858 100644
--- a/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
@@ -29,13 +29,35 @@ NetworkEventMessage.defaultProps = {
indent: 0,
};
-function NetworkEventMessage(props) {
- const { message, serviceContainer, indent } = props;
- const { actor, source, type, level, request, isXHR, timeStamp } = message;
+function NetworkEventMessage({
+ indent,
+ message = {},
+ serviceContainer,
+}) {
+ const {
+ actor,
+ source,
+ type,
+ level,
+ request,
+ response: {
+ httpVersion,
+ status,
+ statusText,
+ },
+ isXHR,
+ timeStamp,
+ totalTime,
+ } = message;
const topLevelClasses = [ "cm-s-mozilla" ];
+ let statusInfo;
- function onUrlClick() {
+ if (httpVersion && status && statusText && totalTime !== undefined) {
+ statusInfo = `[${httpVersion} ${status} ${statusText} ${totalTime}ms]`;
+ }
+
+ function openNetworkMonitor() {
serviceContainer.openNetworkPanel(actor);
}
@@ -43,10 +65,13 @@ function NetworkEventMessage(props) {
const xhr = isXHR
? dom.span({ className: "xhr" }, l10n.getStr("webConsoleXhrIndicator"))
: null;
- const url = dom.a({ className: "url", title: request.url, onClick: onUrlClick },
- request.url.replace(/\?.+/, ""));
+ const url = dom.a({ className: "url", title: request.url, onClick: openNetworkMonitor },
+ request.url.replace(/\?.+/, ""));
+ const statusBody = statusInfo
+ ? dom.a({ className: "status", onClick: openNetworkMonitor }, statusInfo)
+ : null;
- const messageBody = dom.span({}, method, xhr, url);
+ const messageBody = [method, xhr, url, statusBody];
const childProps = {
source,
diff --git a/devtools/client/webconsole/new-console-output/constants.js b/devtools/client/webconsole/new-console-output/constants.js
index 23d690267f41..696e81428c93 100644
--- a/devtools/client/webconsole/new-console-output/constants.js
+++ b/devtools/client/webconsole/new-console-output/constants.js
@@ -11,6 +11,7 @@ const actionTypes = {
MESSAGES_CLEAR: "MESSAGES_CLEAR",
MESSAGE_OPEN: "MESSAGE_OPEN",
MESSAGE_CLOSE: "MESSAGE_CLOSE",
+ NETWORK_MESSAGE_UPDATE: "NETWORK_MESSAGE_UPDATE",
MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
TIMESTAMPS_TOGGLE: "TIMESTAMPS_TOGGLE",
FILTER_TOGGLE: "FILTER_TOGGLE",
diff --git a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
index af253e7de5a9..519800f94167 100644
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -134,7 +134,7 @@ NewConsoleOutputWrapper.prototype = {
let jsterm = this.jsterm;
jsterm.hud.on("new-messages", function onThisMessage(e, messages) {
for (let m of messages) {
- if (m.messageId == messageId) {
+ if (m.messageId === messageId) {
resolve(m.node);
jsterm.hud.off("new-messages", onThisMessage);
return;
@@ -160,6 +160,16 @@ NewConsoleOutputWrapper.prototype = {
store.dispatch(actions.timestampsToggle(enabled));
},
+ dispatchMessageUpdate: function (message, res) {
+ batchedMessageAdd(actions.networkMessageUpdate(message));
+
+ // network-message-updated will emit when eventTimings message arrives
+ // which is the last one of 8 updates happening on network message update.
+ if (res.packet.updateType === "eventTimings") {
+ this.jsterm.hud.emit("network-message-updated", res);
+ }
+ },
+
// Should be used for test purpose only.
getStore: function () {
return store;
diff --git a/devtools/client/webconsole/new-console-output/reducers/messages.js b/devtools/client/webconsole/new-console-output/reducers/messages.js
index ee69e5e15579..3531af55248c 100644
--- a/devtools/client/webconsole/new-console-output/reducers/messages.js
+++ b/devtools/client/webconsole/new-console-output/reducers/messages.js
@@ -98,6 +98,11 @@ function messages(state = new MessageState(), action) {
case constants.MESSAGE_TABLE_RECEIVE:
const {id, data} = action;
return state.set("messagesTableDataById", messagesTableDataById.set(id, data));
+ case constants.NETWORK_MESSAGE_UPDATE:
+ let updateMessage = action.message;
+ return state.set("messagesById", messagesById.map((message) =>
+ (message.id === updateMessage.id) ? updateMessage : message
+ ));
}
return state;
diff --git a/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js b/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
index 2aa550a9e61b..25e4a4bdf7a4 100644
--- a/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
@@ -18,11 +18,12 @@ const { stubPreparedMessages } = require("devtools/client/webconsole/new-console
const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
const EXPECTED_URL = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html";
+const EXPECTED_STATUS = /\[HTTP\/\d\.\d \d+ [A-Za-z ]+ \d+ms\]/;
describe("NetworkEventMessage component:", () => {
describe("GET request", () => {
it("renders as expected", () => {
- const message = stubPreparedMessages.get("GET request");
+ const message = stubPreparedMessages.get("GET request eventTimings");
const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
const L10n = require("devtools/client/webconsole/new-console-output/test/fixtures/L10n");
const { timestampString } = new L10n();
@@ -32,9 +33,8 @@ describe("NetworkEventMessage component:", () => {
expect(wrapper.find(".message-body .xhr").length).toBe(0);
expect(wrapper.find(".message-body .url").length).toBe(1);
expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
- expect(wrapper
- .find("div.message.cm-s-mozilla span.message-body.devtools-monospace").length
- ).toBe(1);
+ expect(wrapper.find(".message-body .status").length).toBe(1);
+ expect(wrapper.find(".message-body .status").text()).toMatch(EXPECTED_STATUS);
});
it("has the expected indent", () => {
@@ -52,21 +52,20 @@ describe("NetworkEventMessage component:", () => {
describe("XHR GET request", () => {
it("renders as expected", () => {
- const message = stubPreparedMessages.get("XHR GET request");
+ const message = stubPreparedMessages.get("XHR GET request eventTimings");
const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
expect(wrapper.find(".message-body .method").text()).toBe("GET");
expect(wrapper.find(".message-body .xhr").length).toBe(1);
expect(wrapper.find(".message-body .xhr").text()).toBe("XHR");
expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
- let selector = "div.message.cm-s-mozilla span.message-body.devtools-monospace";
- expect(wrapper.find(selector).length).toBe(1);
+ expect(wrapper.find(".message-body .status").text()).toMatch(EXPECTED_STATUS);
});
});
describe("XHR POST request", () => {
it("renders as expected", () => {
- const message = stubPreparedMessages.get("XHR POST request");
+ const message = stubPreparedMessages.get("XHR POST request eventTimings");
const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
expect(wrapper.find(".message-body .method").text()).toBe("POST");
@@ -74,8 +73,8 @@ describe("NetworkEventMessage component:", () => {
expect(wrapper.find(".message-body .xhr").text()).toBe("XHR");
expect(wrapper.find(".message-body .url").length).toBe(1);
expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
- let selector = "div.message.cm-s-mozilla span.message-body.devtools-monospace";
- expect(wrapper.find(selector).length);
+ expect(wrapper.find(".message-body .status").length).toBe(1);
+ expect(wrapper.find(".message-body .status").text()).toMatch(EXPECTED_STATUS);
});
});
});
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js
index 5d42935a0393..2b15a6131b59 100644
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js
@@ -24,22 +24,34 @@ add_task(function* () {
ok(ui.jsterm, "jsterm exists");
ok(ui.newConsoleOutput, "newConsoleOutput exists");
- let received = new Promise(resolve => {
+ let networkEvent = new Promise(resolve => {
let i = 0;
- toolbox.target.client.addListener(TARGET, (type, res) => {
+ toolbox.target.activeConsole.on(TARGET, (type, res) => {
stubs.packets.push(formatPacket(keys[i], res));
- stubs.preparedMessages.push(formatNetworkStub(keys[i], res));
+ stubs.preparedMessages.push(formatNetworkEventStub(keys[i], res));
if (++i === keys.length) {
resolve();
}
});
});
+ let networkEventUpdate = new Promise(resolve => {
+ let i = 0;
+ ui.jsterm.hud.on("network-message-updated", function onNetworkUpdated(event, res) {
+ ui.jsterm.hud.off("network-message-updated", onNetworkUpdated);
+ stubs.preparedMessages.push(
+ formatNetworkEventStub(`${keys[i++]} ${res.packet.updateType}`, res));
+ if (i === keys.length) {
+ resolve();
+ }
+ });
+ });
+
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function () {
content.wrappedJSObject.triggerPacket();
});
- yield received;
+ yield Promise.all([networkEvent, networkEventUpdate]);
}
let filePath = OS.Path.join(`${BASE_PATH}/stubs/${TARGET}.js`);
OS.File.writeAtomic(filePath, formatFile(stubs, "NetworkEventMessage"));
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
index f5dcdde5fb02..35c230be3be0 100644
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
@@ -4,7 +4,7 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from ../../../../../framework/test/shared-head.js */
/* exported TEMP_FILE_PATH, TEMP_CSS_FILE_PATH, formatPacket, formatStub,
- formatNetworkStub, formatFile */
+ formatNetworkEventStub, formatFile */
"use strict";
// shared-head.js handles imports, constants, and utility functions
@@ -133,31 +133,9 @@ function formatStub(key, packet) {
return `stubPreparedMessages.set("${key}", new ConsoleMessage(${stringifiedMessage}));`;
}
-function formatNetworkStub(key, packet) {
- let actor = packet.eventActor;
- let networkInfo = {
- _type: "NetworkEvent",
- timeStamp: actor.timeStamp,
- node: null,
- actor: actor.actor,
- discardRequestBody: true,
- discardResponseBody: true,
- startedDateTime: actor.startedDateTime,
- request: {
- url: actor.url,
- method: actor.method,
- },
- isXHR: actor.isXHR,
- cause: actor.cause,
- response: {},
- timings: {},
- // track the list of network event updates
- updates: [],
- private: actor.private,
- fromCache: actor.fromCache,
- fromServiceWorker: actor.fromServiceWorker
- };
- let prepared = prepareMessage(networkInfo, {getNextId: () => "1"});
+function formatNetworkEventStub(key, packet) {
+ let networkInfo = packet.actor ? packet : packet.networkInfo;
+ let prepared = prepareMessage(networkInfo, {getNextId: () => networkInfo.actor});
let stringifiedMessage = JSON.stringify(prepared, null, 2);
return `stubPreparedMessages.set("${key}", ` +
`new NetworkEventMessage(${stringifiedMessage}));`;
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js
index 71abbdff0bc3..02510bad2610 100644
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js
@@ -14,8 +14,8 @@ const { NetworkEventMessage } =
let stubPreparedMessages = new Map();
let stubPackets = new Map();
stubPreparedMessages.set("GET request", new NetworkEventMessage({
- "id": "1",
- "actor": "server1.conn0.child1/netEvent29",
+ "id": "server1.conn0.child1/netEvent30",
+ "actor": "server1.conn0.child1/netEvent30",
"level": "log",
"isXHR": false,
"request": {
@@ -25,13 +25,38 @@ stubPreparedMessages.set("GET request", new NetworkEventMessage({
"response": {},
"source": "network",
"type": "log",
- "timeStamp": 1479159937660,
- "groupId": null
+ "groupId": null,
+ "timeStamp": 1485777989897
+}));
+
+stubPreparedMessages.set("GET request eventTimings", new NetworkEventMessage({
+ "id": "server1.conn0.child1/netEvent30",
+ "actor": "server1.conn0.child1/netEvent30",
+ "level": "log",
+ "isXHR": false,
+ "request": {
+ "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+ "method": "GET",
+ "headersSize": 489
+ },
+ "response": {
+ "httpVersion": "HTTP/1.1",
+ "status": "404",
+ "statusText": "Not Found",
+ "headersSize": 160,
+ "remoteAddress": "127.0.0.1",
+ "remotePort": 8888
+ },
+ "source": "network",
+ "type": "log",
+ "groupId": null,
+ "timeStamp": 1485777989897,
+ "totalTime": 7
}));
stubPreparedMessages.set("XHR GET request", new NetworkEventMessage({
- "id": "1",
- "actor": "server1.conn1.child1/netEvent29",
+ "id": "server1.conn1.child1/netEvent30",
+ "actor": "server1.conn1.child1/netEvent30",
"level": "log",
"isXHR": true,
"request": {
@@ -41,13 +66,38 @@ stubPreparedMessages.set("XHR GET request", new NetworkEventMessage({
"response": {},
"source": "network",
"type": "log",
- "timeStamp": 1479159938522,
- "groupId": null
+ "groupId": null,
+ "timeStamp": 1485777990639
+}));
+
+stubPreparedMessages.set("XHR GET request eventTimings", new NetworkEventMessage({
+ "id": "server1.conn1.child1/netEvent30",
+ "actor": "server1.conn1.child1/netEvent30",
+ "level": "log",
+ "isXHR": true,
+ "request": {
+ "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+ "method": "GET",
+ "headersSize": 489
+ },
+ "response": {
+ "httpVersion": "HTTP/1.1",
+ "status": "404",
+ "statusText": "Not Found",
+ "headersSize": 160,
+ "remoteAddress": "127.0.0.1",
+ "remotePort": 8888
+ },
+ "source": "network",
+ "type": "log",
+ "groupId": null,
+ "timeStamp": 1485777990639,
+ "totalTime": 10
}));
stubPreparedMessages.set("XHR POST request", new NetworkEventMessage({
- "id": "1",
- "actor": "server1.conn2.child1/netEvent29",
+ "id": "server1.conn2.child1/netEvent30",
+ "actor": "server1.conn2.child1/netEvent30",
"level": "log",
"isXHR": true,
"request": {
@@ -57,131 +107,174 @@ stubPreparedMessages.set("XHR POST request", new NetworkEventMessage({
"response": {},
"source": "network",
"type": "log",
- "timeStamp": 1479159939328,
- "groupId": null
+ "groupId": null,
+ "timeStamp": 1485777991739
+}));
+
+stubPreparedMessages.set("XHR POST request eventTimings", new NetworkEventMessage({
+ "id": "server1.conn2.child1/netEvent30",
+ "actor": "server1.conn2.child1/netEvent30",
+ "level": "log",
+ "isXHR": true,
+ "request": {
+ "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+ "method": "POST",
+ "headersSize": 509
+ },
+ "response": {
+ "httpVersion": "HTTP/1.1",
+ "status": "404",
+ "statusText": "Not Found",
+ "headersSize": 160,
+ "remoteAddress": "127.0.0.1",
+ "remotePort": 8888
+ },
+ "source": "network",
+ "type": "log",
+ "groupId": null,
+ "timeStamp": 1485777991739,
+ "totalTime": 9
}));
stubPackets.set("GET request", {
- "from": "server1.conn0.child1/consoleActor2",
- "type": "networkEvent",
- "eventActor": {
- "actor": "server1.conn0.child1/netEvent29",
- "startedDateTime": "2016-10-15T23:12:04.196Z",
- "timeStamp": 1479159937660,
+ "_type": "NetworkEvent",
+ "timeStamp": 1485777989897,
+ "node": null,
+ "actor": "server1.conn0.child1/netEvent30",
+ "discardRequestBody": true,
+ "discardResponseBody": true,
+ "startedDateTime": "2017-01-30T12:06:29.897Z",
+ "request": {
"url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
- "method": "GET",
- "isXHR": false,
- "cause": {
- "type": 3,
- "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
- "stacktrace": [
- {
- "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
- "lineNumber": 3,
- "columnNumber": 1,
- "functionName": "triggerPacket",
- "asyncCause": null
- },
- {
- "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
- "lineNumber": 4,
- "columnNumber": 7,
- "functionName": null,
- "asyncCause": null
- },
- {
- "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
- "lineNumber": 53,
- "columnNumber": 20,
- "functionName": null,
- "asyncCause": null
- }
- ]
- },
- "private": false
- }
+ "method": "GET"
+ },
+ "isXHR": false,
+ "cause": {
+ "type": "img",
+ "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
+ "stacktrace": [
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
+ "lineNumber": 3,
+ "columnNumber": 1,
+ "functionName": "triggerPacket",
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+ "lineNumber": 4,
+ "columnNumber": 7,
+ "functionName": null,
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+ "lineNumber": 53,
+ "columnNumber": 20,
+ "functionName": null,
+ "asyncCause": null
+ }
+ ]
+ },
+ "response": {},
+ "timings": {},
+ "updates": [],
+ "private": false,
+ "from": "server1.conn0.child1/consoleActor2"
});
stubPackets.set("XHR GET request", {
- "from": "server1.conn1.child1/consoleActor2",
- "type": "networkEvent",
- "eventActor": {
- "actor": "server1.conn1.child1/netEvent29",
- "startedDateTime": "2016-10-15T23:12:05.690Z",
- "timeStamp": 1479159938522,
+ "_type": "NetworkEvent",
+ "timeStamp": 1485777990639,
+ "node": null,
+ "actor": "server1.conn1.child1/netEvent30",
+ "discardRequestBody": true,
+ "discardResponseBody": true,
+ "startedDateTime": "2017-01-30T12:06:30.639Z",
+ "request": {
"url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
- "method": "GET",
- "isXHR": true,
- "cause": {
- "type": 11,
- "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
- "stacktrace": [
- {
- "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
- "lineNumber": 4,
- "columnNumber": 1,
- "functionName": "triggerPacket",
- "asyncCause": null
- },
- {
- "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
- "lineNumber": 4,
- "columnNumber": 7,
- "functionName": null,
- "asyncCause": null
- },
- {
- "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
- "lineNumber": 53,
- "columnNumber": 20,
- "functionName": null,
- "asyncCause": null
- }
- ]
- },
- "private": false
- }
+ "method": "GET"
+ },
+ "isXHR": true,
+ "cause": {
+ "type": "xhr",
+ "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
+ "stacktrace": [
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
+ "lineNumber": 4,
+ "columnNumber": 1,
+ "functionName": "triggerPacket",
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+ "lineNumber": 4,
+ "columnNumber": 7,
+ "functionName": null,
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+ "lineNumber": 53,
+ "columnNumber": 20,
+ "functionName": null,
+ "asyncCause": null
+ }
+ ]
+ },
+ "response": {},
+ "timings": {},
+ "updates": [],
+ "private": false,
+ "from": "server1.conn1.child1/consoleActor2"
});
stubPackets.set("XHR POST request", {
- "from": "server1.conn2.child1/consoleActor2",
- "type": "networkEvent",
- "eventActor": {
- "actor": "server1.conn2.child1/netEvent29",
- "startedDateTime": "2016-10-15T23:12:07.158Z",
- "timeStamp": 1479159939328,
+ "_type": "NetworkEvent",
+ "timeStamp": 1485777991739,
+ "node": null,
+ "actor": "server1.conn2.child1/netEvent30",
+ "discardRequestBody": true,
+ "discardResponseBody": true,
+ "startedDateTime": "2017-01-30T12:06:31.739Z",
+ "request": {
"url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
- "method": "POST",
- "isXHR": true,
- "cause": {
- "type": 11,
- "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
- "stacktrace": [
- {
- "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
- "lineNumber": 4,
- "columnNumber": 1,
- "functionName": "triggerPacket",
- "asyncCause": null
- },
- {
- "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
- "lineNumber": 4,
- "columnNumber": 7,
- "functionName": null,
- "asyncCause": null
- },
- {
- "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
- "lineNumber": 53,
- "columnNumber": 20,
- "functionName": null,
- "asyncCause": null
- }
- ]
- },
- "private": false
- }
+ "method": "POST"
+ },
+ "isXHR": true,
+ "cause": {
+ "type": "xhr",
+ "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
+ "stacktrace": [
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
+ "lineNumber": 4,
+ "columnNumber": 1,
+ "functionName": "triggerPacket",
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+ "lineNumber": 4,
+ "columnNumber": 7,
+ "functionName": null,
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+ "lineNumber": 53,
+ "columnNumber": 20,
+ "functionName": null,
+ "asyncCause": null
+ }
+ ]
+ },
+ "response": {},
+ "timings": {},
+ "updates": [],
+ "private": false,
+ "from": "server1.conn2.child1/consoleActor2"
});
module.exports = {
diff --git a/devtools/client/webconsole/new-console-output/types.js b/devtools/client/webconsole/new-console-output/types.js
index 277d8a198ee7..39e909b6b061 100644
--- a/devtools/client/webconsole/new-console-output/types.js
+++ b/devtools/client/webconsole/new-console-output/types.js
@@ -50,6 +50,7 @@ exports.NetworkEventMessage = Immutable.Record({
response: null,
source: MESSAGE_SOURCE.NETWORK,
type: MESSAGE_TYPE.LOG,
- timeStamp: null,
groupId: null,
+ timeStamp: null,
+ totalTime: null,
});
diff --git a/devtools/client/webconsole/new-console-output/utils/id-generator.js b/devtools/client/webconsole/new-console-output/utils/id-generator.js
index 7d875b750627..965f11e4b2e5 100644
--- a/devtools/client/webconsole/new-console-output/utils/id-generator.js
+++ b/devtools/client/webconsole/new-console-output/utils/id-generator.js
@@ -11,12 +11,7 @@ exports.IdGenerator = class IdGenerator {
this.messageId = 1;
}
- getNextId() {
- // Return the next message id, as a string.
- return "" + this.messageId++;
- }
-
- getCurrentId() {
- return this.messageId;
+ getNextId(packet) {
+ return (packet && packet.actor) ? packet.actor : "" + this.messageId++;
}
};
diff --git a/devtools/client/webconsole/new-console-output/utils/messages.js b/devtools/client/webconsole/new-console-output/utils/messages.js
index 38b32e67aac7..3b003d9f6463 100644
--- a/devtools/client/webconsole/new-console-output/utils/messages.js
+++ b/devtools/client/webconsole/new-console-output/utils/messages.js
@@ -29,7 +29,7 @@ function prepareMessage(packet, idGenerator) {
if (packet.allowRepeating) {
packet = packet.set("repeatId", getRepeatId(packet));
}
- return packet.set("id", idGenerator.getNextId());
+ return packet.set("id", idGenerator.getNextId(packet));
}
/**
@@ -182,7 +182,8 @@ function transformPacket(packet) {
isXHR: networkEvent.isXHR,
request: networkEvent.request,
response: networkEvent.response,
- timeStamp: networkEvent.timeStamp
+ timeStamp: networkEvent.timeStamp,
+ totalTime: networkEvent.totalTime,
});
}
diff --git a/devtools/client/webconsole/webconsole-connection-proxy.js b/devtools/client/webconsole/webconsole-connection-proxy.js
index 8ecabfb41fff..e19c09eba1df 100644
--- a/devtools/client/webconsole/webconsole-connection-proxy.js
+++ b/devtools/client/webconsole/webconsole-connection-proxy.js
@@ -228,17 +228,24 @@ WebConsoleConnectionProxy.prototype = {
/**
* Dispatch a message add on the new frontend and emit an event for tests.
*/
- dispatchMessageAdd: function(packet) {
+ dispatchMessageAdd: function (packet) {
this.webConsoleFrame.newConsoleOutput.dispatchMessageAdd(packet);
},
/**
* Batched dispatch of messages.
*/
- dispatchMessagesAdd: function(packets) {
+ dispatchMessagesAdd: function (packets) {
this.webConsoleFrame.newConsoleOutput.dispatchMessagesAdd(packets);
},
+ /**
+ * Dispatch a message event on the new frontend and emit an event for tests.
+ */
+ dispatchMessageUpdate: function (networkInfo, response) {
+ this.webConsoleFrame.newConsoleOutput.dispatchMessageUpdate(networkInfo, response);
+ },
+
/**
* The "cachedMessages" response handler.
*
@@ -360,13 +367,15 @@ WebConsoleConnectionProxy.prototype = {
* @private
* @param string type
* Message type.
- * @param object packet
- * The message received from the server.
- * @param object networkInfo
- * The network request information.
+ * @param object response
+ * The update response received from the server.
*/
- _onNetworkEventUpdate: function (type, { packet, networkInfo }) {
+ _onNetworkEventUpdate: function (type, response) {
+ let { packet, networkInfo } = response;
if (this.webConsoleFrame) {
+ if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
+ this.dispatchMessageUpdate(networkInfo, response);
+ }
this.webConsoleFrame.handleNetworkEventUpdate(networkInfo, packet);
}
},
@@ -497,4 +506,4 @@ WebConsoleConnectionProxy.prototype = {
},
};
-exports.WebConsoleConnectionProxy = WebConsoleConnectionProxy;
\ No newline at end of file
+exports.WebConsoleConnectionProxy = WebConsoleConnectionProxy;
diff --git a/devtools/server/actors/common.js b/devtools/server/actors/common.js
index 2a36543fc3ac..412ff5fc6b30 100644
--- a/devtools/server/actors/common.js
+++ b/devtools/server/actors/common.js
@@ -235,9 +235,9 @@ ActorPool.prototype = {
addActor: function APAddActor(actor) {
actor.conn = this.conn;
if (!actor.actorID) {
- let prefix = actor.actorPrefix;
+ // Older style actors use actorPrefix, while protocol.js-based actors use typeName
+ let prefix = actor.actorPrefix || actor.typeName;
if (!prefix && typeof actor == "function") {
- // typeName is a convention used with protocol.js-based actors
prefix = actor.prototype.actorPrefix || actor.prototype.typeName;
}
actor.actorID = this.conn.allocID(prefix || undefined);
diff --git a/devtools/shared/fronts/inspector.js b/devtools/shared/fronts/inspector.js
index c76b41fe7d19..3e1ee7c01dd9 100644
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -991,11 +991,7 @@ var InspectorFront = FrontClassWithSpec(inspectorSpec, {
if (toolbox) {
// If the eyedropper was already started using the gcli command, hide it so we don't
// end up with 2 instances of the eyedropper on the page.
- let {target} = toolbox;
- let requisition = yield CommandUtils.createRequisition(target, {
- environment: CommandUtils.createEnvironment({target})
- });
- yield requisition.updateExec("eyedropper --hide");
+ CommandUtils.executeOnTarget(toolbox.target, "eyedropper --hide");
}
yield this._pickColorFromPage(options);
diff --git a/devtools/shared/gcli/commands/paintflashing.js b/devtools/shared/gcli/commands/paintflashing.js
index a3df9bdd1cc1..c355d7c7ffa3 100644
--- a/devtools/shared/gcli/commands/paintflashing.js
+++ b/devtools/shared/gcli/commands/paintflashing.js
@@ -19,6 +19,9 @@ try {
const EventEmitter = require("devtools/shared/event-emitter");
const eventEmitter = new EventEmitter();
+// exports the event emitter to help test know when this command is toggled
+exports.eventEmitter = eventEmitter;
+
const gcli = require("gcli/index");
const l10n = require("gcli/l10n");
diff --git a/dom/crypto/CryptoKey.cpp b/dom/crypto/CryptoKey.cpp
index 453dfeb48515..99f72b90d467 100644
--- a/dom/crypto/CryptoKey.cpp
+++ b/dom/crypto/CryptoKey.cpp
@@ -79,7 +79,7 @@ DestroyPrivateKeyWithoutDestroyingPKCS11Object(SECKEYPrivateKey* key)
// generates a random ID for each key. The given template must contain an
// attribute slot for a key ID, but it must consist of a null pointer and have a
// length of 0.
-SECKEYPrivateKey*
+UniqueSECKEYPrivateKey
PrivateKeyFromPrivateKeyTemplate(CK_ATTRIBUTE* aTemplate,
CK_ULONG aTemplateSize)
{
@@ -147,7 +147,8 @@ PrivateKeyFromPrivateKeyTemplate(CK_ATTRIBUTE* aTemplate,
}
// Have NSS translate the object to a private key.
- return PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr);
+ return UniqueSECKEYPrivateKey(
+ PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr));
}
CryptoKey::CryptoKey(nsIGlobalObject* aGlobal)
@@ -364,8 +365,8 @@ CryptoKey::AddPublicKeyData(SECKEYPublicKey* aPublicKey)
{ CKA_VALUE, value.data, value.len },
};
- mPrivateKey = UniqueSECKEYPrivateKey(
- PrivateKeyFromPrivateKeyTemplate(keyTemplate, ArrayLength(keyTemplate)));
+ mPrivateKey = PrivateKeyFromPrivateKeyTemplate(keyTemplate,
+ ArrayLength(keyTemplate));
NS_ENSURE_TRUE(mPrivateKey, NS_ERROR_DOM_OPERATION_ERR);
return NS_OK;
@@ -485,24 +486,24 @@ CryptoKey::GetSymKey() const
return mSymKey;
}
-SECKEYPrivateKey*
+UniqueSECKEYPrivateKey
CryptoKey::GetPrivateKey() const
{
nsNSSShutDownPreventionLock locker;
if (!mPrivateKey || isAlreadyShutDown()) {
return nullptr;
}
- return SECKEY_CopyPrivateKey(mPrivateKey.get());
+ return UniqueSECKEYPrivateKey(SECKEY_CopyPrivateKey(mPrivateKey.get()));
}
-SECKEYPublicKey*
+UniqueSECKEYPublicKey
CryptoKey::GetPublicKey() const
{
nsNSSShutDownPreventionLock locker;
if (!mPublicKey || isAlreadyShutDown()) {
return nullptr;
}
- return SECKEY_CopyPublicKey(mPublicKey.get());
+ return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(mPublicKey.get()));
}
void CryptoKey::virtualDestroyNSSReference()
@@ -519,11 +520,10 @@ void CryptoKey::destructorSafeDestroyNSSReference()
// Serialization and deserialization convenience methods
-SECKEYPrivateKey*
+UniqueSECKEYPrivateKey
CryptoKey::PrivateKeyFromPkcs8(CryptoBuffer& aKeyData,
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
- SECKEYPrivateKey* privKey;
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
if (!slot) {
return nullptr;
@@ -542,6 +542,7 @@ CryptoKey::PrivateKeyFromPkcs8(CryptoBuffer& aKeyData,
// Allow everything, we enforce usage ourselves
unsigned int usage = KU_ALL;
+ SECKEYPrivateKey* privKey;
SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey(
slot.get(), &pkcs8Item, nullptr, nullptr, false, false,
usage, &privKey, nullptr);
@@ -549,10 +550,11 @@ CryptoKey::PrivateKeyFromPkcs8(CryptoBuffer& aKeyData,
if (rv == SECFailure) {
return nullptr;
}
- return privKey;
+
+ return UniqueSECKEYPrivateKey(privKey);
}
-SECKEYPublicKey*
+UniqueSECKEYPublicKey
CryptoKey::PublicKeyFromSpki(CryptoBuffer& aKeyData,
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
@@ -607,7 +609,7 @@ CryptoKey::PublicKeyFromSpki(CryptoBuffer& aKeyData,
return nullptr;
}
- return SECKEY_CopyPublicKey(tmp.get());
+ return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(tmp.get()));
}
nsresult
@@ -748,7 +750,7 @@ CreateECPointForCoordinates(const CryptoBuffer& aX,
return point;
}
-SECKEYPrivateKey*
+UniqueSECKEYPrivateKey
CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk,
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
@@ -1000,7 +1002,7 @@ CryptoKey::PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
}
}
-SECKEYPublicKey*
+UniqueSECKEYPublicKey
CreateECPublicKey(const SECItem* aKeyData, const nsString& aNamedCurve)
{
UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
@@ -1037,10 +1039,10 @@ CreateECPublicKey(const SECItem* aKeyData, const nsString& aNamedCurve)
return nullptr;
}
- return SECKEY_CopyPublicKey(key.get());
+ return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(key.get()));
}
-SECKEYPublicKey*
+UniqueSECKEYPublicKey
CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk,
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
@@ -1074,7 +1076,7 @@ CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk,
return nullptr;
}
- return SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA);
+ return UniqueSECKEYPublicKey(SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA));
}
if (aJwk.mKty.EqualsLiteral(JWK_TYPE_EC)) {
@@ -1140,7 +1142,7 @@ CryptoKey::PublicKeyToJwk(SECKEYPublicKey* aPubKey,
}
}
-SECKEYPublicKey*
+UniqueSECKEYPublicKey
CryptoKey::PublicDhKeyFromRaw(CryptoBuffer& aKeyData,
const CryptoBuffer& aPrime,
const CryptoBuffer& aGenerator,
@@ -1171,7 +1173,7 @@ CryptoKey::PublicDhKeyFromRaw(CryptoBuffer& aKeyData,
key->u.dh.base.type = siUnsignedInteger;
key->u.dh.publicValue.type = siUnsignedInteger;
- return SECKEY_CopyPublicKey(key);
+ return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(key));
}
nsresult
@@ -1185,7 +1187,7 @@ CryptoKey::PublicDhKeyToRaw(SECKEYPublicKey* aPubKey,
return NS_OK;
}
-SECKEYPublicKey*
+UniqueSECKEYPublicKey
CryptoKey::PublicECKeyFromRaw(CryptoBuffer& aKeyData,
const nsString& aNamedCurve,
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
@@ -1322,12 +1324,10 @@ CryptoKey::ReadStructuredClone(JSStructuredCloneReader* aReader)
return false;
}
if (priv.Length() > 0) {
- mPrivateKey = UniqueSECKEYPrivateKey(
- CryptoKey::PrivateKeyFromPkcs8(priv, locker));
+ mPrivateKey = CryptoKey::PrivateKeyFromPkcs8(priv, locker);
}
if (pub.Length() > 0) {
- mPublicKey = UniqueSECKEYPublicKey(
- CryptoKey::PublicKeyFromSpki(pub, locker));
+ mPublicKey = CryptoKey::PublicKeyFromSpki(pub, locker);
}
// Ensure that what we've read is consistent
diff --git a/dom/crypto/CryptoKey.h b/dom/crypto/CryptoKey.h
index 422c5c2a77a6..9b671d9a2e78 100644
--- a/dom/crypto/CryptoKey.h
+++ b/dom/crypto/CryptoKey.h
@@ -132,12 +132,9 @@ public:
nsresult SetPublicKey(SECKEYPublicKey* aPublicKey);
// Accessors for the keys themselves
- // Note: GetPrivateKey and GetPublicKey return copies of the internal
- // key handles, which the caller must free with SECKEY_DestroyPrivateKey
- // or SECKEY_DestroyPublicKey.
const CryptoBuffer& GetSymKey() const;
- SECKEYPrivateKey* GetPrivateKey() const;
- SECKEYPublicKey* GetPublicKey() const;
+ UniqueSECKEYPrivateKey GetPrivateKey() const;
+ UniqueSECKEYPublicKey GetPublicKey() const;
// For nsNSSShutDownObject
virtual void virtualDestroyNSSReference() override;
@@ -148,41 +145,47 @@ public:
// 1. The inputs aKeyData are non-const only because the NSS import
// functions lack the const modifier. They should not be modified.
// 2. All of the NSS key objects returned need to be freed by the caller.
- static SECKEYPrivateKey* PrivateKeyFromPkcs8(CryptoBuffer& aKeyData,
- const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static UniqueSECKEYPrivateKey PrivateKeyFromPkcs8(
+ CryptoBuffer& aKeyData,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
static nsresult PrivateKeyToPkcs8(SECKEYPrivateKey* aPrivKey,
CryptoBuffer& aRetVal,
const nsNSSShutDownPreventionLock& /*proofOfLock*/);
- static SECKEYPublicKey* PublicKeyFromSpki(CryptoBuffer& aKeyData,
- const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static UniqueSECKEYPublicKey PublicKeyFromSpki(
+ CryptoBuffer& aKeyData,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
static nsresult PublicKeyToSpki(SECKEYPublicKey* aPubKey,
CryptoBuffer& aRetVal,
const nsNSSShutDownPreventionLock& /*proofOfLock*/);
- static SECKEYPrivateKey* PrivateKeyFromJwk(const JsonWebKey& aJwk,
- const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static UniqueSECKEYPrivateKey PrivateKeyFromJwk(
+ const JsonWebKey& aJwk,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
static nsresult PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
JsonWebKey& aRetVal,
const nsNSSShutDownPreventionLock& /*proofOfLock*/);
- static SECKEYPublicKey* PublicKeyFromJwk(const JsonWebKey& aKeyData,
- const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static UniqueSECKEYPublicKey PublicKeyFromJwk(
+ const JsonWebKey& aKeyData,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
static nsresult PublicKeyToJwk(SECKEYPublicKey* aPubKey,
JsonWebKey& aRetVal,
const nsNSSShutDownPreventionLock& /*proofOfLock*/);
- static SECKEYPublicKey* PublicDhKeyFromRaw(CryptoBuffer& aKeyData,
- const CryptoBuffer& aPrime,
- const CryptoBuffer& aGenerator,
- const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static UniqueSECKEYPublicKey PublicDhKeyFromRaw(
+ CryptoBuffer& aKeyData,
+ const CryptoBuffer& aPrime,
+ const CryptoBuffer& aGenerator,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
static nsresult PublicDhKeyToRaw(SECKEYPublicKey* aPubKey,
CryptoBuffer& aRetVal,
const nsNSSShutDownPreventionLock& /*proofOfLock*/);
- static SECKEYPublicKey* PublicECKeyFromRaw(CryptoBuffer& aKeyData,
- const nsString& aNamedCurve,
- const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+ static UniqueSECKEYPublicKey PublicECKeyFromRaw(
+ CryptoBuffer& aKeyData,
+ const nsString& aNamedCurve,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/);
static nsresult PublicECKeyToRaw(SECKEYPublicKey* aPubKey,
CryptoBuffer& aRetVal,
const nsNSSShutDownPreventionLock& /*proofOfLock*/);
diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp
index 33e11cd6bdd4..5910f4c994eb 100644
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -1780,11 +1780,9 @@ private:
!mJwk.mD.WasPassed())) {
// Public key import
if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
- pubKey = UniqueSECKEYPublicKey(
- CryptoKey::PublicKeyFromSpki(mKeyData, locker));
+ pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
} else {
- pubKey = UniqueSECKEYPublicKey(
- CryptoKey::PublicKeyFromJwk(mJwk, locker));
+ pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
}
if (!pubKey) {
@@ -1801,11 +1799,9 @@ private:
mJwk.mD.WasPassed())) {
// Private key import
if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8)) {
- privKey = UniqueSECKEYPrivateKey(
- CryptoKey::PrivateKeyFromPkcs8(mKeyData, locker));
+ privKey = CryptoKey::PrivateKeyFromPkcs8(mKeyData, locker);
} else {
- privKey = UniqueSECKEYPrivateKey(
- CryptoKey::PrivateKeyFromJwk(mJwk, locker));
+ privKey = CryptoKey::PrivateKeyFromJwk(mJwk, locker);
}
if (!privKey) {
@@ -1929,8 +1925,7 @@ private:
nsNSSShutDownPreventionLock locker;
if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) && mJwk.mD.WasPassed()) {
// Private key import
- privKey = UniqueSECKEYPrivateKey(
- CryptoKey::PrivateKeyFromJwk(mJwk, locker));
+ privKey = CryptoKey::PrivateKeyFromJwk(mJwk, locker);
if (!privKey) {
return NS_ERROR_DOM_DATA_ERR;
}
@@ -1946,14 +1941,11 @@ private:
!mJwk.mD.WasPassed())) {
// Public key import
if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
- pubKey = UniqueSECKEYPublicKey(
- CryptoKey::PublicECKeyFromRaw(mKeyData, mNamedCurve, locker));
+ pubKey = CryptoKey::PublicECKeyFromRaw(mKeyData, mNamedCurve, locker);
} else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
- pubKey = UniqueSECKEYPublicKey(
- CryptoKey::PublicKeyFromSpki(mKeyData, locker));
+ pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
} else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
- pubKey = UniqueSECKEYPublicKey(
- CryptoKey::PublicKeyFromJwk(mJwk, locker));
+ pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
} else {
MOZ_ASSERT(false);
}
@@ -2087,11 +2079,10 @@ private:
mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
// Public key import
if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
- pubKey = UniqueSECKEYPublicKey(
- CryptoKey::PublicDhKeyFromRaw(mKeyData, mPrime, mGenerator, locker));
+ pubKey = CryptoKey::PublicDhKeyFromRaw(mKeyData, mPrime, mGenerator,
+ locker);
} else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
- pubKey = UniqueSECKEYPublicKey(
- CryptoKey::PublicKeyFromSpki(mKeyData, locker));
+ pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
} else {
MOZ_ASSERT(false);
}
@@ -3025,7 +3016,7 @@ public:
}
CryptoKey* publicKey = params.mPublic;
- mPubKey = UniqueSECKEYPublicKey(publicKey->GetPublicKey());
+ mPubKey = publicKey->GetPublicKey();
if (!mPubKey) {
mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
return;
@@ -3125,7 +3116,7 @@ public:
}
CryptoKey* publicKey = params.mPublic;
- mPubKey = UniqueSECKEYPublicKey(publicKey->GetPublicKey());
+ mPubKey = publicKey->GetPublicKey();
if (!mPubKey) {
mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
return;
diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp
index c8f3bb1806e5..26694e551db0 100644
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -223,6 +223,7 @@ using namespace mozilla::system;
using namespace mozilla::widget;
namespace mozilla {
+
namespace dom {
// IPC sender for remote GC/CC logging.
@@ -1353,7 +1354,20 @@ ContentChild::RecvSetProcessSandbox(const MaybeFileDesc& aBroker)
// didn't intend it.
MOZ_RELEASE_ASSERT(brokerFd >= 0);
}
- sandboxEnabled = SetContentProcessSandbox(brokerFd);
+ // Allow user overrides of seccomp-bpf syscall filtering
+ std::vector syscallWhitelist;
+ nsAdoptingCString extraSyscalls =
+ Preferences::GetCString("security.sandbox.content.syscall_whitelist");
+ if (extraSyscalls) {
+ for (const nsCSubstring& callNrString : extraSyscalls.Split(',')) {
+ nsresult rv;
+ int callNr = PromiseFlatCString(callNrString).ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ syscallWhitelist.push_back(callNr);
+ }
+ }
+ }
+ sandboxEnabled = SetContentProcessSandbox(brokerFd, syscallWhitelist);
}
#elif defined(XP_WIN)
mozilla::SandboxTarget::Instance()->StartSandbox();
diff --git a/dom/media/webrtc/RTCCertificate.cpp b/dom/media/webrtc/RTCCertificate.cpp
index d956d233db7b..79ad9ab3f9e9 100644
--- a/dom/media/webrtc/RTCCertificate.cpp
+++ b/dom/media/webrtc/RTCCertificate.cpp
@@ -220,11 +220,11 @@ private:
{
// Make copies of the private key and certificate, otherwise, when this
// object is deleted, the structures they reference will be deleted too.
- SECKEYPrivateKey* key = mKeyPair->mPrivateKey.get()->GetPrivateKey();
+ UniqueSECKEYPrivateKey key = mKeyPair->mPrivateKey.get()->GetPrivateKey();
CERTCertificate* cert = CERT_DupCertificate(mCertificate.get());
RefPtr result =
new RTCCertificate(mResultPromise->GetParentObject(),
- key, cert, mAuthType, mExpires);
+ key.release(), cert, mAuthType, mExpires);
mResultPromise->MaybeResolve(result);
}
};
@@ -416,7 +416,7 @@ RTCCertificate::ReadPrivateKey(JSStructuredCloneReader* aReader,
if (!jwk.Init(json)) {
return false;
}
- mPrivateKey.reset(CryptoKey::PrivateKeyFromJwk(jwk, aLockProof));
+ mPrivateKey = CryptoKey::PrivateKeyFromJwk(jwk, aLockProof);
return !!mPrivateKey;
}
diff --git a/editor/libeditor/EditorEventListener.cpp b/editor/libeditor/EditorEventListener.cpp
index f923d472459e..8bd639bb6fbc 100644
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -667,6 +667,13 @@ EditorEventListener::MouseClick(nsIDOMMouseEvent* aMouseEvent)
return rv;
}
+ // IMEStateManager::OnClickInEditor() may cause anything because it may
+ // set input context. For example, it may cause opening VKB, changing focus
+ // or reflow. So, mEditorBase here might have been gone.
+ if (!mEditorBase) {
+ return NS_OK;
+ }
+
// If we got a mouse down inside the editing area, we should force the
// IME to commit before we change the cursor position
mEditorBase->ForceCompositionEnd();
@@ -765,7 +772,9 @@ EditorEventListener::MouseDown(nsIDOMMouseEvent* aMouseEvent)
{
// FYI: This may be called by HTMLEditorEventListener::MouseDown() even
// when the event is not acceptable for committing composition.
- mEditorBase->ForceCompositionEnd();
+ if (mEditorBase) {
+ mEditorBase->ForceCompositionEnd();
+ }
return NS_OK;
}
diff --git a/ipc/ipdl/ipdl/lower.py b/ipc/ipdl/ipdl/lower.py
index 0accd9d6c0c5..6785a9ffe977 100644
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -9,7 +9,7 @@ from collections import OrderedDict
import ipdl.ast
import ipdl.builtin
from ipdl.cxx.ast import *
-from ipdl.type import Actor, ActorType, ProcessGraph, TypeVisitor, builtinHeaderIncludes
+from ipdl.type import ActorType, ProcessGraph, TypeVisitor, builtinHeaderIncludes
##-----------------------------------------------------------------------------
## "Public" interface to lowering
@@ -419,10 +419,6 @@ def _killProcess(pid):
ExprVar('base::PROCESS_END_KILLED_BY_USER'),
ExprLiteral.FALSE ])
-def _badTransition():
- # FIXME: make this a FatalError()
- return [ _printWarningMessage('bad state transition!') ]
-
# Results that IPDL-generated code returns back to *Channel code.
# Users never see these
class _Result:
@@ -578,10 +574,10 @@ def _cxxConstPtrToType(ipdltype, side):
return t
def _allocMethod(ptype, side):
- return ExprVar('Alloc'+ str(Actor(ptype, side)))
+ return ExprVar('Alloc' + ptype.name() + side.title())
def _deallocMethod(ptype, side):
- return ExprVar('Dealloc'+ str(Actor(ptype, side)))
+ return ExprVar('Dealloc' + ptype.name() + side.title())
##
## A _HybridDecl straddles IPDL and C++ decls. It knows which C++
diff --git a/ipc/ipdl/ipdl/type.py b/ipc/ipdl/ipdl/type.py
index 92782844dd41..0e1508809ba9 100644
--- a/ipc/ipdl/ipdl/type.py
+++ b/ipc/ipdl/ipdl/type.py
@@ -439,11 +439,6 @@ def iteractortypes(t, visited=None):
for actor in iteractortypes(c, visited):
yield actor
-def hasactor(type):
- """Return true iff |type| is an actor or has one buried within."""
- for _ in iteractortypes(type): return True
- return False
-
def hasshmem(type):
"""Return true iff |type| is shmem or has it buried within."""
class found: pass
@@ -455,17 +450,6 @@ def hasshmem(type):
return True
return False
-def hasfd(type):
- """Return true iff |type| is fd or has it buried within."""
- class found: pass
- class findFD(TypeVisitor):
- def visitFDType(self, s): raise found()
- try:
- type.accept(findFD())
- except found:
- return True
- return False
-
##--------------------
_builtinloc = Loc('', 0)
def makeBuiltinUsing(tname):
diff --git a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
index b35df42c9d58..79ce3c39cb3f 100644
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -56,8 +56,6 @@ import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.HomePanelsManager;
import org.mozilla.gecko.home.HomeScreen;
import org.mozilla.gecko.home.SearchEngine;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
import org.mozilla.gecko.icons.Icons;
import org.mozilla.gecko.javaaddons.JavaAddonManager;
import org.mozilla.gecko.media.VideoPlayer;
@@ -77,7 +75,6 @@ import org.mozilla.gecko.reader.SavedReaderViewHelper;
import org.mozilla.gecko.reader.ReaderModeUtils;
import org.mozilla.gecko.reader.ReadingListHelper;
import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.RestrictedProfileConfiguration;
import org.mozilla.gecko.restrictions.Restrictions;
import org.mozilla.gecko.search.SearchEngineManager;
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
@@ -101,7 +98,6 @@ import org.mozilla.gecko.updater.UpdateServiceHelper;
import org.mozilla.gecko.util.ActivityUtils;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.ContextUtils;
-import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.FloatUtils;
import org.mozilla.gecko.util.GamepadUtils;
@@ -146,8 +142,6 @@ import android.support.v4.app.NotificationCompat;
import android.support.v4.view.MenuItemCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Base64;
-import android.util.Base64OutputStream;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
@@ -168,14 +162,13 @@ import android.widget.Button;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.ViewFlipper;
-import com.keepsafe.switchboard.AsyncConfigLoader;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.AsyncConfigLoader;
+import org.mozilla.gecko.switchboard.SwitchBoard;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import org.json.JSONException;
import org.json.JSONObject;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -184,7 +177,6 @@ import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -656,9 +648,20 @@ public class BrowserApp extends GeckoApp
runOnUiThread(new Runnable() {
@Override
public void run() {
+ if (BrowserApp.this.isFinishing()) {
+ // TabHistoryController is rather slow - and involves calling into Gecko
+ // to retrieve tab history. That means there can be a significant
+ // delay between the back-button long-press, and onShowHistory()
+ // being called. Hence we need to guard against the Activity being
+ // shut down (in which case trying to perform UI changes, such as showing
+ // fragments below, will crash).
+ return;
+ }
+
final TabHistoryFragment fragment = TabHistoryFragment.newInstance(historyPageList, toIndex);
final FragmentManager fragmentManager = getSupportFragmentManager();
GeckoAppShell.vibrateOnHapticFeedbackEnabled(getResources().getIntArray(R.array.long_press_vibrate_msec));
+ if (BrowserApp.this.isForegrounded())
fragment.show(R.id.tab_history_panel, fragmentManager.beginTransaction(), TAB_HISTORY_FRAGMENT_TAG);
}
});
@@ -1069,6 +1072,7 @@ public class BrowserApp extends GeckoApp
@Override
public void onResume() {
super.onResume();
+
if (mIsAbortingAppLaunch) {
return;
}
diff --git a/mobile/android/base/java/org/mozilla/gecko/Experiments.java b/mobile/android/base/java/org/mozilla/gecko/Experiments.java
index 99b2222a0bd2..eb42f612a432 100644
--- a/mobile/android/base/java/org/mozilla/gecko/Experiments.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Experiments.java
@@ -9,8 +9,8 @@ import android.content.Context;
import android.util.Log;
import android.text.TextUtils;
-import com.keepsafe.switchboard.Preferences;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.Preferences;
+import org.mozilla.gecko.switchboard.SwitchBoard;
import java.util.LinkedList;
import java.util.List;
diff --git a/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStream.java b/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStream.java
index d1f26b666af4..ba6eaf795887 100644
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStream.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStream.java
@@ -8,14 +8,10 @@ package org.mozilla.gecko.activitystream;
import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
-import android.support.annotation.NonNull;
import android.text.TextUtils;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.SwitchBoard;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.Experiments;
import org.mozilla.gecko.GeckoSharedPrefs;
@@ -24,7 +20,6 @@ import org.mozilla.gecko.util.StringUtils;
import org.mozilla.gecko.util.publicsuffix.PublicSuffix;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
public class ActivityStream {
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/SyncAction.java b/mobile/android/base/java/org/mozilla/gecko/dlc/SyncAction.java
index 104bdad1839a..e8ca0d9bcbe8 100644
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/SyncAction.java
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/SyncAction.java
@@ -9,7 +9,7 @@ import android.content.Context;
import android.net.Uri;
import android.util.Log;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.SwitchBoard;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/FeedService.java b/mobile/android/base/java/org/mozilla/gecko/feeds/FeedService.java
index 3744862153ba..c42a853ef73c 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/FeedService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/feeds/FeedService.java
@@ -14,10 +14,9 @@ import android.support.annotation.Nullable;
import android.support.v4.net.ConnectivityManagerCompat;
import android.util.Log;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.SwitchBoard;
import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.feeds.action.FeedAction;
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java
index f5bf39997da7..ecf2df77caee 100644
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java
+++ b/mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java
@@ -11,7 +11,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.SwitchBoard;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.feeds.FeedAlarmReceiver;
diff --git a/mobile/android/base/java/org/mozilla/gecko/notifications/WhatsNewReceiver.java b/mobile/android/base/java/org/mozilla/gecko/notifications/WhatsNewReceiver.java
index 6e799bf742e8..9ddd60109552 100644
--- a/mobile/android/base/java/org/mozilla/gecko/notifications/WhatsNewReceiver.java
+++ b/mobile/android/base/java/org/mozilla/gecko/notifications/WhatsNewReceiver.java
@@ -15,7 +15,7 @@ import android.net.Uri;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.SwitchBoard;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.Locales;
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
index 6fb36c8eaa48..997362907d00 100644
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
@@ -9,7 +9,6 @@ import org.json.JSONArray;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.AdjustConstants;
import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.DataReportingNotification;
@@ -17,7 +16,6 @@ import org.mozilla.gecko.DynamicToolbar;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.Experiments;
import org.mozilla.gecko.GeckoActivityStatus;
-import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.GeckoProfile;
@@ -31,7 +29,6 @@ import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.TelemetryContract.Method;
import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
import org.mozilla.gecko.feeds.FeedService;
import org.mozilla.gecko.feeds.action.CheckForUpdatesAction;
@@ -48,7 +45,6 @@ import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.InputOptionsUtils;
-import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.ViewUtil;
@@ -81,16 +77,12 @@ import android.preference.TwoStatePreference;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TextInputLayout;
import android.support.v4.content.LocalBroadcastManager;
-import android.support.v4.content.res.ResourcesCompat;
-import android.support.v4.text.TextUtilsCompat;
-import android.support.v4.view.ViewCompat;
import android.support.v7.app.ActionBar;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
-import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
@@ -99,7 +91,7 @@ import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.SwitchBoard;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java b/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java
index c1eeb6bd5091..9d9ebb519423 100644
--- a/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java
+++ b/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java
@@ -12,7 +12,7 @@ import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.util.Log;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.SwitchBoard;
import org.json.JSONException;
import org.json.JSONObject;
diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/ReaderViewBookmarkPromotion.java b/mobile/android/base/java/org/mozilla/gecko/promotion/ReaderViewBookmarkPromotion.java
index db5a531c665a..6a991decbcfe 100644
--- a/mobile/android/base/java/org/mozilla/gecko/promotion/ReaderViewBookmarkPromotion.java
+++ b/mobile/android/base/java/org/mozilla/gecko/promotion/ReaderViewBookmarkPromotion.java
@@ -8,7 +8,7 @@ package org.mozilla.gecko.promotion;
import android.content.Intent;
import android.content.SharedPreferences;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.SwitchBoard;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.GeckoSharedPrefs;
diff --git a/mobile/android/thirdparty/com/keepsafe/switchboard/AsyncConfigLoader.java b/mobile/android/base/java/org/mozilla/gecko/switchboard/AsyncConfigLoader.java
similarity index 95%
rename from mobile/android/thirdparty/com/keepsafe/switchboard/AsyncConfigLoader.java
rename to mobile/android/base/java/org/mozilla/gecko/switchboard/AsyncConfigLoader.java
index 2cff4b4c3656..53f516f27c68 100644
--- a/mobile/android/thirdparty/com/keepsafe/switchboard/AsyncConfigLoader.java
+++ b/mobile/android/base/java/org/mozilla/gecko/switchboard/AsyncConfigLoader.java
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
-package com.keepsafe.switchboard;
+package org.mozilla.gecko.switchboard;
import android.content.Context;
@@ -21,10 +21,10 @@ import android.os.AsyncTask;
/**
* An async loader to load user config in background thread based on internal generated UUID.
- *
- * Call AsyncConfigLoader.execute()
to load SwitchBoard.loadConfig() with own ID.
+ *
+ * Call AsyncConfigLoader.execute()
to load SwitchBoard.loadConfig() with own ID.
* To use your custom UUID call AsyncConfigLoader.execute(uuid)
with uuid being your unique user id
- * as a String
+ * as a String
*
* @author Philipp Berner
*
diff --git a/mobile/android/thirdparty/com/keepsafe/switchboard/DeviceUuidFactory.java b/mobile/android/base/java/org/mozilla/gecko/switchboard/DeviceUuidFactory.java
similarity index 98%
rename from mobile/android/thirdparty/com/keepsafe/switchboard/DeviceUuidFactory.java
rename to mobile/android/base/java/org/mozilla/gecko/switchboard/DeviceUuidFactory.java
index c4476d2cd0b0..b81e684e696b 100644
--- a/mobile/android/thirdparty/com/keepsafe/switchboard/DeviceUuidFactory.java
+++ b/mobile/android/base/java/org/mozilla/gecko/switchboard/DeviceUuidFactory.java
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
-package com.keepsafe.switchboard;
+package org.mozilla.gecko.switchboard;
import java.util.UUID;
@@ -22,7 +22,7 @@ import android.content.SharedPreferences;
/**
* Generates a UUID and stores is persistent as in the apps shared preferences.
- *
+ *
* @author Philipp Berner
*/
public class DeviceUuidFactory {
diff --git a/mobile/android/thirdparty/com/keepsafe/switchboard/Preferences.java b/mobile/android/base/java/org/mozilla/gecko/switchboard/Preferences.java
similarity index 98%
rename from mobile/android/thirdparty/com/keepsafe/switchboard/Preferences.java
rename to mobile/android/base/java/org/mozilla/gecko/switchboard/Preferences.java
index f7f6f7cb781b..d2247be0462a 100644
--- a/mobile/android/thirdparty/com/keepsafe/switchboard/Preferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/switchboard/Preferences.java
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
-package com.keepsafe.switchboard;
+package org.mozilla.gecko.switchboard;
import android.content.Context;
diff --git a/mobile/android/thirdparty/com/keepsafe/switchboard/Switch.java b/mobile/android/base/java/org/mozilla/gecko/switchboard/Switch.java
similarity index 97%
rename from mobile/android/thirdparty/com/keepsafe/switchboard/Switch.java
rename to mobile/android/base/java/org/mozilla/gecko/switchboard/Switch.java
index 5307750bbc99..7fdc8cb61a8c 100644
--- a/mobile/android/thirdparty/com/keepsafe/switchboard/Switch.java
+++ b/mobile/android/base/java/org/mozilla/gecko/switchboard/Switch.java
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
-package com.keepsafe.switchboard;
+package org.mozilla.gecko.switchboard;
import org.json.JSONObject;
@@ -21,7 +21,7 @@ import android.content.Context;
/**
* Single instance of an existing experiment for easier and cleaner code.
- *
+ *
* @author Philipp Berner
*
*/
@@ -64,7 +64,7 @@ public class Switch {
* @return Values in JSONObject or null if non
*/
public JSONObject getValues() {
- if(hasValues())
+ if (hasValues())
return SwitchBoard.getExperimentValuesFromJson(context, experimentName);
else
return null;
diff --git a/mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java b/mobile/android/base/java/org/mozilla/gecko/switchboard/SwitchBoard.java
similarity index 96%
rename from mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java
rename to mobile/android/base/java/org/mozilla/gecko/switchboard/SwitchBoard.java
index e99144045c9a..2e917596ccda 100644
--- a/mobile/android/thirdparty/com/keepsafe/switchboard/SwitchBoard.java
+++ b/mobile/android/base/java/org/mozilla/gecko/switchboard/SwitchBoard.java
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
-package com.keepsafe.switchboard;
+package org.mozilla.gecko.switchboard;
import java.io.BufferedReader;
import java.io.IOException;
@@ -23,7 +23,6 @@ import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
@@ -43,18 +42,18 @@ import android.util.Log;
/**
* SwitchBoard is the core class of the KeepSafe Switchboard mobile A/B testing framework.
- * This class provides a bunch of static methods that can be used in your app to run A/B tests.
- *
- * The SwitchBoard supports production and staging environment.
- *
+ * This class provides a bunch of static methods that can be used in your app to run A/B tests.
+ *
+ * The SwitchBoard supports production and staging environment.
+ *
* For usage initDefaultServerUrls
for first time usage. Server URLs can be updates from
* a remote location with initConfigServerUrl
.
- *
- * To run a experiment use isInExperiment()
. The experiment name has to match the one you
+ *
+ * To run a experiment use isInExperiment()
. The experiment name has to match the one you
* setup on the server.
- * All functions are design to be safe for programming mistakes and network connection issues. If the
+ * All functions are design to be safe for programming mistakes and network connection issues. If the
* experiment does not exists it will return false and pretend the user is not part of it.
- *
+ *
* @author Philipp Berner
*
*/
@@ -223,7 +222,7 @@ public class SwitchBoard {
if (!country.matches(matchKeys.getString(KEY_COUNTRY))) {
return false;
}
- } catch (MissingResourceException|JSONException e) {
+ } catch (MissingResourceException | JSONException e) {
Log.e(TAG, "Exception matching country", e);
}
}
@@ -245,7 +244,7 @@ public class SwitchBoard {
if (!lang.matches(matchKeys.getString(KEY_LANG))) {
return false;
}
- } catch (MissingResourceException|JSONException e) {
+ } catch (MissingResourceException | JSONException e) {
Log.e(TAG, "Exception matching lang", e);
}
}
@@ -266,7 +265,7 @@ public class SwitchBoard {
if (!version.matches(matchKeys.getString(KEY_VERSION))) {
return false;
}
- } catch (NameNotFoundException|JSONException e) {
+ } catch (NameNotFoundException | JSONException e) {
Log.e(TAG, "Exception matching version", e);
}
}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryFragment.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryFragment.java
index ff2a6aac0e6c..e6deabdcf971 100644
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryFragment.java
@@ -124,7 +124,10 @@ public class TabHistoryFragment extends Fragment implements OnItemClickListener,
dismissed = false;
transaction.add(containerViewId, this, tag);
transaction.addToBackStack(tag);
- backStackId = transaction.commit();
+ // Populating the tab history requires a gecko call (which can be slow) - therefore the app
+ // state by the time we try to show this fragment is unknown, and we could be in the
+ // middle of shutting down:
+ backStackId = transaction.commitAllowingStateLoss();
}
// Pop the fragment from backstack if it exists.
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java
index fdab37ff04c8..27e77cdea273 100644
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java
@@ -6,7 +6,7 @@
package org.mozilla.gecko.tabs;
import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.AppConstants.Versions;
+
import org.mozilla.gecko.Experiments;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoApplication;
@@ -44,7 +44,7 @@ import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.SwitchBoard;
import org.mozilla.gecko.widget.themed.ThemedImageButton;
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
index d1bed759fac3..d77366939733 100644
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
@@ -44,7 +44,7 @@ import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
-import com.keepsafe.switchboard.SwitchBoard;
+import org.mozilla.gecko.switchboard.SwitchBoard;
/**
* {@code ToolbarDisplayLayout} is the UI for when the toolbar is in
diff --git a/mobile/android/base/java/org/mozilla/gecko/updater/PostUpdateHandler.java b/mobile/android/base/java/org/mozilla/gecko/updater/PostUpdateHandler.java
index f0ad78e776d0..3b8e370b57bb 100644
--- a/mobile/android/base/java/org/mozilla/gecko/updater/PostUpdateHandler.java
+++ b/mobile/android/base/java/org/mozilla/gecko/updater/PostUpdateHandler.java
@@ -5,13 +5,10 @@
package org.mozilla.gecko.updater;
-import android.content.Context;
import android.content.res.AssetManager;
import android.content.SharedPreferences;
import android.util.Log;
-import com.keepsafe.switchboard.SwitchBoard;
-
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.delegates.BrowserAppDelegateWithReference;
@@ -21,14 +18,10 @@ import org.mozilla.gecko.util.IOUtils;
import org.mozilla.gecko.util.ThreadUtils;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.util.Enumeration;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
/**
* Perform tasks in the background after the app has been installed/updated.
diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build
index 88b24aeed91f..88e6253cbb29 100644
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -702,6 +702,11 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'SiteIdentity.java',
'SnackbarBuilder.java',
'SuggestClient.java',
+ 'switchboard/AsyncConfigLoader.java',
+ 'switchboard/DeviceUuidFactory.java',
+ 'switchboard/Preferences.java',
+ 'switchboard/Switch.java',
+ 'switchboard/SwitchBoard.java',
'Tab.java',
'tabqueue/TabQueueHelper.java',
'tabqueue/TabQueuePrompt.java',
@@ -988,11 +993,6 @@ gtjar.sources += [ thirdparty_source_dir + f for f in [
'com/jakewharton/disklrucache/DiskLruCache.java',
'com/jakewharton/disklrucache/StrictLineReader.java',
'com/jakewharton/disklrucache/Util.java',
- 'com/keepsafe/switchboard/AsyncConfigLoader.java',
- 'com/keepsafe/switchboard/DeviceUuidFactory.java',
- 'com/keepsafe/switchboard/Preferences.java',
- 'com/keepsafe/switchboard/Switch.java',
- 'com/keepsafe/switchboard/SwitchBoard.java',
'com/squareup/leakcanary/LeakCanary.java',
'com/squareup/leakcanary/RefWatcher.java',
'com/squareup/picasso/Action.java',
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js
index 56e8cb60dfd7..1538b0f962ff 100644
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1388,7 +1388,7 @@ var BrowserApp = {
quit: function quit(aClear = { sanitize: {}, dontSaveSession: false }) {
// Notify all windows that an application quit has been requested.
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
- Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null);
// Quit aborted.
if (cancelQuit.data) {
@@ -5536,6 +5536,7 @@ var XPInstallObserver = {
// If nothing aborted, quit the app
if (cancelQuit.data == false) {
+ Services.obs.notifyObservers(null, "quit-application-proceeding", null);
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
appStartup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
}
diff --git a/mobile/android/components/SessionStore.js b/mobile/android/components/SessionStore.js
index 86d2e34c3a0c..a5181a9c7389 100644
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -1002,10 +1002,12 @@ SessionStore.prototype = {
// If we have private data, send it to Java; otherwise, send null to
// indicate that there is no private data
let window = Services.wm.getMostRecentWindow("navigator:browser");
- window.WindowEventDispatcher.sendRequest({
- type: "PrivateBrowsing:Data",
- session: (privateData.windows.length > 0 && privateData.windows[0].tabs.length > 0) ? JSON.stringify(privateData) : null
- });
+ if (window) { // can be null if we're restarting
+ window.WindowEventDispatcher.sendRequest({
+ type: "PrivateBrowsing:Data",
+ session: (privateData.windows.length > 0 && privateData.windows[0].tabs.length > 0) ? JSON.stringify(privateData) : null
+ });
+ }
this._lastSaveTime = Date.now();
},
diff --git a/mobile/android/tests/background/junit4/src/com/keepsafe/switchboard/TestSwitchboard.java b/mobile/android/tests/background/junit4/src/com/keepsafe/switchboard/TestSwitchboard.java
index 5726f12db003..71de1c346a9a 100644
--- a/mobile/android/tests/background/junit4/src/com/keepsafe/switchboard/TestSwitchboard.java
+++ b/mobile/android/tests/background/junit4/src/com/keepsafe/switchboard/TestSwitchboard.java
@@ -9,6 +9,9 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.Experiments;
+import org.mozilla.gecko.switchboard.DeviceUuidFactory;
+import org.mozilla.gecko.switchboard.Preferences;
+import org.mozilla.gecko.switchboard.SwitchBoard;
import org.mozilla.gecko.util.IOUtils;
import org.robolectric.RuntimeEnvironment;
diff --git a/security/sandbox/linux/Sandbox.cpp b/security/sandbox/linux/Sandbox.cpp
index 7f1182be9972..bf2b0bc6d95c 100644
--- a/security/sandbox/linux/Sandbox.cpp
+++ b/security/sandbox/linux/Sandbox.cpp
@@ -30,6 +30,7 @@
#include
#include
+#include
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/SandboxInfo.h"
@@ -634,7 +635,7 @@ SandboxEarlyInit(GeckoProcessType aType)
* Will normally make the process exit on failure.
*/
bool
-SetContentProcessSandbox(int aBrokerFd)
+SetContentProcessSandbox(int aBrokerFd, std::vector& aSyscallWhitelist)
{
if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForContent)) {
if (aBrokerFd >= 0) {
@@ -649,7 +650,8 @@ SetContentProcessSandbox(int aBrokerFd)
sBroker.emplace(aBrokerFd);
}
- SetCurrentProcessSandbox(GetContentSandboxPolicy(sBroker.ptrOr(nullptr)));
+ SetCurrentProcessSandbox(GetContentSandboxPolicy(sBroker.ptrOr(nullptr),
+ aSyscallWhitelist));
return true;
}
#endif // MOZ_CONTENT_SANDBOX
diff --git a/security/sandbox/linux/Sandbox.h b/security/sandbox/linux/Sandbox.h
index 94b26e25ba3b..6f2d4ae44e22 100644
--- a/security/sandbox/linux/Sandbox.h
+++ b/security/sandbox/linux/Sandbox.h
@@ -24,7 +24,8 @@ MOZ_EXPORT void SandboxEarlyInit(GeckoProcessType aType);
// (No-op if MOZ_DISABLE_CONTENT_SANDBOX is set.)
// aBrokerFd is the filesystem broker client file descriptor,
// or -1 to allow direct filesystem access.
-MOZ_EXPORT bool SetContentProcessSandbox(int aBrokerFd);
+MOZ_EXPORT bool SetContentProcessSandbox(int aBrokerFd,
+ std::vector& aSyscallWhitelist);
#endif
#ifdef MOZ_GMP_SANDBOX
diff --git a/security/sandbox/linux/SandboxFilter.cpp b/security/sandbox/linux/SandboxFilter.cpp
index 018e9c64446c..58d3b18e7a0c 100644
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -11,7 +11,6 @@
#include "SandboxInfo.h"
#include "SandboxInternal.h"
#include "SandboxLogging.h"
-
#include "mozilla/UniquePtr.h"
#include
@@ -26,6 +25,8 @@
#include
#include
#include
+#include
+#include
#include "sandbox/linux/bpf_dsl/bpf_dsl.h"
#include "sandbox/linux/system_headers/linux_seccomp.h"
@@ -347,7 +348,9 @@ public:
// this is the Android process permission model; on desktop,
// namespaces and chroot() will be used.
class ContentSandboxPolicy : public SandboxPolicyCommon {
+private:
SandboxBrokerClient* mBroker;
+ std::vector mSyscallWhitelist;
// Trap handlers for filesystem brokering.
// (The amount of code duplication here could be improved....)
@@ -497,7 +500,10 @@ class ContentSandboxPolicy : public SandboxPolicyCommon {
}
public:
- explicit ContentSandboxPolicy(SandboxBrokerClient* aBroker):mBroker(aBroker) { }
+ explicit ContentSandboxPolicy(SandboxBrokerClient* aBroker,
+ const std::vector& aSyscallWhitelist)
+ : mBroker(aBroker),
+ mSyscallWhitelist(aSyscallWhitelist) {}
virtual ~ContentSandboxPolicy() { }
virtual ResultExpr PrctlPolicy() const override {
// Ideally this should be restricted to a whitelist, but content
@@ -570,6 +576,14 @@ public:
#endif
virtual ResultExpr EvaluateSyscall(int sysno) const override {
+ // Straight allow for anything that got overriden via prefs
+ if (std::find(mSyscallWhitelist.begin(), mSyscallWhitelist.end(), sysno)
+ != mSyscallWhitelist.end()) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("Allowing syscall nr %d via whitelist", sysno);
+ }
+ return Allow();
+ }
if (mBroker) {
// Have broker; route the appropriate syscalls to it.
switch (sysno) {
@@ -834,9 +848,10 @@ public:
};
UniquePtr
-GetContentSandboxPolicy(SandboxBrokerClient* aMaybeBroker)
+GetContentSandboxPolicy(SandboxBrokerClient* aMaybeBroker,
+ const std::vector& aSyscallWhitelist)
{
- return UniquePtr(new ContentSandboxPolicy(aMaybeBroker));
+ return MakeUnique(aMaybeBroker, aSyscallWhitelist);
}
#endif // MOZ_CONTENT_SANDBOX
diff --git a/security/sandbox/linux/SandboxFilter.h b/security/sandbox/linux/SandboxFilter.h
index 6b1cb47f4f6a..67f82b0d56ed 100644
--- a/security/sandbox/linux/SandboxFilter.h
+++ b/security/sandbox/linux/SandboxFilter.h
@@ -7,6 +7,7 @@
#ifndef mozilla_SandboxFilter_h
#define mozilla_SandboxFilter_h
+#include
#include "mozilla/Atomics.h"
#include "mozilla/UniquePtr.h"
@@ -21,7 +22,8 @@ namespace mozilla {
#ifdef MOZ_CONTENT_SANDBOX
class SandboxBrokerClient;
-UniquePtr GetContentSandboxPolicy(SandboxBrokerClient* aMaybeBroker);
+UniquePtr GetContentSandboxPolicy(SandboxBrokerClient* aMaybeBroker,
+ const std::vector& aSyscallWhitelist);
#endif
#ifdef MOZ_GMP_SANDBOX
diff --git a/security/sandbox/linux/broker/SandboxBroker.cpp b/security/sandbox/linux/broker/SandboxBroker.cpp
index a31d1fc6684a..302be04746e5 100644
--- a/security/sandbox/linux/broker/SandboxBroker.cpp
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -211,22 +211,36 @@ SandboxBroker::Policy::AddDir(int aPerms, const char* aPath)
if (path[path.Length() - 1] != '/') {
path.Append('/');
}
+
+ Policy::AddPrefixInternal(aPerms, path);
+}
+
+void
+SandboxBroker::Policy::AddPrefix(int aPerms, const char* aPath)
+{
+ Policy::AddPrefixInternal(aPerms, nsDependentCString(aPath));
+}
+
+void
+SandboxBroker::Policy::AddPrefixInternal(int aPerms, const nsACString& aPath)
+{
int origPerms;
- if (!mMap.Get(path, &origPerms)) {
+ if (!mMap.Get(aPath, &origPerms)) {
origPerms = MAY_ACCESS;
} else {
MOZ_ASSERT(origPerms & MAY_ACCESS);
}
int newPerms = origPerms | aPerms | RECURSIVE;
if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
- SANDBOX_LOG_ERROR("policy for %s: %d -> %d", aPath, origPerms, newPerms);
+ SANDBOX_LOG_ERROR("policy for %s: %d -> %d", PromiseFlatCString(aPath).get(),
+ origPerms, newPerms);
}
- mMap.Put(path, newPerms);
+ mMap.Put(aPath, newPerms);
}
void
-SandboxBroker::Policy::AddPrefix(int aPerms, const char* aDir,
- const char* aPrefix)
+SandboxBroker::Policy::AddFilePrefix(int aPerms, const char* aDir,
+ const char* aPrefix)
{
size_t prefixLen = strlen(aPrefix);
DIR* dirp = opendir(aDir);
@@ -246,6 +260,25 @@ SandboxBroker::Policy::AddPrefix(int aPerms, const char* aDir,
closedir(dirp);
}
+void
+SandboxBroker::Policy::AddDynamic(int aPerms, const char* aPath)
+{
+ struct stat statBuf;
+ bool exists = (stat(aPath, &statBuf) == 0);
+
+ if (!exists) {
+ AddPrefix(aPerms, aPath);
+ } else {
+ size_t len = strlen(aPath);
+ if (!len) return;
+ if (aPath[len - 1] == '/') {
+ AddDir(aPerms, aPath);
+ } else {
+ AddPath(aPerms, aPath);
+ }
+ }
+}
+
int
SandboxBroker::Policy::Lookup(const nsACString& aPath) const
{
diff --git a/security/sandbox/linux/broker/SandboxBroker.h b/security/sandbox/linux/broker/SandboxBroker.h
index bb4570a64b2e..7012451175ad 100644
--- a/security/sandbox/linux/broker/SandboxBroker.h
+++ b/security/sandbox/linux/broker/SandboxBroker.h
@@ -80,7 +80,12 @@ class SandboxBroker final
// added after creation (the dir itself must exist).
void AddDir(int aPerms, const char* aPath);
// All files in a directory with a given prefix; useful for devices.
- void AddPrefix(int aPerms, const char* aDir, const char* aPrefix);
+ void AddFilePrefix(int aPerms, const char* aDir, const char* aPrefix);
+ // Everything starting with the given path, even those files/dirs
+ // added after creation. The file or directory may or may not exist.
+ void AddPrefix(int aPerms, const char* aPath);
+ // Adds a file or dir (end with /) if it exists, and a prefix otherwhise.
+ void AddDynamic(int aPerms, const char* aPath);
// Default: add file if it exists when creating policy or if we're
// conferring permission to create it (log files, etc.).
void AddPath(int aPerms, const char* aPath) {
@@ -98,6 +103,7 @@ class SandboxBroker final
// * No trailing slash
// * No /../ path traversal
bool ValidatePath(const char* path) const;
+ void AddPrefixInternal(int aPerms, const nsACString& aPath);
};
// Constructing a broker involves creating a socketpair and a
diff --git a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
index a985d257a2c6..d3e002f66614 100644
--- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -6,6 +6,7 @@
#include "SandboxBrokerPolicyFactory.h"
#include "SandboxInfo.h"
+#include "SandboxLogging.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
@@ -67,7 +68,7 @@ SandboxBrokerPolicyFactory::SandboxBrokerPolicyFactory()
// Graphics devices are a significant source of attack surface, but
// there's not much we can do about it without proxying (which is
// very difficult and a perforamnce hit).
- policy->AddPrefix(rdwr, "/dev", "kgsl"); // bug 995072
+ policy->AddFilePrefix(rdwr, "/dev", "kgsl"); // bug 995072
policy->AddPath(rdwr, "/dev/qemu_pipe"); // but 1198410: goldfish gralloc.
// Bug 1198475: mochitest logs. (This is actually passed in via URL
@@ -138,9 +139,9 @@ SandboxBrokerPolicyFactory::SandboxBrokerPolicyFactory()
}
// Bug 1308851: NVIDIA proprietary driver when using WebGL
- policy->AddPrefix(rdwr, "/dev", "nvidia");
+ policy->AddFilePrefix(rdwr, "/dev", "nvidia");
- // Bug 1312678: radeonsi/Intel with DRI when using WebGL
+ // Bug 1312678: radeonsi/Intel with DRI when using WebGL
policy->AddDir(rdwr, "/dev/dri");
#ifdef MOZ_ALSA
@@ -190,6 +191,20 @@ SandboxBrokerPolicyFactory::GetContentPolicy(int aPid)
#else
UniquePtr
policy(new SandboxBroker::Policy(*mCommonContentPolicy));
+
+ // Now read any extra paths, this requires accessing user preferences
+ // so we can only do it now. Our constructor is initialized before
+ // user preferences are read in.
+ nsAdoptingCString extraPathString =
+ Preferences::GetCString("security.sandbox.content.write_path_whitelist");
+ if (extraPathString) {
+ for (const nsCSubstring& path : extraPathString.Split(',')) {
+ nsCString trimPath(path);
+ trimPath.Trim(" ", true, true);
+ policy->AddDynamic(rdwr, trimPath.get());
+ }
+ }
+
// Return the common policy.
return policy;
#endif
diff --git a/taskcluster/ci/build/linux.yml b/taskcluster/ci/build/linux.yml
index 7193cb1d968b..145d5af37d07 100644
--- a/taskcluster/ci/build/linux.yml
+++ b/taskcluster/ci/build/linux.yml
@@ -327,6 +327,7 @@ linux64-ccov/opt:
index:
product: firefox
job-name: linux64-ccov-opt
+ needs-sccache: false
treeherder:
platform: linux64/ccov
symbol: tc(B)
diff --git a/taskcluster/taskgraph/transforms/task.py b/taskcluster/taskgraph/transforms/task.py
index e638db178ee8..bde80475e5bf 100644
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -459,6 +459,8 @@ def build_docker_worker_payload(config, task, task_def):
level=config.params['level'])
)
worker['env']['USE_SCCACHE'] = '1'
+ else:
+ worker['env']['SCCACHE_DISABLE'] = '1'
capabilities = {}
diff --git a/testing/firefox-ui/harness/firefox_ui_harness/testcases.py b/testing/firefox-ui/harness/firefox_ui_harness/testcases.py
index 77aa85f5c2cd..36e6b1b72de2 100644
--- a/testing/firefox-ui/harness/firefox_ui_harness/testcases.py
+++ b/testing/firefox-ui/harness/firefox_ui_harness/testcases.py
@@ -52,10 +52,26 @@ class UpdateTestCase(PuppeteerMixin, MarionetteTestCase):
self.software_update = SoftwareUpdate(self.marionette)
+ # If a custom update channel has to be set, force a restart of
+ # Firefox to actually get it applied as a default pref. Use the clean
+ # option to force a non in_app restart, which would allow Firefox to
+ # dump the logs to the console.
+ if self.update_channel:
+ self.software_update.update_channel = self.update_channel
+ self.restart(clean=True)
+
+ self.assertEqual(self.software_update.update_channel, self.update_channel)
+
# If requested modify the list of allowed MAR channels
if self.update_mar_channels:
self.software_update.mar_channels.add_channels(self.update_mar_channels)
+ self.assertTrue(self.update_mar_channels.issubset(
+ self.software_update.mar_channels.channels),
+ 'Allowed MAR channels have been set: expected "{}" in "{}"'.format(
+ ', '.join(self.update_mar_channels),
+ ', '.join(self.software_update.mar_channels.channels)))
+
# Ensure that there exists no already partially downloaded update
self.remove_downloaded_update()
@@ -70,12 +86,6 @@ class UpdateTestCase(PuppeteerMixin, MarionetteTestCase):
'success': False,
}]
- self.assertTrue(self.update_mar_channels.issubset(
- self.software_update.mar_channels.channels),
- 'Allowed MAR channels have been set: expected "{}" in "{}"'.format(
- ', '.join(self.update_mar_channels),
- ', '.join(self.software_update.mar_channels.channels)))
-
# Check if the user has permissions to run the update
self.assertTrue(self.software_update.allowed,
'Current user has permissions to update the application.')
@@ -349,8 +359,6 @@ class UpdateTestCase(PuppeteerMixin, MarionetteTestCase):
def set_preferences_defaults(self):
"""Set the default value for specific preferences to force its usage."""
- if self.update_channel:
- self.software_update.update_channel = self.update_channel
if self.update_url:
self.software_update.update_url = self.update_url
diff --git a/testing/firefox-ui/tests/puppeteer/test_software_update.py b/testing/firefox-ui/tests/puppeteer/test_software_update.py
index acb4a548ba70..04ca4a1f4a3e 100644
--- a/testing/firefox-ui/tests/puppeteer/test_software_update.py
+++ b/testing/firefox-ui/tests/puppeteer/test_software_update.py
@@ -17,14 +17,11 @@ class TestSoftwareUpdate(PuppeteerMixin, MarionetteTestCase):
self.software_update = SoftwareUpdate(self.marionette)
self.saved_mar_channels = self.software_update.mar_channels.channels
- self.saved_update_channel = self.software_update.update_channel
-
self.software_update.mar_channels.channels = set(['expected', 'channels'])
def tearDown(self):
try:
self.software_update.mar_channels.channels = self.saved_mar_channels
- self.software_update.update_channel = self.saved_update_channel
finally:
super(TestSoftwareUpdate, self).tearDown()
@@ -71,10 +68,36 @@ class TestSoftwareUpdate(PuppeteerMixin, MarionetteTestCase):
def test_staging_directory(self):
self.assertTrue(self.software_update.staging_directory)
- def test_set_update_channel(self):
- self.software_update.update_channel = 'new_channel'
- self.assertEqual(self.marionette.get_pref('app.update.channel', default_branch=True),
- 'new_channel')
+
+class TestUpdateChannel(PuppeteerMixin, MarionetteTestCase):
+
+ def setUp(self):
+ super(TestUpdateChannel, self).setUp()
+
+ self.software_update = SoftwareUpdate(self.marionette)
+
+ self.saved_channel = self.software_update.update_channel
+ self.software_update.update_channel = 'expected_channel'
+
+ def tearDown(self):
+ try:
+ self.software_update.update_channel = self.saved_channel
+ finally:
+ super(TestUpdateChannel, self).tearDown()
+
+ def test_update_channel_default_channel(self):
+ # Without a restart the update channel will not change.
+ self.assertEqual(self.software_update.update_channel, self.saved_channel)
+
+ def test_update_channel_set_channel(self):
+ try:
+ # Use the clean option to force a non in_app restart, which would allow
+ # Firefox to dump the logs to the console.
+ self.restart(clean=True)
+ self.assertEqual(self.software_update.update_channel, 'expected_channel')
+ finally:
+ self.software_update.update_channel = self.saved_channel
+ self.restart(clean=True)
class TestMARChannels(PuppeteerMixin, MarionetteTestCase):
diff --git a/testing/marionette/client/marionette_driver/geckoinstance.py b/testing/marionette/client/marionette_driver/geckoinstance.py
index f457b6b5e436..2cfdf8d5f830 100644
--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -386,10 +386,8 @@ class DesktopInstance(GeckoInstance):
# Do not show the EULA notification which can interfer with tests
"browser.EULA.override": True,
- # Bug 1145668, 1312674
- # Turn off once Marionette can correctly handle error pages, and doesn't
- # hang when about:blank gets loaded twice
- "browser.newtabpage.enabled": True,
+ # Turn off about:newtab and make use of about:blank instead for new opened tabs
+ "browser.newtabpage.enabled": False,
# Assume the about:newtab page"s intro panels have been shown to not depend on
# which test runs first and happens to open about:newtab
"browser.newtabpage.introShown": True,
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
index 1ec57aed1d26..b82e7674ed5c 100644
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
@@ -158,13 +158,18 @@ class TestNavigate(WindowManagerMixin, MarionetteTestCase):
self.assertTrue(self.marionette.execute_script(
"return window.visited", sandbox=None))
- @skip_if_mobile("Fennec doesn't support other chrome windows")
+ @skip_if_mobile("Bug 1334095 - Timeout: No new tab has been opened")
def test_about_blank_for_new_docshell(self):
""" Bug 1312674 - Hang when loading about:blank for a new docshell."""
- # Open a window to get a new docshell created for the first tab
- with self.marionette.using_context("chrome"):
- tab = self.open_tab(lambda: self.marionette.execute_script(" window.open() "))
- self.marionette.switch_to_window(tab)
+ def open_with_link():
+ link = self.marionette.find_element(By.ID, "new-blank-tab")
+ link.click()
+
+ # Open a new tab to get a new docshell created
+ self.marionette.navigate(self.marionette.absolute_url("windowHandles.html"))
+ new_tab = self.open_tab(trigger=open_with_link)
+ self.marionette.switch_to_window(new_tab)
+ self.assertEqual(self.marionette.get_url(), "about:blank")
self.marionette.navigate('about:blank')
self.marionette.close()
diff --git a/testing/marionette/harness/marionette_harness/www/windowHandles.html b/testing/marionette/harness/marionette_harness/www/windowHandles.html
index 184c1f955169..bcd0b08dc375 100644
--- a/testing/marionette/harness/marionette_harness/www/windowHandles.html
+++ b/testing/marionette/harness/marionette_harness/www/windowHandles.html
@@ -8,7 +8,9 @@
Marionette New Tab Link
- Click me!
- Click me!
+ New Tab
+ New blank Tab
+
+ New Window