Merge latest green inbound changeset and mozilla-central

This commit is contained in:
Ed Morley 2014-03-03 14:44:44 +00:00
commit 30446e5cc4
84 changed files with 1313 additions and 444 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1392852504000">
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1393524054000">
<emItems>
<emItem blockID="i454" id="sqlmoz@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
@ -766,7 +766,7 @@
<prefs>
</prefs>
</emItem>
<emItem blockID="i394" id="{7D4F1959-3F72-49d5-8E59-F02F8AA6815D}">
<emItem blockID="i562" id="iobitapps@mybrowserbar.com">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
@ -921,6 +921,12 @@
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i560" id="adsremoval@adsremoval.net">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i461" id="{8E9E3331-D360-4f87-8803-52DE43566502}">
<versionRange minVersion="0" maxVersion="*" severity="1">
@ -1423,6 +1429,12 @@
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i394" id="{7D4F1959-3F72-49d5-8E59-F02F8AA6815D}">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i165" id="{EEF73632-A085-4fd3-A778-ECD82C8CB297}">
<versionRange minVersion="0" maxVersion="*" severity="3">
@ -2033,6 +2045,12 @@
</targetApplication>
</versionRange>
</pluginItem>
<pluginItem blockID="p556">
<match name="filename" exp="npUnity3D32\.dll" /> <versionRange minVersion="0" maxVersion="4.3.4.99999999" severity="0" vulnerabilitystatus="1"></versionRange>
</pluginItem>
<pluginItem blockID="p558">
<match name="description" exp="^($|Unity Web Player version ([0-3]|(4\.([0-2]|3(\.[0-4])?[^0-9.]))))" /> <match name="filename" exp="Unity Web Player\.plugin" /> <versionRange severity="0" vulnerabilitystatus="1"></versionRange>
</pluginItem>
</pluginItems>
<gfxItems>

File diff suppressed because one or more lines are too long

View File

@ -261,6 +261,11 @@ toolbarpaletteitem > #personal-bookmarks > #PlacesToolbar,
display: none;
}
#PlacesToolbarDropIndicatorHolder {
position: absolute;
top: 25%;
}
toolbarpaletteitem > #personal-bookmarks > #bookmarks-toolbar-placeholder,
#personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder,
#personal-bookmarks[cui-areatype="toolbar"].overflowedItem > #bookmarks-toolbar-placeholder {

View File

@ -139,6 +139,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader",
"resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
XPCOMUtils.defineLazyModuleGetter(this, "gCustomizationTabPreloader",
"resource:///modules/CustomizationTabPreloader.jsm", "CustomizationTabPreloader");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");

View File

@ -691,6 +691,8 @@
<image id="mixed-content-blocked-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="webRTC-shareMicrophone-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="webRTC-sharingMicrophone-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="servicesInstall-notification-icon" class="notification-anchor-icon" role="button"/>
</box>
@ -946,7 +948,7 @@
tooltip="bhTooltip"
popupsinherittooltip="true">
<hbox flex="1">
<hbox align="center">
<hbox id="PlacesToolbarDropIndicatorHolder" align="center">
<image id="PlacesToolbarDropIndicator"
mousethrough="always"
collapsed="true"/>

View File

@ -38,18 +38,18 @@
#endif
/>
<checkbox label="&engine.bookmarks.label;"
accesskey="&engine.bookmarks.accesskey;"
preference="engine.bookmarks"/>
<checkbox label="&engine.history.label;"
accesskey="&engine.history.accesskey;"
preference="engine.history"/>
<checkbox label="&engine.tabs.label;"
accesskey="&engine.tabs.accesskey;"
preference="engine.tabs"/>
<checkbox label="&engine.bookmarks.label;"
accesskey="&engine.bookmarks.accesskey;"
preference="engine.bookmarks"/>
<checkbox label="&engine.passwords.label;"
accesskey="&engine.passwords.accesskey;"
preference="engine.passwords"/>
<checkbox label="&engine.history.label;"
accesskey="&engine.history.accesskey;"
preference="engine.history"/>
<checkbox label="&engine.addons.label;"
accesskey="&engine.addons.accesskey;"
preference="engine.addons"/>

View File

@ -1594,6 +1594,8 @@
!PrivateBrowsingUtils.isWindowPrivate(window) &&
!gMultiProcessBrowser) {
docShellsSwapped = gBrowserNewTabPreloader.newTab(t);
} else if (aURI == "about:customizing") {
docShellsSwapped = gCustomizationTabPreloader.newTab(t);
}
// Dispatch a new tab notification. We do this once we're

View File

@ -213,7 +213,11 @@ let gTests = [
});
yield promisePopupNotification("webRTC-shareDevices", true);
is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
"webRTC-shareDevices-notification-icon", "anchored to device icon");
checkDeviceSelectors(true, true);
is(PopupNotifications.panel.firstChild.getAttribute("popupid"),
"webRTC-shareDevices", "panel using devices icon");
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
@ -237,7 +241,11 @@ let gTests = [
});
yield promisePopupNotification("webRTC-shareDevices", true);
is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
"webRTC-shareMicrophone-notification-icon", "anchored to mic icon");
checkDeviceSelectors(true);
is(PopupNotifications.panel.firstChild.getAttribute("popupid"),
"webRTC-shareMicrophone", "panel using microphone icon");
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
@ -260,7 +268,11 @@ let gTests = [
});
yield promisePopupNotification("webRTC-shareDevices", true);
is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
"webRTC-shareDevices-notification-icon", "anchored to device icon");
checkDeviceSelectors(false, true);
is(PopupNotifications.panel.firstChild.getAttribute("popupid"),
"webRTC-shareDevices", "panel using devices icon");
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
@ -668,8 +680,16 @@ let gTests = [
expectNotification("recording-device-events");
yield checkSharingUI();
// Stop sharing.
PopupNotifications.getNotification("webRTC-sharingDevices").reshow();
let expectedIcon = "webRTC-sharingDevices";
if (aRequestAudio && !aRequestVideo)
expectedIcon = "webRTC-sharingMicrophone";
is(PopupNotifications.getNotification("webRTC-sharingDevices").anchorID,
expectedIcon + "-notification-icon", "anchored to correct icon");
is(PopupNotifications.panel.firstChild.getAttribute("popupid"), expectedIcon,
"panel using correct icon");
// Stop sharing.
activateSecondaryAction(kActionDeny);
yield promiseNotification("recording-device-events");

View File

@ -129,11 +129,8 @@ add_task(function MoveWidgetsInTwoWindows() {
checkPalette(widgetId, method);
}
}
otherWin.close();
yield promiseWindowClosed(otherWin);
otherWin = null;
if (otherWin) {
otherWin.close();
}
yield endCustomizing();
});

View File

@ -258,7 +258,7 @@ add_task(function() {
contextMenu.hidePopup();
yield hiddenContextPromise;
yield endCustomizing(this.otherWin);
this.otherWin.close();
yield promiseWindowClosed(this.otherWin);
this.otherWin = null;
});

View File

@ -72,10 +72,7 @@ add_task(function() {
assertWidgetExists(private1, false);
assertWidgetExists(private2, false);
plain1.close();
plain2.close();
private1.close();
private2.close();
yield Promise.all([plain1, plain2, private1, private2].map(promiseWindowClosed));
CustomizableUI.destroyWidget("some-widget");
});
@ -127,10 +124,7 @@ add_task(function() {
assertWidgetExists(private1, false);
assertWidgetExists(private2, false);
plain1.close();
plain2.close();
private1.close();
private2.close();
yield Promise.all([plain1, plain2, private1, private2].map(promiseWindowClosed));
CustomizableUI.destroyWidget("some-widget");
});

View File

@ -46,7 +46,7 @@ add_task(function() {
is(otherPersonalbar.getAttribute("currentset"), personalbarCurrentset,
"Should have updated other window's currentSet after remove.");
otherWin.close();
yield promiseWindowClosed(otherWin);
// Reset in asyncCleanup will put our button back for us.
});

View File

@ -19,7 +19,7 @@ add_task(function() {
let otherNavBar = newWindow.document.getElementById(CustomizableUI.AREA_NAVBAR);
yield waitForCondition(() => otherNavBar.hasAttribute("overflowing"));
ok(otherNavBar.hasAttribute("overflowing"), "Other window should have an overflowing toolbar.");
newWindow.close();
yield promiseWindowClosed(newWindow);
window.resizeTo(originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));

View File

@ -42,7 +42,7 @@ add_task(function() {
}
}
} finally {
otherWindow.close();
yield promiseWindowClosed(otherWindow);
}
btn.remove();
btn2.remove();

View File

@ -25,7 +25,7 @@ add_task(function testPrivateBrowsingCustomizeModeWidget() {
privateWidgetArray = privateWidgetArray.map((w) => w.id);
is(privateWidgetArray.indexOf(kWidgetId), -1,
"Widget should not appear as unused in private window");
privateWindow.close();
yield promiseWindowClosed(privateWindow);
CustomizableUI.destroyWidget(kWidgetId);
});

View File

@ -94,20 +94,7 @@ function configureFxAccountIdentity() {
// uid will be set to the username.
};
let MockInternal = {
signedInUser: {
version: DATA_FORMAT_VERSION,
accountData: user
},
getCertificate: function(data, keyPair, mustBeValidUntil) {
this.cert = {
validUntil: Date.now() + CERT_LIFETIME,
cert: "certificate",
};
return Promise.resolve(this.cert.cert);
},
};
let MockInternal = {};
let mockTSC = { // TokenServerClient
getTokenFromBrowserIDAssertion: function(uri, assertion, cb) {
token.uid = "username";
@ -117,6 +104,19 @@ function configureFxAccountIdentity() {
let authService = Weave.Service.identity;
authService._fxaService = new FxAccounts(MockInternal);
authService._fxaService.internal.currentAccountState.signedInUser = {
version: DATA_FORMAT_VERSION,
accountData: user
}
authService._fxaService.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) {
this.cert = {
validUntil: authService._fxaService.internal.now() + CERT_LIFETIME,
cert: "certificate",
};
return Promise.resolve(this.cert.cert);
};
authService._tokenServerClient = mockTSC;
// Set the "account" of the browserId manager to be the "email" of the
// logged in user of the mockFXA service.

View File

@ -59,7 +59,7 @@ add_task(function() {
"Widget should be in navbar in other window.");
}
}
otherWin.close();
yield promiseWindowClosed(otherWin);
});
add_task(function asyncCleanup() {

View File

@ -38,7 +38,7 @@ add_task(function() {
gNavToolbox.removeEventListener("customizationchange", handler);
otherToolbox.removeEventListener("customizationchange", handler);
newWindow.close();
yield promiseWindowClosed(newWindow);
});
add_task(function asyncCleanup() {

View File

@ -60,12 +60,9 @@ function resetCustomization() {
}
function isInWin8() {
let sysInfo = Services.sysinfo;
let osName = sysInfo.getProperty("name");
let version = sysInfo.getProperty("version");
// Windows 8 is version >= 6.2
return osName == "Windows_NT" && version >= 6.2;
if (!Services.metro)
return false;
return Services.metro.supported;
}
function addSwitchToMetroButtonInWindows8(areaPanelPlacements) {
@ -202,6 +199,11 @@ function openAndLoadWindow(aOptions, aWaitForDelayedStartup=false) {
return deferred.promise;
}
function promiseWindowClosed(win) {
win.close();
return waitForCondition(() => win.closed);
}
function promisePanelShown(win) {
let panelEl = win.PanelUI.panel;
return promisePanelElementShown(win, panelEl);

View File

@ -50,6 +50,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
XPCOMUtils.defineLazyModuleGetter(this, "BrowserNewTabPreloader",
"resource:///modules/BrowserNewTabPreloader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizationTabPreloader",
"resource:///modules/CustomizationTabPreloader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PdfJs",
"resource://pdf.js/PdfJs.jsm");
@ -469,6 +472,7 @@ BrowserGlue.prototype = {
PageThumbs.init();
NewTabUtils.init();
BrowserNewTabPreloader.init();
CustomizationTabPreloader.init();
SignInToWebsiteUX.init();
PdfJs.init();
#ifdef NIGHTLY_BUILD
@ -650,6 +654,7 @@ BrowserGlue.prototype = {
}
BrowserNewTabPreloader.uninit();
CustomizationTabPreloader.uninit();
webappsUI.uninit();
SignInToWebsiteUX.uninit();
webrtcUI.uninit();

View File

@ -25,8 +25,8 @@ var gConnectionsDialog = {
var proxyPortPref = document.getElementById("network.proxy." + proxyPrefs[i] + "_port");
var backupServerURLPref = document.getElementById("network.proxy.backup." + proxyPrefs[i]);
var backupPortPref = document.getElementById("network.proxy.backup." + proxyPrefs[i] + "_port");
backupServerURLPref.value = proxyServerURLPref.value;
backupPortPref.value = proxyPortPref.value;
backupServerURLPref.value = backupServerURLPref.value || proxyServerURLPref.value;
backupPortPref.value = backupPortPref.value || proxyPortPref.value;
proxyServerURLPref.value = httpProxyURLPref.value;
proxyPortPref.value = httpProxyPortPref.value;
}

View File

@ -9,6 +9,7 @@ support-files =
[browser_connection.js]
[browser_healthreport.js]
skip-if = !healthreport || (os == 'linux' && debug)
[browser_proxy_backup.js]
[browser_privacypane_1.js]
[browser_privacypane_3.js]
[browser_privacypane_5.js]

View File

@ -7,7 +7,7 @@ Components.utils.import("resource://gre/modules/Services.jsm");
function test() {
waitForExplicitFinish();
// network.proxy.type needs to be backed up and restored because mochitest
// changes this setting from the default
let oldNetworkProxyType = Services.prefs.getIntPref("network.proxy.type");
@ -16,10 +16,9 @@ function test() {
Services.prefs.clearUserPref("network.proxy.no_proxies_on");
Services.prefs.clearUserPref("browser.preferences.instantApply");
});
let connectionURI = "chrome://browser/content/preferences/connection.xul";
let windowWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
let connectionURL = "chrome://browser/content/preferences/connection.xul";
let windowWatcher = Services.ww;
// instantApply must be true, otherwise connection dialog won't save
// when opened from in-content prefs
@ -28,13 +27,12 @@ function test() {
// this observer is registered after the pref tab loads
let observer = {
observe: function(aSubject, aTopic, aData) {
if (aTopic == "domwindowopened") {
// when connection window loads, run tests and acceptDialog()
let win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
win.addEventListener("load", function winLoadListener() {
win.removeEventListener("load", winLoadListener, false);
if (win.location.href == connectionURI) {
if (win.location.href == connectionURL) {
ok(true, "connection window opened");
runConnectionTests(win);
win.document.documentElement.acceptDialog();
@ -43,7 +41,7 @@ function test() {
} else if (aTopic == "domwindowclosed") {
// finish up when connection window closes
let win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
if (win.location.href == connectionURI) {
if (win.location.href == connectionURL) {
windowWatcher.unregisterNotification(observer);
ok(true, "connection window closed");
// runConnectionTests will have changed this pref - make sure it was
@ -54,9 +52,8 @@ function test() {
finish();
}
}
}
}
};
/*
The connection dialog alone won't save onaccept since it uses type="child",
@ -82,7 +79,7 @@ function runConnectionTests(win) {
"networkProxyNone textbox is multiline");
is(networkProxyNone.getAttribute("rows"), "2",
"networkProxyNone textbox has two rows");
// check if sanitizing the given input for the no_proxies_on pref results in
// expected string
function testSanitize(input, expected, errorMessage) {

View File

@ -0,0 +1,88 @@
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://gre/modules/Services.jsm");
function test() {
waitForExplicitFinish();
// network.proxy.type needs to be backed up and restored because mochitest
// changes this setting from the default
let oldNetworkProxyType = Services.prefs.getIntPref("network.proxy.type");
registerCleanupFunction(function() {
Services.prefs.setIntPref("network.proxy.type", oldNetworkProxyType);
Services.prefs.clearUserPref("browser.preferences.instantApply");
Services.prefs.clearUserPref("network.proxy.share_proxy_settings");
for (let proxyType of ["http", "ssl", "ftp", "socks"]) {
Services.prefs.clearUserPref("network.proxy." + proxyType);
Services.prefs.clearUserPref("network.proxy." + proxyType + "_port");
if (proxyType == "http") {
continue;
}
Services.prefs.clearUserPref("network.proxy.backup." + proxyType);
Services.prefs.clearUserPref("network.proxy.backup." + proxyType + "_port");
}
});
let connectionURL = "chrome://browser/content/preferences/connection.xul";
let windowWatcher = Services.ww;
// instantApply must be true, otherwise connection dialog won't save
// when opened from in-content prefs
Services.prefs.setBoolPref("browser.preferences.instantApply", true);
// Set a shared proxy and a SOCKS backup
Services.prefs.setIntPref("network.proxy.type", 1);
Services.prefs.setBoolPref("network.proxy.share_proxy_settings", true);
Services.prefs.setCharPref("network.proxy.http", "example.com");
Services.prefs.setIntPref("network.proxy.http_port", 1200);
Services.prefs.setCharPref("network.proxy.socks", "example.com");
Services.prefs.setIntPref("network.proxy.socks_port", 1200);
Services.prefs.setCharPref("network.proxy.backup.socks", "127.0.0.1");
Services.prefs.setIntPref("network.proxy.backup.socks_port", 9050);
// this observer is registered after the pref tab loads
let observer = {
observe: function(aSubject, aTopic, aData) {
if (aTopic == "domwindowopened") {
// when connection window loads, run tests and acceptDialog()
let win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
win.addEventListener("load", function winLoadListener() {
win.removeEventListener("load", winLoadListener, false);
if (win.location.href == connectionURL) {
ok(true, "connection window opened");
win.document.documentElement.acceptDialog();
}
}, false);
} else if (aTopic == "domwindowclosed") {
// finish up when connection window closes
let win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
if (win.location.href == connectionURL) {
windowWatcher.unregisterNotification(observer);
ok(true, "connection window closed");
// The SOCKS backup should not be replaced by the shared value
is(Services.prefs.getCharPref("network.proxy.backup.socks"), "127.0.0.1", "Shared proxy backup shouldn't be replaced");
is(Services.prefs.getIntPref("network.proxy.backup.socks_port"), 9050, "Shared proxy port backup shouldn't be replaced");
gBrowser.removeCurrentTab();
finish();
}
}
}
};
/*
The connection dialog alone won't save onaccept since it uses type="child",
so it has to be opened as a sub dialog of the main pref tab.
Open the main tab here.
*/
open_preferences(function tabOpened(aContentWindow) {
is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
windowWatcher.registerNotification(observer);
gBrowser.contentWindow.gAdvancedPane.showConnections();
});
}

View File

@ -264,9 +264,9 @@
orient="vertical"
onselect="if (this.selectedCount) this.clearSelection();">
<richlistitem>
<checkbox label="&engine.addons.label;"
accesskey="&engine.addons.accesskey;"
preference="engine.addons"/>
<checkbox label="&engine.tabs.label;"
accesskey="&engine.tabs.accesskey;"
preference="engine.tabs"/>
</richlistitem>
<richlistitem>
<checkbox label="&engine.bookmarks.label;"
@ -278,20 +278,20 @@
accesskey="&engine.passwords.accesskey;"
preference="engine.passwords"/>
</richlistitem>
<richlistitem>
<checkbox label="&engine.prefs.label;"
accesskey="&engine.prefs.accesskey;"
preference="engine.prefs"/>
</richlistitem>
<richlistitem>
<checkbox label="&engine.history.label;"
accesskey="&engine.history.accesskey;"
preference="engine.history"/>
</richlistitem>
<richlistitem>
<checkbox label="&engine.tabs.label;"
accesskey="&engine.tabs.accesskey;"
preference="engine.tabs"/>
<checkbox label="&engine.addons.label;"
accesskey="&engine.addons.accesskey;"
preference="engine.addons"/>
</richlistitem>
<richlistitem>
<checkbox label="&engine.prefs.label;"
accesskey="&engine.prefs.accesskey;"
preference="engine.prefs"/>
</richlistitem>
</richlistbox>
</vbox>

View File

@ -13,10 +13,14 @@ let output = null;
let menu = null;
function test() {
let originalNetPref = Services.prefs.getBoolPref("devtools.webconsole.filter.networkinfo");
registerCleanupFunction(() => {
Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", originalNetPref);
HUD = output = menu = null;
});
Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", true);
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);

View File

@ -17,8 +17,13 @@ let requestCallback = null;
function test()
{
const PREF = "devtools.webconsole.persistlog";
let original = Services.prefs.getBoolPref("devtools.webconsole.filter.networkinfo");
Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", true);
Services.prefs.setBoolPref(PREF, true);
registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
registerCleanupFunction(() => {
Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", original);
Services.prefs.clearUserPref(PREF);
});
addTab("data:text/html;charset=utf-8,Web Console network logging tests");

View File

@ -11,6 +11,11 @@ const CONTEXT_MENU_ID = "#menu_openURL";
let HUD = null, outputNode = null, contextMenu = null;
function test() {
let original = Services.prefs.getBoolPref("devtools.webconsole.filter.networkinfo");
Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", true);
registerCleanupFunction(() => {
Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", original);
});
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);

View File

@ -6,6 +6,11 @@
// navigate to that destination (bug 975707).
function test() {
let originalNetPref = Services.prefs.getBoolPref("devtools.webconsole.filter.networkinfo");
registerCleanupFunction(() => {
Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", originalNetPref);
});
Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", true);
Task.spawn(runner).then(finishTest);
function* runner() {

View File

@ -45,5 +45,5 @@
<!ENTITY shareproxy.label "Use this proxy server for all protocols">
<!ENTITY shareproxy.accesskey "s">
<!ENTITY autologinproxy.label "Do not prompt for authentication if password is saved">
<!ENTITY autologinproxy.accesskey "v">
<!ENTITY autologinproxy.accesskey "i">
<!ENTITY autologinproxy.tooltip "This option silently authenticates you to proxies when you have saved credentials for them. You will be prompted if authentication fails.">

View File

@ -734,6 +734,21 @@
item.setAttribute("label", label);
item.setAttribute("value", value);
item.setAttribute("iconURI", iconURI);
let xpFaviconURI = Services.io.newURI(iconURI.replace("moz-anno:favicon:",""), null, null);
Task.spawn(function() {
let colorInfo = yield ColorUtils.getForegroundAndBackgroundIconColors(xpFaviconURI);
if ( !(colorInfo && colorInfo.background && colorInfo.foreground)
|| (item.getAttribute("iconURI") != iconURI) ) {
return;
}
let { background, foreground } = colorInfo;
item.style.color = foreground; //color text
item.setAttribute("customColor", background);
// when bound, use the setter to propogate the color change through the tile
if ('color' in item) {
item.color = background;
}
}).then(null, err => Components.utils.reportError(err));
}
this._results.arrangeItems();

View File

@ -40,6 +40,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "ColorUtils",
"resource:///modules/colorUtils.jsm");
#ifdef NIGHTLY_BUILD
XPCOMUtils.defineLazyModuleGetter(this, "ShumwayUtils",
"resource://shumway/ShumwayUtils.jsm");

View File

@ -0,0 +1,245 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["CustomizationTabPreloader"];
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
const HTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window%20id='win'/>";
const CUSTOMIZATION_URL = "about:customizing";
// The interval between swapping in a preload docShell and kicking off the
// next preload in the background.
const PRELOADER_INTERVAL_MS = 600;
// The initial delay before we start preloading our first customization page. The
// timer is started after the first 'browser-delayed-startup' has been sent.
const PRELOADER_INIT_DELAY_MS = 7000;
const TOPIC_TIMER_CALLBACK = "timer-callback";
const TOPIC_DELAYED_STARTUP = "browser-delayed-startup-finished";
function createTimer(obj, delay) {
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.init(obj, delay, Ci.nsITimer.TYPE_ONE_SHOT);
return timer;
}
function clearTimer(timer) {
if (timer) {
timer.cancel();
}
return null;
}
this.CustomizationTabPreloader = {
init: function() {
CustomizationTabPreloaderInternal.init();
},
uninit: function () {
CustomizationTabPreloaderInternal.uninit();
},
newTab: function (aTab) {
return CustomizationTabPreloaderInternal.newTab(aTab);
},
};
Object.freeze(CustomizationTabPreloader);
this.CustomizationTabPreloaderInternal = {
_browser: null,
_timer: null,
_observing: false,
init: function () {
Services.obs.addObserver(this, TOPIC_DELAYED_STARTUP, false);
this._observing = true;
},
uninit: function () {
this._timer = clearTimer(this._timer);
if (this._observing) {
Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
this._observing = false;
}
HostFrame.destroy();
if (this._browser) {
this._browser.destroy();
this._browser = null;
}
},
newTab: function (aTab) {
let win = aTab.ownerDocument.defaultView;
if (win.gBrowser && this._browser) {
return this._browser.swapWithNewTab(aTab);
}
return false;
},
observe: function (aSubject, aTopic, aData) {
if (aTopic == TOPIC_DELAYED_STARTUP) {
Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
this._observing = false;
this._startTimer();
} else if (aTopic == TOPIC_TIMER_CALLBACK) {
this._timer = null;
this._startPreloader();
}
},
_startTimer: function () {
this._timer = createTimer(this, PRELOADER_INIT_DELAY_MS);
},
_startPreloader: function () {
this._browser = new HiddenBrowser();
}
};
function HiddenBrowser() {
this._createBrowser();
}
HiddenBrowser.prototype = {
_timer: null,
get isPreloaded() {
return this._browser &&
this._browser.contentDocument &&
this._browser.contentDocument.readyState === "complete" &&
this._browser.currentURI.spec === CUSTOMIZATION_URL;
},
swapWithNewTab: function (aTab) {
if (!this.isPreloaded || this._timer) {
return false;
}
let win = aTab.ownerDocument.defaultView;
let tabbrowser = win.gBrowser;
if (!tabbrowser) {
return false;
}
// Swap docShells.
tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
// Load all default frame scripts attached to the target window.
let mm = aTab.linkedBrowser.messageManager;
let scripts = win.messageManager.getDelayedFrameScripts();
Array.forEach(scripts, ([script, runGlobal]) => mm.loadFrameScript(script, true, runGlobal));
// Remove the browser, it will be recreated by a timer.
this._removeBrowser();
// Start a timer that will kick off preloading the next page.
this._timer = createTimer(this, PRELOADER_INTERVAL_MS);
// Signal that we swapped docShells.
return true;
},
observe: function () {
this._timer = null;
// Start pre-loading the customization page.
this._createBrowser();
},
destroy: function () {
this._removeBrowser();
this._timer = clearTimer(this._timer);
},
_createBrowser: function () {
HostFrame.get().then(aFrame => {
let doc = aFrame.document;
this._browser = doc.createElementNS(XUL_NS, "browser");
this._browser.setAttribute("type", "content");
this._browser.setAttribute("src", CUSTOMIZATION_URL);
this._browser.style.width = "400px";
this._browser.style.height = "400px";
doc.getElementById("win").appendChild(this._browser);
});
},
_removeBrowser: function () {
if (this._browser) {
this._browser.remove();
this._browser = null;
}
}
};
let HostFrame = {
_frame: null,
_deferred: null,
get hiddenDOMDocument() {
return Services.appShell.hiddenDOMWindow.document;
},
get isReady() {
return this.hiddenDOMDocument.readyState === "complete";
},
get: function () {
if (!this._deferred) {
this._deferred = Promise.defer();
this._create();
}
return this._deferred.promise;
},
destroy: function () {
if (this._frame) {
if (!Cu.isDeadWrapper(this._frame)) {
this._frame.removeEventListener("load", this, true);
this._frame.remove();
}
this._frame = null;
this._deferred = null;
}
},
handleEvent: function () {
let contentWindow = this._frame.contentWindow;
if (contentWindow.location.href === XUL_PAGE) {
this._frame.removeEventListener("load", this, true);
this._deferred.resolve(contentWindow);
} else {
contentWindow.location = XUL_PAGE;
}
},
_create: function () {
if (this.isReady) {
let doc = this.hiddenDOMDocument;
this._frame = doc.createElementNS(HTML_NS, "iframe");
this._frame.addEventListener("load", this, true);
doc.documentElement.appendChild(this._frame);
} else {
let flags = Ci.nsIThread.DISPATCH_NORMAL;
Services.tm.currentThread.dispatch(() => this._create(), flags);
}
}
};

View File

@ -11,6 +11,7 @@ EXTRA_JS_MODULES += [
'BrowserUITelemetry.jsm',
'ContentClick.jsm',
'ContentLinkHandler.jsm',
'CustomizationTabPreloader.jsm',
'Feeds.jsm',
'NetworkPrioritizer.jsm',
'offlineAppCache.jsm',

View File

@ -172,11 +172,17 @@ function prompt(aContentWindow, aCallID, aAudioRequested, aVideoRequested, aDevi
if (aTopic == "swapping")
return true;
let chromeDoc = this.browser.ownerDocument;
if (aTopic == "shown") {
let PopupNotifications = chromeDoc.defaultView.PopupNotifications;
let popupId = requestType == "Microphone" ? "Microphone" : "Devices";
PopupNotifications.panel.firstChild.setAttribute("popupid", "webRTC-share" + popupId);
}
if (aTopic != "showing")
return false;
let chromeDoc = this.browser.ownerDocument;
function listDevices(menupopup, devices) {
while (menupopup.lastChild)
menupopup.removeChild(menupopup.lastChild);
@ -245,9 +251,10 @@ function prompt(aContentWindow, aCallID, aAudioRequested, aVideoRequested, aDevi
}
};
let anchorId = requestType == "Microphone" ? "webRTC-shareMicrophone-notification-icon"
: "webRTC-shareDevices-notification-icon";
chromeWin.PopupNotifications.show(browser, "webRTC-shareDevices", message,
"webRTC-shareDevices-notification-icon", mainAction,
secondaryActions, options);
anchorId, mainAction, secondaryActions, options);
}
function updateIndicators() {
@ -313,11 +320,19 @@ function showBrowserSpecificIndicator(aBrowser) {
let options = {
hideNotNow: true,
dismissed: true,
eventCallback: function(aTopic) aTopic == "swapping"
eventCallback: function(aTopic) {
if (aTopic == "shown") {
let PopupNotifications = this.browser.ownerDocument.defaultView.PopupNotifications;
let popupId = captureState == "Microphone" ? "Microphone" : "Devices";
PopupNotifications.panel.firstChild.setAttribute("popupid", "webRTC-sharing" + popupId);
}
return aTopic == "swapping";
}
};
let anchorId = captureState == "Microphone" ? "webRTC-sharingMicrophone-notification-icon"
: "webRTC-sharingDevices-notification-icon";
chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingDevices", message,
"webRTC-sharingDevices-notification-icon", mainAction,
secondaryActions, options);
anchorId, mainAction, secondaryActions, options);
}
function removeBrowserSpecificIndicator(aSubject, aTopic, aData) {

View File

@ -1093,6 +1093,11 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png);
}
.popup-notification-icon[popupid="webRTC-sharingMicrophone"],
.popup-notification-icon[popupid="webRTC-shareMicrophone"] {
list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-64.png);
}
.popup-notification-icon[popupid="pointerLock"] {
list-style-image: url(chrome://browser/skin/pointerLock-64.png);
}
@ -1225,6 +1230,16 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16.png);
}
.webRTC-shareMicrophone-notification-icon,
#webRTC-shareMicrophone-notification-icon {
list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-16.png);
}
.webRTC-sharingMicrophone-notification-icon,
#webRTC-sharingMicrophone-notification-icon {
list-style-image: url(chrome://browser/skin/webRTC-sharingMicrophone-16.png);
}
.web-notifications-notification-icon,
#web-notifications-notification-icon {
list-style-image: url(chrome://browser/skin/notification-16.png);

View File

@ -68,6 +68,9 @@ browser.jar:
skin/classic/browser/webRTC-shareDevice-16.png
skin/classic/browser/webRTC-shareDevice-64.png
skin/classic/browser/webRTC-sharingDevice-16.png
skin/classic/browser/webRTC-shareMicrophone-16.png
skin/classic/browser/webRTC-shareMicrophone-64.png
skin/classic/browser/webRTC-sharingMicrophone-16.png
skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png)
skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png)
skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -3470,6 +3470,28 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
}
.webRTC-shareMicrophone-notification-icon,
#webRTC-shareMicrophone-notification-icon {
list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-16.png);
}
@media (min-resolution: 2dppx) {
.webRTC-shareMicrophone-notification-icon,
#webRTC-shareMicrophone-notification-icon {
list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-16@2x.png);
}
}
.webRTC-sharingMicrophone-notification-icon,
#webRTC-sharingMicrophone-notification-icon {
list-style-image: url(chrome://browser/skin/webRTC-sharingMicrophone-16.png);
}
@media (min-resolution: 2dppx) {
.webRTC-sharingMicrophone-notification-icon,
#webRTC-sharingMicrophone-notification-icon {
list-style-image: url(chrome://browser/skin/webRTC-sharingMicrophone-16@2x.png);
}
}
.web-notifications-notification-icon,
#web-notifications-notification-icon {
list-style-image: url(chrome://browser/skin/notification-16.png);
@ -3623,6 +3645,17 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
}
.popup-notification-icon[popupid="webRTC-sharingMicrophone"],
.popup-notification-icon[popupid="webRTC-shareMicrophone"] {
list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-64.png);
}
@media (min-resolution: 2dppx) {
.popup-notification-icon[popupid="webRTC-sharingMicrophone"],
.popup-notification-icon[popupid="webRTC-shareMicrophone"] {
list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-64@2x.png);
}
}
/* Popup Buttons */
#identity-popup-more-info-button {
@hudButton@

View File

@ -113,6 +113,12 @@ browser.jar:
skin/classic/browser/webRTC-shareDevice-64@2x.png
skin/classic/browser/webRTC-sharingDevice-16.png
skin/classic/browser/webRTC-sharingDevice-16@2x.png
skin/classic/browser/webRTC-shareMicrophone-16.png
skin/classic/browser/webRTC-shareMicrophone-16@2x.png
skin/classic/browser/webRTC-shareMicrophone-64.png
skin/classic/browser/webRTC-shareMicrophone-64@2x.png
skin/classic/browser/webRTC-sharingMicrophone-16.png
skin/classic/browser/webRTC-sharingMicrophone-16@2x.png
skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png)
skin/classic/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png)
skin/classic/browser/customizableui/customize-titleBar-toggle@2x.png (customizableui/customize-titleBar-toggle@2x.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -250,7 +250,13 @@
position: relative;
}
#TabsToolbar:not(:-moz-lwtheme)::before {
#TabsToolbar:not(:-moz-lwtheme)::after {
/* Because we use placeholders for window controls etc. in the tabstrip,
* and position those with ordinal attributes, and because our layout code
* expects :before/:after nodes to come first/last in the frame list,
* we have to reorder this element to come last, hence the
* ordinal group value (see bug 853415). */
-moz-box-ordinal-group: 1001;
box-shadow: 0 0 30px 30px rgba(174,189,204,0.85);
content: "";
display: -moz-box;

View File

@ -1982,6 +1982,11 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png);
}
.popup-notification-icon[popupid="webRTC-sharingMicrophone"],
.popup-notification-icon[popupid="webRTC-shareMicrophone"] {
list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-64.png);
}
.popup-notification-icon[popupid="pointerLock"] {
list-style-image: url(chrome://browser/skin/pointerLock-64.png);
}
@ -2113,6 +2118,16 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16.png);
}
.webRTC-shareMicrophone-notification-icon,
#webRTC-shareMicrophone-notification-icon {
list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-16.png);
}
.webRTC-sharingMicrophone-notification-icon,
#webRTC-sharingMicrophone-notification-icon {
list-style-image: url(chrome://browser/skin/webRTC-sharingMicrophone-16.png);
}
.web-notifications-notification-icon,
#web-notifications-notification-icon {
list-style-image: url(chrome://browser/skin/notification-16.png);

View File

@ -86,6 +86,9 @@ browser.jar:
skin/classic/browser/webRTC-shareDevice-16.png
skin/classic/browser/webRTC-shareDevice-64.png
skin/classic/browser/webRTC-sharingDevice-16.png
skin/classic/browser/webRTC-shareMicrophone-16.png
skin/classic/browser/webRTC-shareMicrophone-64.png
skin/classic/browser/webRTC-sharingMicrophone-16.png
skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png)
skin/classic/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico)
skin/classic/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png)
@ -404,6 +407,9 @@ browser.jar:
skin/classic/aero/browser/webRTC-shareDevice-16.png
skin/classic/aero/browser/webRTC-shareDevice-64.png
skin/classic/aero/browser/webRTC-sharingDevice-16.png
skin/classic/aero/browser/webRTC-shareMicrophone-16.png
skin/classic/aero/browser/webRTC-shareMicrophone-64.png
skin/classic/aero/browser/webRTC-sharingMicrophone-16.png
skin/classic/aero/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png)
skin/classic/aero/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png)
skin/classic/aero/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -370,6 +370,8 @@ public class GeckoAppShell
public static void sendEventToGecko(GeckoEvent e) {
if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
notifyGeckoOfEvent(e);
// Gecko will copy the event data into a normal C++ object. We can recycle the evet now.
e.recycle();
} else {
gPendingEvents.addLast(e);
}

View File

@ -25,10 +25,12 @@ import android.os.Build;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.MotionEvent;
import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;
/* We're not allowed to hold on to most events given to us
* so we save the parts of the events we want to use in GeckoEvent.
@ -42,6 +44,34 @@ import java.nio.ByteBuffer;
public class GeckoEvent {
private static final String LOGTAG = "GeckoEvent";
private static final int EVENT_FACTORY_SIZE = 5;
// Maybe we're probably better to just make mType non final, and just store GeckoEvents in here...
private static SparseArray<ArrayBlockingQueue<GeckoEvent>> mEvents = new SparseArray<ArrayBlockingQueue<GeckoEvent>>();
public static GeckoEvent get(NativeGeckoEvent type) {
synchronized (mEvents) {
ArrayBlockingQueue<GeckoEvent> events = mEvents.get(type.value);
if (events != null && events.size() > 0) {
return events.poll();
}
}
return new GeckoEvent(type);
}
public void recycle() {
synchronized (mEvents) {
ArrayBlockingQueue<GeckoEvent> events = mEvents.get(mType);
if (events == null) {
events = new ArrayBlockingQueue<GeckoEvent>(EVENT_FACTORY_SIZE);
mEvents.put(mType, events);
}
events.offer(this);
}
}
// Make sure to keep these values in sync with the enum in
// AndroidGeckoEvent in widget/android/AndroidJavaWrappers.h
@JNITarget
@ -209,36 +239,36 @@ public class GeckoEvent {
}
public static GeckoEvent createAppBackgroundingEvent() {
return new GeckoEvent(NativeGeckoEvent.APP_BACKGROUNDING);
return GeckoEvent.get(NativeGeckoEvent.APP_BACKGROUNDING);
}
public static GeckoEvent createAppForegroundingEvent() {
return new GeckoEvent(NativeGeckoEvent.APP_FOREGROUNDING);
return GeckoEvent.get(NativeGeckoEvent.APP_FOREGROUNDING);
}
public static GeckoEvent createNoOpEvent() {
return new GeckoEvent(NativeGeckoEvent.NOOP);
return GeckoEvent.get(NativeGeckoEvent.NOOP);
}
public static GeckoEvent createKeyEvent(KeyEvent k, int metaState) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.KEY_EVENT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.KEY_EVENT);
event.initKeyEvent(k, metaState);
return event;
}
public static GeckoEvent createCompositorCreateEvent(int width, int height) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.COMPOSITOR_CREATE);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.COMPOSITOR_CREATE);
event.mWidth = width;
event.mHeight = height;
return event;
}
public static GeckoEvent createCompositorPauseEvent() {
return new GeckoEvent(NativeGeckoEvent.COMPOSITOR_PAUSE);
return GeckoEvent.get(NativeGeckoEvent.COMPOSITOR_PAUSE);
}
public static GeckoEvent createCompositorResumeEvent() {
return new GeckoEvent(NativeGeckoEvent.COMPOSITOR_RESUME);
return GeckoEvent.get(NativeGeckoEvent.COMPOSITOR_RESUME);
}
private void initKeyEvent(KeyEvent k, int metaState) {
@ -342,7 +372,7 @@ public class GeckoEvent {
public static GeckoEvent createNativeGestureEvent(int action, PointF pt, double size) {
try {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.NATIVE_GESTURE_EVENT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.NATIVE_GESTURE_EVENT);
event.mAction = action;
event.mCount = 1;
event.mPoints = new Point[1];
@ -373,7 +403,7 @@ public class GeckoEvent {
* relative to gecko's coordinate system (CSS pixels relative to gecko scroll position).
*/
public static GeckoEvent createMotionEvent(MotionEvent m, boolean keepInViewCoordinates) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.MOTION_EVENT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.MOTION_EVENT);
event.initMotionEvent(m, keepInViewCoordinates);
return event;
}
@ -494,7 +524,7 @@ public class GeckoEvent {
switch(sensor_type) {
case Sensor.TYPE_ACCELEROMETER:
event = new GeckoEvent(NativeGeckoEvent.SENSOR_EVENT);
event = GeckoEvent.get(NativeGeckoEvent.SENSOR_EVENT);
event.mFlags = GeckoHalDefines.SENSOR_ACCELERATION;
event.mMetaState = HalSensorAccuracyFor(s.accuracy);
event.mX = s.values[0];
@ -503,7 +533,7 @@ public class GeckoEvent {
break;
case 10 /* Requires API Level 9, so just use the raw value - Sensor.TYPE_LINEAR_ACCELEROMETER*/ :
event = new GeckoEvent(NativeGeckoEvent.SENSOR_EVENT);
event = GeckoEvent.get(NativeGeckoEvent.SENSOR_EVENT);
event.mFlags = GeckoHalDefines.SENSOR_LINEAR_ACCELERATION;
event.mMetaState = HalSensorAccuracyFor(s.accuracy);
event.mX = s.values[0];
@ -512,7 +542,7 @@ public class GeckoEvent {
break;
case Sensor.TYPE_ORIENTATION:
event = new GeckoEvent(NativeGeckoEvent.SENSOR_EVENT);
event = GeckoEvent.get(NativeGeckoEvent.SENSOR_EVENT);
event.mFlags = GeckoHalDefines.SENSOR_ORIENTATION;
event.mMetaState = HalSensorAccuracyFor(s.accuracy);
event.mX = s.values[0];
@ -521,7 +551,7 @@ public class GeckoEvent {
break;
case Sensor.TYPE_GYROSCOPE:
event = new GeckoEvent(NativeGeckoEvent.SENSOR_EVENT);
event = GeckoEvent.get(NativeGeckoEvent.SENSOR_EVENT);
event.mFlags = GeckoHalDefines.SENSOR_GYROSCOPE;
event.mMetaState = HalSensorAccuracyFor(s.accuracy);
event.mX = Math.toDegrees(s.values[0]);
@ -530,7 +560,7 @@ public class GeckoEvent {
break;
case Sensor.TYPE_PROXIMITY:
event = new GeckoEvent(NativeGeckoEvent.SENSOR_EVENT);
event = GeckoEvent.get(NativeGeckoEvent.SENSOR_EVENT);
event.mFlags = GeckoHalDefines.SENSOR_PROXIMITY;
event.mMetaState = HalSensorAccuracyFor(s.accuracy);
event.mX = s.values[0];
@ -539,7 +569,7 @@ public class GeckoEvent {
break;
case Sensor.TYPE_LIGHT:
event = new GeckoEvent(NativeGeckoEvent.SENSOR_EVENT);
event = GeckoEvent.get(NativeGeckoEvent.SENSOR_EVENT);
event.mFlags = GeckoHalDefines.SENSOR_LIGHT;
event.mMetaState = HalSensorAccuracyFor(s.accuracy);
event.mX = s.values[0];
@ -549,26 +579,26 @@ public class GeckoEvent {
}
public static GeckoEvent createLocationEvent(Location l) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.LOCATION_EVENT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.LOCATION_EVENT);
event.mLocation = l;
return event;
}
public static GeckoEvent createIMEEvent(ImeAction action) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.IME_EVENT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
event.mAction = action.value;
return event;
}
public static GeckoEvent createIMEKeyEvent(KeyEvent k) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.IME_KEY_EVENT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_KEY_EVENT);
event.initKeyEvent(k, 0);
return event;
}
public static GeckoEvent createIMEReplaceEvent(int start, int end,
String text) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.IME_EVENT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
event.mAction = ImeAction.IME_REPLACE_TEXT.value;
event.mStart = start;
event.mEnd = end;
@ -577,7 +607,7 @@ public class GeckoEvent {
}
public static GeckoEvent createIMESelectEvent(int start, int end) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.IME_EVENT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
event.mAction = ImeAction.IME_SET_SELECTION.value;
event.mStart = start;
event.mEnd = end;
@ -585,7 +615,7 @@ public class GeckoEvent {
}
public static GeckoEvent createIMECompositionEvent(int start, int end) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.IME_EVENT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
event.mAction = ImeAction.IME_UPDATE_COMPOSITION.value;
event.mStart = start;
event.mEnd = end;
@ -600,7 +630,7 @@ public class GeckoEvent {
int rangeForeColor,
int rangeBackColor,
int rangeLineColor) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.IME_EVENT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
event.mAction = ImeAction.IME_ADD_COMPOSITION_RANGE.value;
event.mStart = start;
event.mEnd = end;
@ -615,13 +645,13 @@ public class GeckoEvent {
}
public static GeckoEvent createDrawEvent(Rect rect) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.DRAW);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.DRAW);
event.mRect = rect;
return event;
}
public static GeckoEvent createSizeChangedEvent(int w, int h, int screenw, int screenh) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.SIZE_CHANGED);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.SIZE_CHANGED);
event.mPoints = new Point[2];
event.mPoints[0] = new Point(w, h);
event.mPoints[1] = new Point(screenw, screenh);
@ -630,14 +660,14 @@ public class GeckoEvent {
@RobocopTarget
public static GeckoEvent createBroadcastEvent(String subject, String data) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.BROADCAST);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.BROADCAST);
event.mCharacters = subject;
event.mCharactersExtra = data;
return event;
}
public static GeckoEvent createViewportEvent(ImmutableViewportMetrics metrics, DisplayPortMetrics displayPort) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.VIEWPORT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.VIEWPORT);
event.mCharacters = "Viewport:Change";
StringBuilder sb = new StringBuilder(256);
sb.append("{ \"x\" : ").append(metrics.viewportRectLeft)
@ -654,35 +684,35 @@ public class GeckoEvent {
}
public static GeckoEvent createURILoadEvent(String uri) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.LOAD_URI);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.LOAD_URI);
event.mCharacters = uri;
event.mCharactersExtra = "";
return event;
}
public static GeckoEvent createWebappLoadEvent(String uri) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.LOAD_URI);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.LOAD_URI);
event.mCharacters = uri;
event.mCharactersExtra = "-webapp";
return event;
}
public static GeckoEvent createBookmarkLoadEvent(String uri) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.LOAD_URI);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.LOAD_URI);
event.mCharacters = uri;
event.mCharactersExtra = "-bookmark";
return event;
}
public static GeckoEvent createVisitedEvent(String data) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.VISITED);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.VISITED);
event.mCharacters = data;
return event;
}
public static GeckoEvent createNetworkEvent(double bandwidth, boolean canBeMetered,
boolean isWifi, int DHCPGateway) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.NETWORK_CHANGED);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.NETWORK_CHANGED);
event.mBandwidth = bandwidth;
event.mCanBeMetered = canBeMetered;
event.mIsWifi = isWifi;
@ -691,7 +721,7 @@ public class GeckoEvent {
}
public static GeckoEvent createThumbnailEvent(int tabId, int bufw, int bufh, ByteBuffer buffer) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.THUMBNAIL);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.THUMBNAIL);
event.mPoints = new Point[1];
event.mPoints[0] = new Point(bufw, bufh);
event.mMetaState = tabId;
@ -700,13 +730,13 @@ public class GeckoEvent {
}
public static GeckoEvent createScreenOrientationEvent(short aScreenOrientation) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.SCREENORIENTATION_CHANGED);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.SCREENORIENTATION_CHANGED);
event.mScreenOrientation = aScreenOrientation;
return event;
}
public static GeckoEvent createCallObserverEvent(String observerKey, String topic, String data) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.CALL_OBSERVER);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.CALL_OBSERVER);
event.mCharacters = observerKey;
event.mCharactersExtra = topic;
event.mData = data;
@ -714,14 +744,14 @@ public class GeckoEvent {
}
public static GeckoEvent createRemoveObserverEvent(String observerKey) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.REMOVE_OBSERVER);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.REMOVE_OBSERVER);
event.mCharacters = observerKey;
return event;
}
@RobocopTarget
public static GeckoEvent createPreferencesObserveEvent(int requestId, String[] prefNames) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.PREFERENCES_OBSERVE);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.PREFERENCES_OBSERVE);
event.mCount = requestId;
event.mPrefNames = prefNames;
return event;
@ -729,7 +759,7 @@ public class GeckoEvent {
@RobocopTarget
public static GeckoEvent createPreferencesGetEvent(int requestId, String[] prefNames) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.PREFERENCES_GET);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.PREFERENCES_GET);
event.mCount = requestId;
event.mPrefNames = prefNames;
return event;
@ -737,40 +767,40 @@ public class GeckoEvent {
@RobocopTarget
public static GeckoEvent createPreferencesRemoveObserversEvent(int requestId) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.PREFERENCES_REMOVE_OBSERVERS);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.PREFERENCES_REMOVE_OBSERVERS);
event.mCount = requestId;
return event;
}
public static GeckoEvent createLowMemoryEvent(int level) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.LOW_MEMORY);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.LOW_MEMORY);
event.mMetaState = level;
return event;
}
public static GeckoEvent createNetworkLinkChangeEvent(String status) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.NETWORK_LINK_CHANGE);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.NETWORK_LINK_CHANGE);
event.mCharacters = status;
return event;
}
public static GeckoEvent createTelemetryHistogramAddEvent(String histogram,
int value) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.TELEMETRY_HISTOGRAM_ADD);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.TELEMETRY_HISTOGRAM_ADD);
event.mCharacters = histogram;
event.mCount = value;
return event;
}
public static GeckoEvent createTelemetryUISessionStartEvent(String session, long timestamp) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.TELEMETRY_UI_SESSION_START);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.TELEMETRY_UI_SESSION_START);
event.mCharacters = session;
event.mTime = timestamp;
return event;
}
public static GeckoEvent createTelemetryUISessionStopEvent(String session, String reason, long timestamp) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.TELEMETRY_UI_SESSION_STOP);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.TELEMETRY_UI_SESSION_STOP);
event.mCharacters = session;
event.mCharactersExtra = reason;
event.mTime = timestamp;
@ -778,7 +808,7 @@ public class GeckoEvent {
}
public static GeckoEvent createTelemetryUIEvent(String action, String method, long timestamp, String extras) {
GeckoEvent event = new GeckoEvent(NativeGeckoEvent.TELEMETRY_UI_EVENT);
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.TELEMETRY_UI_EVENT);
event.mData = action;
event.mCharacters = method;
event.mCharactersExtra = extras;

View File

@ -887,7 +887,7 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
* You must hold the monitor while calling this.
*/
@Override
public void scrollMarginsBy(float dx, float dy) {
public void onSubdocumentScrollBy(float dx, float dy) {
ImmutableViewportMetrics newMarginsMetrics =
mMarginsAnimator.scrollBy(mViewportMetrics, dx, dy);
mViewportMetrics = mViewportMetrics.setMarginsFrom(newMarginsMetrics);

View File

@ -128,8 +128,6 @@ class JavaPanZoomController
private boolean mMediumPress;
/* Used to change the scrollY direction */
private boolean mNegateWheelScrollY;
/* Whether the current event has been default-prevented. */
private boolean mDefaultPrevented;
// Handler to be notified when overscroll occurs
private Overscroll mOverscroll;
@ -345,9 +343,7 @@ class JavaPanZoomController
return mTouchEventHandler.handleEvent(event);
}
boolean handleEvent(MotionEvent event, boolean defaultPrevented) {
mDefaultPrevented = defaultPrevented;
boolean handleEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: return handleTouchStart(event);
case MotionEvent.ACTION_MOVE: return handleTouchMove(event);
@ -405,6 +401,17 @@ class JavaPanZoomController
}
}
/** This function must be called on the UI thread. */
public void preventedTouchFinished() {
checkMainThread();
if (mState == PanZoomState.WAITING_LISTENERS) {
// if we enter here, we just finished a block of events whose default actions
// were prevented by touch listeners. Now there are no touch points left, so
// we need to reset our state and re-bounce because we might be in overscroll
bounce();
}
}
/** This must be called on the UI thread. */
@Override
public void pageRectUpdated() {
@ -517,18 +524,16 @@ class JavaPanZoomController
case FLING:
case AUTONAV:
case BOUNCE:
case WAITING_LISTENERS:
// should never happen
Log.e(LOGTAG, "Received impossible touch end while in " + mState);
// fall through
case ANIMATED_ZOOM:
case NOTHING:
// may happen if user double-taps and drags without lifting after the
// second tap. ignore if this happens.
return false;
case WAITING_LISTENERS:
if (!mDefaultPrevented) {
// should never happen
Log.e(LOGTAG, "Received impossible touch end while in " + mState);
}
// fall through
case TOUCHING:
// the switch into TOUCHING might have happened while the page was
// snapping back after overscroll. we need to finish the snap if that
@ -557,6 +562,16 @@ class JavaPanZoomController
private boolean handleTouchCancel(MotionEvent event) {
cancelTouch();
if (mState == PanZoomState.WAITING_LISTENERS) {
// we might get a cancel event from the TouchEventHandler while in the
// WAITING_LISTENERS state if the touch listeners prevent-default the
// block of events. at this point being in WAITING_LISTENERS is equivalent
// to being in NOTHING with the exception of possibly being in overscroll.
// so here we don't want to do anything right now; the overscroll will be
// corrected in preventedTouchFinished().
return false;
}
// ensure we snap back if we're overscrolled
bounce();
return false;
@ -812,9 +827,9 @@ class JavaPanZoomController
if (FloatUtils.fuzzyEquals(displacement.x, 0.0f) && FloatUtils.fuzzyEquals(displacement.y, 0.0f)) {
return;
}
if (mDefaultPrevented || mSubscroller.scrollBy(displacement)) {
if (mSubscroller.scrollBy(displacement)) {
synchronized (mTarget.getLock()) {
mTarget.scrollMarginsBy(displacement.x, displacement.y);
mTarget.onSubdocumentScrollBy(displacement.x, displacement.y);
}
} else {
synchronized (mTarget.getLock()) {

View File

@ -19,7 +19,7 @@ public interface PanZoomTarget {
public void setAnimationTarget(ImmutableViewportMetrics viewport);
public void setViewportMetrics(ImmutableViewportMetrics viewport);
public void scrollBy(float dx, float dy);
public void scrollMarginsBy(float dx, float dy);
public void onSubdocumentScrollBy(float dx, float dy);
public void panZoomStopped();
/** This triggers an (asynchronous) viewport update/redraw. */
public void forceRedraw(DisplayPortMetrics displayPort);

View File

@ -74,11 +74,11 @@ final class TouchEventHandler implements Tabs.OnTabsChangedListener {
// default-prevented or not (or we time out waiting for that).
private boolean mHoldInQueue;
// false if the current event block has been default-prevented. In this case,
// we still pass the event to both Gecko and the pan/zoom controller, but the
// latter will not use it to scroll content. It may still use the events for
// other things, such as making the dynamic toolbar visible.
private boolean mAllowDefaultAction;
// true if we should dispatch incoming events to the gesture detector and the pan/zoom
// controller. if this is false, then the current block of events has been
// default-prevented, and we should not dispatch these events (although we'll still send
// them to gecko listeners).
private boolean mDispatchEvents;
// this next variable requires some explanation. strap yourself in.
//
@ -128,7 +128,7 @@ final class TouchEventHandler implements Tabs.OnTabsChangedListener {
mGestureDetector = new GestureDetector(context, mPanZoomController);
mScaleGestureDetector = new SimpleScaleGestureDetector(mPanZoomController);
mListenerTimeoutProcessor = new ListenerTimeoutProcessor();
mAllowDefaultAction = true;
mDispatchEvents = true;
mGestureDetector.setOnDoubleTapListener(mPanZoomController);
@ -145,10 +145,10 @@ final class TouchEventHandler implements Tabs.OnTabsChangedListener {
// this is the start of a new block of events! whee!
mHoldInQueue = mWaitForTouchListeners;
// Set mAllowDefaultAction to true so that in the event we dispatch events, the
// PanZoomController doesn't treat them as if they've been prevent-defaulted
// when they haven't.
mAllowDefaultAction = true;
// Set mDispatchEvents to true so that we are guaranteed to either queue these
// events or dispatch them. The only time we should not do either is once we've
// heard back from content to preventDefault this block.
mDispatchEvents = true;
if (mHoldInQueue) {
// if the new block we are starting is the current block (i.e. there are no
// other blocks waiting in the queue, then we should let the pan/zoom controller
@ -170,12 +170,17 @@ final class TouchEventHandler implements Tabs.OnTabsChangedListener {
mView.postDelayed(mListenerTimeoutProcessor, EVENT_LISTENER_TIMEOUT);
}
// if we need to hold the events, add it to the queue, otherwise dispatch
// it directly.
// if we need to hold the events, add it to the queue. if we need to dispatch
// it directly, do that. it is possible that both mHoldInQueue and mDispatchEvents
// are false, in which case we are processing a block of events that we know
// has been default-prevented. in that case we don't keep the events as we don't
// need them (but we still pass them to the gecko listener).
if (mHoldInQueue) {
mEventQueue.add(MotionEvent.obtain(event));
} else {
dispatchEvent(event, mAllowDefaultAction);
} else if (mDispatchEvents) {
dispatchEvent(event);
} else if (touchFinished(event)) {
mPanZoomController.preventedTouchFinished();
}
return false;
@ -219,7 +224,7 @@ final class TouchEventHandler implements Tabs.OnTabsChangedListener {
/**
* Dispatch the event to the gesture detectors and the pan/zoom controller.
*/
private void dispatchEvent(MotionEvent event, boolean allowDefaultAction) {
private void dispatchEvent(MotionEvent event) {
if (mGestureDetector.onTouchEvent(event)) {
return;
}
@ -227,7 +232,7 @@ final class TouchEventHandler implements Tabs.OnTabsChangedListener {
if (mScaleGestureDetector.isInProgress()) {
return;
}
mPanZoomController.handleEvent(event, !allowDefaultAction);
mPanZoomController.handleEvent(event);
}
/**
@ -235,6 +240,13 @@ final class TouchEventHandler implements Tabs.OnTabsChangedListener {
* whether it has been default-prevented or not.
*/
private void processEventBlock(boolean allowDefaultAction) {
if (!allowDefaultAction) {
// if the block has been default-prevented, cancel whatever stuff we had in
// progress in the gesture detector and pan zoom controller
long now = SystemClock.uptimeMillis();
dispatchEvent(MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0));
}
if (mEventQueue.isEmpty()) {
Log.e(LOGTAG, "Unexpected empty event queue in processEventBlock!", new Exception());
return;
@ -251,7 +263,13 @@ final class TouchEventHandler implements Tabs.OnTabsChangedListener {
// that has already been dispatched.
if (event != null) {
dispatchEvent(event, allowDefaultAction);
// for each event we process, only dispatch it if the block hasn't been
// default-prevented.
if (allowDefaultAction) {
dispatchEvent(event);
} else if (touchFinished(event)) {
mPanZoomController.preventedTouchFinished();
}
}
if (mEventQueue.isEmpty()) {
// we have processed the backlog of events, and are all caught up.
@ -260,7 +278,7 @@ final class TouchEventHandler implements Tabs.OnTabsChangedListener {
// remaining events in this block (which is still ongoing) without
// having to put them in the queue.
mHoldInQueue = false;
mAllowDefaultAction = allowDefaultAction;
mDispatchEvents = allowDefaultAction;
break;
}
event = mEventQueue.peek();

View File

@ -43,8 +43,8 @@ public class HomeBanner extends LinearLayout
// Used to detect for upwards scroll to push banner all the way up
private boolean mSnapBannerToTop;
// Tracks if the banner has been enabled by HomePager to avoid race conditions.
private boolean mEnabled = false;
// Tracks whether or not the banner should be shown.
private boolean mActive = false;
// The user is currently swiping between HomePager pages
private boolean mScrollingPages = false;
@ -138,7 +138,11 @@ public class HomeBanner extends LinearLayout
public void run() {
mTextView.setText(text);
setVisibility(VISIBLE);
animateUp();
// Animate the banner if it is currently active.
if (mActive) {
animateUp();
}
}
});
} catch (JSONException e) {
@ -161,14 +165,20 @@ public class HomeBanner extends LinearLayout
});
}
public void setEnabled(boolean enabled) {
public void setActive(boolean active) {
// No need to animate if not changing
if (mEnabled == enabled) {
if (mActive == active) {
return;
}
mEnabled = enabled;
if (enabled) {
mActive = active;
// Don't animate if the banner isn't visible.
if (getVisibility() != View.VISIBLE) {
return;
}
if (active) {
animateUp();
} else {
animateDown();
@ -176,14 +186,9 @@ public class HomeBanner extends LinearLayout
}
private void animateUp() {
// Check to make sure that message has been received and the banner has been enabled.
// Necessary to avoid race conditions between show() and handleMessage() calls.
if (!mEnabled || TextUtils.isEmpty(mTextView.getText()) || mUserSwipedDown) {
return;
}
// No need to animate if already translated.
if (ViewHelper.getTranslationY(this) == 0) {
// Don't try to animate if the banner is already translated, or if the user swiped
// the banner down previously to hide it.
if (ViewHelper.getTranslationY(this) == 0 || mUserSwipedDown) {
return;
}
@ -193,7 +198,7 @@ public class HomeBanner extends LinearLayout
}
private void animateDown() {
// No need to animate if already translated or gone.
// Don't try to animate if the banner is already translated.
if (ViewHelper.getTranslationY(this) == getHeight()) {
return;
}
@ -204,7 +209,7 @@ public class HomeBanner extends LinearLayout
}
public void handleHomeTouch(MotionEvent event) {
if (!mEnabled || getVisibility() == GONE || mScrollingPages) {
if (!mActive || getVisibility() == GONE || mScrollingPages) {
return;
}

View File

@ -248,9 +248,9 @@ public class HomePager extends ViewPager {
}
public void onToolbarFocusChange(boolean hasFocus) {
// We should only enable the banner if the toolbar is not focused and we are on the default page
final boolean enabled = !hasFocus && getCurrentItem() == mDefaultPageIndex;
mHomeBanner.setEnabled(enabled);
// We should only make the banner active if the toolbar is not focused and we are on the default page
final boolean active = !hasFocus && getCurrentItem() == mDefaultPageIndex;
mHomeBanner.setActive(active);
}
private void updateUiFromPanelConfigs(List<PanelConfig> panelConfigs) {
@ -265,7 +265,7 @@ public class HomePager extends ViewPager {
}
if (mHomeBanner != null) {
mHomeBanner.setEnabled(false);
mHomeBanner.setActive(false);
}
final HomeAdapter adapter = (HomeAdapter) getAdapter();
@ -345,7 +345,7 @@ public class HomePager extends ViewPager {
}
if (mHomeBanner != null) {
mHomeBanner.setEnabled(position == mDefaultPageIndex);
mHomeBanner.setActive(position == mDefaultPageIndex);
}
}

View File

@ -92,6 +92,7 @@ public class IconGridInput extends PromptInput implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mSelected = position;
notifyListeners(Integer.toString(position));
}
@Override

View File

@ -38,7 +38,8 @@ import android.widget.TextView;
import java.util.ArrayList;
public class Prompt implements OnClickListener, OnCancelListener, OnItemClickListener {
public class Prompt implements OnClickListener, OnCancelListener, OnItemClickListener,
PromptInput.OnChangeListener {
private static final String LOGTAG = "GeckoPromptService";
private String[] mButtons;
@ -148,6 +149,10 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
* selected attribute to an array of booleans.
*/
private void addListResult(final JSONObject result, int which) {
if (mAdapter == null) {
return;
}
try {
JSONArray selected = new JSONArray();
@ -203,23 +208,7 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
@Override
public void onClick(DialogInterface dialog, int which) {
ThreadUtils.assertOnUiThread();
JSONObject ret = new JSONObject();
try {
addButtonResult(ret, which);
addInputValues(ret);
if (mAdapter != null) {
addListResult(ret, which);
}
} catch(Exception ex) {
Log.i(LOGTAG, "Error building return: " + ex);
}
if (dialog != null) {
dialog.dismiss();
}
finishDialog(ret);
closeDialog(which);
}
/* Adds a set of list items to the prompt. This can be used for either context menu type dialogs, checked lists,
@ -275,7 +264,12 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
*/
private void addSingleSelectList(AlertDialog.Builder builder, PromptListItem[] listItems) {
mAdapter = new PromptListAdapter(mContext, R.layout.select_dialog_singlechoice, listItems);
builder.setSingleChoiceItems(mAdapter, mAdapter.getSelectedIndex(), this);
builder.setSingleChoiceItems(mAdapter, mAdapter.getSelectedIndex(), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
closeIfNoButtons(which);
}
});
}
/* Shows a single-select list.
@ -360,6 +354,20 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ThreadUtils.assertOnUiThread();
mAdapter.toggleSelected(position);
// If there are no buttons on this dialog, then we take selecting an item as a sign to close
// the dialog. Note that means it will be hard to select multiple things in this list, but
// given there is no way to confirm+close the dialog, it seems reasonable.
closeIfNoButtons(position);
}
private boolean closeIfNoButtons(int selected) {
ThreadUtils.assertOnUiThread();
if (mButtons == null || mButtons.length == 0) {
closeDialog(selected);
return true;
}
return false;
}
/* @DialogInterface.OnCancelListener
@ -387,6 +395,20 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
finishDialog(ret);
}
/* Called any time we're closing the dialog to cleanup and notify listeners that the dialog
* is closing.
*/
private void closeDialog(int which) {
JSONObject ret = new JSONObject();
mDialog.dismiss();
addButtonResult(ret, which);
addListResult(ret, which);
addInputValues(ret);
finishDialog(ret);
}
/* Called any time we're closing the dialog to cleanup and notify listeners that the dialog
* is closing.
*/
@ -421,6 +443,7 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
for (int i = 0; i < mInputs.length; i++) {
try {
mInputs[i] = PromptInput.getInput(inputs.getJSONObject(i));
mInputs[i].setListener(this);
} catch(Exception ex) { }
}
@ -437,6 +460,15 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
show(title, text, menuitems, choiceMode);
}
// Called when the prompt inputs on the dialog change
@Override
public void onChange(PromptInput input) {
// If there are no buttons on this dialog, assuming that "changing" an input
// means something was selected and we can close. This provides a way to tap
// on a list item and close the dialog automatically.
closeIfNoButtons(-1);
}
private static JSONArray getSafeArray(JSONObject json, String key) {
try {
return json.getJSONArray(key);

View File

@ -38,9 +38,18 @@ public class PromptInput {
protected final String mType;
protected final String mId;
protected final String mValue;
protected OnChangeListener mListener;
protected View mView;
public static final String LOGTAG = "GeckoPromptInput";
public interface OnChangeListener {
public void onChange(PromptInput input);
}
public void setListener(OnChangeListener listener) {
mListener = listener;
}
public static class EditInput extends PromptInput {
protected final String mHint;
protected final boolean mAutofocus;
@ -376,4 +385,10 @@ public class PromptInput {
public boolean canApplyInputStyle() {
return true;
}
protected void notifyListeners(String val) {
if (mListener != null) {
mListener.onChange(this);
}
}
}

View File

@ -104,6 +104,7 @@ public class TabInput extends PromptInput implements AdapterView.OnItemClickList
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ThreadUtils.assertOnUiThread();
mPosition = position;
notifyListeners(Integer.toString(position));
}
}

View File

@ -3930,6 +3930,11 @@ Tab.prototype = {
} catch (e) {}
}
// Update the page actions URI for helper apps.
if (BrowserApp.selectedTab == this) {
ExternalApps.updatePageActionUri(fixedURI);
}
let message = {
type: "Content:LocationChange",
tabID: this.id,
@ -4538,7 +4543,6 @@ var BrowserEventHandler = {
let [x, y] = [data.x, data.y];
if (ElementTouchHelper.isElementClickable(element)) {
[x, y] = this._moveClickPoint(element, x, y);
element = ElementTouchHelper.anyElementFromPoint(x, y);
}
// Was the element already focused before it was clicked?
@ -7852,7 +7856,7 @@ let Reader = {
};
var ExternalApps = {
_contextMenuId: -1,
_contextMenuId: null,
// extend _getLink to pickup html5 media links.
_getMediaLink: function(aElement) {
@ -7884,7 +7888,10 @@ var ExternalApps = {
},
uninit: function helper_uninit() {
NativeWindow.contextmenus.remove(this._contextMenuId);
if (this._contextMenuId !== null) {
NativeWindow.contextmenus.remove(this._contextMenuId);
}
this._contextMenuId = null;
},
filter: {
@ -7920,8 +7927,12 @@ var ExternalApps = {
});
},
_setUriForPageAction: function setUriForPageAction(uri, apps) {
updatePageActionUri: function updatePageActionUri(uri) {
this._pageActionUri = uri;
},
_setUriForPageAction: function setUriForPageAction(uri, apps) {
this.updatePageActionUri(uri);
// If the pageaction is already added, simply update the URI to be launched when 'onclick' is triggered.
if (this._pageActionId != undefined)
@ -7930,11 +7941,8 @@ var ExternalApps = {
this._pageActionId = NativeWindow.pageactions.add({
title: Strings.browser.GetStringFromName("openInApp.pageAction"),
icon: "drawable://icon_openinapp",
clickCallback: (function() {
let callback = function(app) {
app.launch(uri);
}
clickCallback: () => {
if (apps.length > 1) {
// Use the HelperApps prompt here to filter out any Http handlers
HelperApps.prompt(apps, {
@ -7944,15 +7952,15 @@ var ExternalApps = {
Strings.browser.GetStringFromName("openInApp.cancel")
]
}, function(result) {
if (result.button != 0)
if (result.button != 0) {
return;
callback(apps[result.icongrid0]);
}
apps[result.icongrid0].launch(this._pageActionUri);
});
} else {
callback(apps[0]);
apps[0].launch(this._pageActionUri);
}
}).bind(this)
}
});
},

View File

@ -118,8 +118,15 @@ function updateCountryCode(callback) {
*/
function updateSnippets() {
_httpGetRequest(gSnippetsURL, function(responseText) {
cacheSnippets(responseText);
updateBanner(responseText);
try {
let messages = JSON.parse(responseText);
updateBanner(messages);
// Only cache the response if it is valid JSON.
cacheSnippets(responseText);
} catch (e) {
Cu.reportError("Error parsing snippets responseText: " + e);
}
});
}
@ -139,7 +146,10 @@ function cacheSnippets(response) {
*/
function loadSnippetsFromCache() {
let promise = OS.File.read(gSnippetsPath);
promise.then(array => updateBanner(gDecoder.decode(array)), e => {
promise.then(array => {
let messages = JSON.parse(gDecoder.decode(array));
updateBanner(messages);
}, e => {
if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
Cu.reportError("Couldn't show snippets because cache does not exist yet.");
} else {
@ -155,8 +165,7 @@ var gMessageIds = [];
/**
* Updates set of snippets in the home banner message rotation.
*
* @param response responseText returned from snippets server.
* This should be a JSON array of message data JSON objects.
* @param messages JSON array of message data JSON objects.
* Each message object should have the following properties:
* - id (?): Unique identifier for this snippets message
* - text (string): Text to show as banner message
@ -164,15 +173,13 @@ var gMessageIds = [];
* - icon (data URI): Icon to appear in banner
* - target_geo (string): Country code for where this message should be shown (e.g. "US")
*/
function updateBanner(response) {
function updateBanner(messages) {
// Remove the current messages, if there are any.
gMessageIds.forEach(function(id) {
Home.banner.remove(id);
})
gMessageIds = [];
let messages = JSON.parse(response);
try {
let removedSnippetIds = JSON.parse(Services.prefs.getCharPref(SNIPPETS_REMOVED_IDS_PREF));
messages = messages.filter(function(message) {

View File

@ -12,7 +12,7 @@ Components.utils.import("resource://gre/modules/Messaging.jsm");
this.EXPORTED_SYMBOLS = ["Prompt"];
function log(msg) {
//Services.console.logStringMessage(msg);
Services.console.logStringMessage(msg);
}
function Prompt(aOptions) {

View File

@ -4159,9 +4159,9 @@ pref("social.enabled", false);
// providers that can install from their own website without user warnings.
// entries are
pref("social.whitelist", "https://mozsocial.cliqz.com,https://now.msn.com,https://mixi.jp");
// omma separated list of domain origins (e.g. https://domain.com) for directory
// websites (e.g. AMO) that can install providers for other sites
pref("social.directories", "https://addons.mozilla.org");
// comma separated list of domain origins (e.g. https://domain.com) for
// directory websites (e.g. AMO) that can install providers for other sites
pref("social.directories", "https://activations.mozilla.org");
// remote-install allows any website to activate a provider, with extended UI
// notifying user of installation. we can later pref off remote install if
// necessary. This does not affect whitelisted and directory installs.

View File

@ -22,6 +22,7 @@ chrome.google.com: did not receive HSTS header (error ignored - included regardl
cloud.google.com: did not receive HSTS header (error ignored - included regardless)
code.google.com: did not receive HSTS header (error ignored - included regardless)
codereview.chromium.org: did not receive HSTS header (error ignored - included regardless)
conformal.com: could not connect to host
crate.io: did not receive HSTS header
crowdcurity.com: did not receive HSTS header
crypto.is: did not receive HSTS header
@ -37,7 +38,6 @@ espra.com: could not connect to host
fatzebra.com.au: did not receive HSTS header
fj.simple.com: did not receive HSTS header
get.zenpayroll.com: did not receive HSTS header
getcloak.com: max-age too low: 2678400
glass.google.com: did not receive HSTS header (error ignored - included regardless)
gmail.com: did not receive HSTS header
gocardless.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 125" data: no]
@ -64,7 +64,6 @@ lumi.do: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FA
mail.google.com: did not receive HSTS header (error ignored - included regardless)
market.android.com: did not receive HSTS header (error ignored - included regardless)
medium.com: max-age too low: 2592000
members.nearlyfreespeech.net: could not connect to host
my.alfresco.com: did not receive HSTS header
mydigipass.com: did not receive HSTS header
neonisi.com: could not connect to host
@ -82,6 +81,7 @@ platform.lookout.com: could not connect to host
play.google.com: did not receive HSTS header (error ignored - included regardless)
plus.google.com: did not receive HSTS header (error ignored - included regardless)
plus.sandbox.google.com: did not receive HSTS header (error ignored - included regardless)
prodpad.com: did not receive HSTS header
profiles.google.com: did not receive HSTS header (error ignored - included regardless)
rapidresearch.me: did not receive HSTS header
sah3.net: could not connect to host
@ -112,7 +112,6 @@ www.cueup.com: did not receive HSTS header
www.developer.mydigipass.com: could not connect to host
www.dropcam.com: max-age too low: 2592000
www.elanex.biz: did not receive HSTS header
www.getcloak.com: max-age too low: 2678400
www.gmail.com: did not receive HSTS header
www.googlemail.com: did not receive HSTS header
www.gov.uk: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 125" data: no]

View File

@ -8,7 +8,7 @@
/*****************************************************************************/
#include <stdint.h>
const PRTime gPreloadListExpirationTime = INT64_C(1403348819505000);
const PRTime gPreloadListExpirationTime = INT64_C(1404558396496000);
class nsSTSPreload
{
@ -19,6 +19,7 @@ class nsSTSPreload
static const nsSTSPreload kSTSPreloadList[] = {
{ "accounts.google.com", true },
{ "aclu.org", false },
{ "aladdinschools.appspot.com", false },
{ "alpha.irccloud.com", false },
{ "api.intercom.io", false },
@ -84,6 +85,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "forum.linode.com", false },
{ "forum.quantifiedself.com", true },
{ "gernert-server.de", true },
{ "getcloak.com", false },
{ "getlantern.org", false },
{ "glass.google.com", true },
{ "go.xero.com", false },
@ -119,6 +121,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "luneta.nearbuysystems.com", false },
{ "mail.de", true },
{ "mail.google.com", true },
{ "mailbox.org", false },
{ "makeyourlaws.org", false },
{ "manage.zenpayroll.com", false },
{ "manager.linode.com", false },
@ -189,11 +192,13 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "wiki.python.org", true },
{ "wiz.biz", true },
{ "writeapp.me", false },
{ "www.aclu.org", false },
{ "www.apollo-auto.com", true },
{ "www.braintreepayments.com", false },
{ "www.cyveillance.com", true },
{ "www.entropia.de", false },
{ "www.evernote.com", false },
{ "www.getcloak.com", false },
{ "www.gov.uk", false },
{ "www.grc.com", false },
{ "www.heliosnet.com", true },

View File

@ -34,6 +34,12 @@ this.TokenServerClientError = function TokenServerClientError(message) {
}
TokenServerClientError.prototype = new Error();
TokenServerClientError.prototype.constructor = TokenServerClientError;
TokenServerClientError.prototype._toStringFields = function() {
return {message: this.message};
}
TokenServerClientError.prototype.toString = function() {
return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
}
/**
* Represents a TokenServerClient error that occurred in the network layer.
@ -49,6 +55,9 @@ this.TokenServerClientNetworkError =
TokenServerClientNetworkError.prototype = new TokenServerClientError();
TokenServerClientNetworkError.prototype.constructor =
TokenServerClientNetworkError;
TokenServerClientNetworkError.prototype._toStringFields = function() {
return {error: this.error};
}
/**
* Represents a TokenServerClient error that occurred on the server.
@ -82,6 +91,7 @@ TokenServerClientNetworkError.prototype.constructor =
*/
this.TokenServerClientServerError =
function TokenServerClientServerError(message, cause="general") {
this.now = new Date().toISOString(); // may be useful to diagnose time-skew issues.
this.name = "TokenServerClientServerError";
this.message = message || "Server error.";
this.cause = cause;
@ -90,6 +100,20 @@ TokenServerClientServerError.prototype = new TokenServerClientError();
TokenServerClientServerError.prototype.constructor =
TokenServerClientServerError;
TokenServerClientServerError.prototype._toStringFields = function() {
let fields = {
now: this.now,
message: this.message,
cause: this.cause,
};
if (this.response) {
fields.response_body = this.response.body;
fields.response_headers = this.response.headers;
fields.response_status = this.response.status;
}
return fields;
};
/**
* Represents a client to the Token Server.
*

View File

@ -41,6 +41,164 @@ let publicProperties = [
"whenVerified"
];
// An AccountState object holds all state related to one specific account.
// Only one AccountState is ever "current" in the FxAccountsInternal object -
// whenever a user logs out or logs in, the current AccountState is discarded,
// making it impossible for the wrong state or state data to be accidentally
// used.
// In addition, it has some promise-related helpers to ensure that if an
// attempt is made to resolve a promise on a "stale" state (eg, if an
// operation starts, but a different user logs in before the operation
// completes), the promise will be rejected.
// It is intended to be used thusly:
// somePromiseBasedFunction: function() {
// let currentState = this.currentAccountState;
// return someOtherPromiseFunction().then(
// data => currentState.resolve(data)
// );
// }
// If the state has changed between the function being called and the promise
// being resolved, the .resolve() call will actually be rejected.
AccountState = function(fxaInternal) {
this.fxaInternal = fxaInternal;
};
AccountState.prototype = {
cert: null,
keyPair: null,
signedInUser: null,
whenVerifiedPromise: null,
whenKeysReadyPromise: null,
get isCurrent() this.fxaInternal && this.fxaInternal.currentAccountState === this,
abort: function() {
if (this.whenVerifiedPromise) {
this.whenVerifiedPromise.reject(
new Error("Verification aborted; Another user signing in"));
this.whenVerifiedPromise = null;
}
if (this.whenKeysReadyPromise) {
this.whenKeysReadyPromise.reject(
new Error("Verification aborted; Another user signing in"));
this.whenKeysReadyPromise = null;
}
this.cert = null;
this.keyPair = null;
this.signedInUser = null;
this.fxaInternal = null;
},
getUserAccountData: function() {
// Skip disk if user is cached.
if (this.signedInUser) {
return this.resolve(this.signedInUser.accountData);
}
return this.fxaInternal.signedInUserStorage.get().then(
user => {
log.debug("getUserAccountData -> " + JSON.stringify(user));
if (user && user.version == this.version) {
log.debug("setting signed in user");
this.signedInUser = user;
}
return this.resolve(user ? user.accountData : null);
},
err => {
if (err instanceof OS.File.Error && err.becauseNoSuchFile) {
// File hasn't been created yet. That will be done
// on the first call to getSignedInUser
return this.resolve(null);
}
return this.reject(err);
}
);
},
setUserAccountData: function(accountData) {
return this.fxaInternal.signedInUserStorage.get().then(record => {
if (!this.isCurrent) {
return this.reject(new Error("Another user has signed in"));
}
record.accountData = accountData;
this.signedInUser = record;
return this.fxaInternal.signedInUserStorage.set(record)
.then(() => this.resolve(accountData));
});
},
getCertificate: function(data, keyPair, mustBeValidUntil) {
log.debug("getCertificate" + JSON.stringify(this.signedInUser));
// TODO: get the lifetime from the cert's .exp field
if (this.cert && this.cert.validUntil > mustBeValidUntil) {
log.debug(" getCertificate already had one");
return this.resolve(this.cert.cert);
}
// else get our cert signed
let willBeValidUntil = this.fxaInternal.now() + CERT_LIFETIME;
return this.fxaInternal.getCertificateSigned(data.sessionToken,
keyPair.serializedPublicKey,
CERT_LIFETIME).then(
cert => {
this.cert = {
cert: cert,
validUntil: willBeValidUntil
};
return cert;
}
).then(result => this.resolve(result));
},
getKeyPair: function(mustBeValidUntil) {
if (this.keyPair && (this.keyPair.validUntil > mustBeValidUntil)) {
log.debug("getKeyPair: already have a keyPair");
return this.resolve(this.keyPair.keyPair);
}
// Otherwse, create a keypair and set validity limit.
let willBeValidUntil = this.fxaInternal.now() + KEY_LIFETIME;
let d = Promise.defer();
jwcrypto.generateKeyPair("DS160", (err, kp) => {
if (err) {
return this.reject(err);
}
this.keyPair = {
keyPair: kp,
validUntil: willBeValidUntil
};
log.debug("got keyPair");
delete this.cert;
d.resolve(this.keyPair.keyPair);
});
return d.promise.then(result => this.resolve(result));
},
resolve: function(result) {
if (!this.isCurrent) {
log.info("An accountState promise was resolved, but was actually rejected" +
" due to a different user being signed in. Originally resolved" +
" with: " + result);
return Promise.reject(new Error("A different user signed in"));
}
return Promise.resolve(result);
},
reject: function(error) {
// It could be argued that we should just let it reject with the original
// error - but this runs the risk of the error being (eg) a 401, which
// might cause the consumer to attempt some remediation and cause other
// problems.
if (!this.isCurrent) {
log.info("An accountState promise was rejected, but we are ignoring that" +
"reason and rejecting it due to a different user being signed in." +
"Originally rejected with: " + reason);
return Promise.reject(new Error("A different user signed in"));
}
return Promise.reject(error);
},
}
/**
* The public API's constructor.
*/
@ -70,9 +228,6 @@ this.FxAccounts = function (mockInternal) {
* The internal API's constructor.
*/
function FxAccountsInternal() {
this.cert = null;
this.keyPair = null;
this.signedInUser = null;
this.version = DATA_FORMAT_VERSION;
// Make a local copy of these constants so we can mock it in testing
@ -89,11 +244,10 @@ function FxAccountsInternal() {
// conceivable that while we are waiting to verify one identity, a caller
// could start verification on a second, different identity, we need to be
// able to abort all work on the first sign-in process. The currentTimer and
// generationCount are used for this purpose.
this.whenVerifiedPromise = null;
this.whenKeysReadyPromise = null;
// currentAccountState are used for this purpose.
// (XXX - should the timer be directly on the currentAccountState?)
this.currentTimer = null;
this.generationCount = 0;
this.currentAccountState = new AccountState(this);
this.fxAccountsClient = new FxAccountsClient();
@ -184,7 +338,8 @@ FxAccountsInternal.prototype = {
* or null if no user is signed in.
*/
getSignedInUser: function getSignedInUser() {
return this.getUserAccountData().then(data => {
let currentState = this.currentAccountState;
return currentState.getUserAccountData().then(data => {
if (!data) {
return null;
}
@ -195,7 +350,7 @@ FxAccountsInternal.prototype = {
this.startVerifiedCheck(data);
}
return data;
});
}).then(result => currentState.resolve(result));
},
/**
@ -222,8 +377,9 @@ FxAccountsInternal.prototype = {
this.abortExistingFlow();
let record = {version: this.version, accountData: credentials};
let currentState = this.currentAccountState;
// Cache a clone of the credentials object.
this.signedInUser = JSON.parse(JSON.stringify(record));
currentState.signedInUser = JSON.parse(JSON.stringify(record));
// This promise waits for storage, but not for verification.
// We're telling the caller that this is durable now.
@ -232,7 +388,7 @@ FxAccountsInternal.prototype = {
if (!this.isUserEmailVerified(credentials)) {
this.startVerifiedCheck(credentials);
}
});
}).then(result => currentState.resolve(result));
},
/**
@ -241,8 +397,9 @@ FxAccountsInternal.prototype = {
*/
getAssertion: function getAssertion(audience) {
log.debug("enter getAssertion()");
let currentState = this.currentAccountState;
let mustBeValidUntil = this.now() + ASSERTION_LIFETIME;
return this.getUserAccountData().then(data => {
return currentState.getUserAccountData().then(data => {
if (!data) {
// No signed-in user
return null;
@ -251,13 +408,13 @@ FxAccountsInternal.prototype = {
// Signed-in user has not verified email
return null;
}
return this.getKeyPair(mustBeValidUntil).then(keyPair => {
return this.getCertificate(data, keyPair, mustBeValidUntil)
return currentState.getKeyPair(mustBeValidUntil).then(keyPair => {
return currentState.getCertificate(data, keyPair, mustBeValidUntil)
.then(cert => {
return this.getAssertionFromCert(data, keyPair, cert, audience);
});
});
});
}).then(result => currentState.resolve(result));
},
/**
@ -265,12 +422,13 @@ FxAccountsInternal.prototype = {
*
*/
resendVerificationEmail: function resendVerificationEmail() {
let currentState = this.currentAccountState;
return this.getSignedInUser().then(data => {
// If the caller is asking for verification to be re-sent, and there is
// no signed-in user to begin with, this is probably best regarded as an
// error.
if (data) {
this.pollEmailStatus(data.sessionToken, "start");
this.pollEmailStatus(currentState, data.sessionToken, "start");
return this.fxAccountsClient.resendVerificationEmail(data.sessionToken);
}
throw new Error("Cannot resend verification email; no signed-in user");
@ -286,25 +444,13 @@ FxAccountsInternal.prototype = {
clearTimeout(this.currentTimer);
this.currentTimer = 0;
}
this.generationCount++;
log.debug("generationCount: " + this.generationCount);
if (this.whenVerifiedPromise) {
this.whenVerifiedPromise.reject(
new Error("Verification aborted; Another user signing in"));
this.whenVerifiedPromise = null;
}
if (this.whenKeysReadyPromise) {
this.whenKeysReadyPromise.reject(
new Error("KeyFetch aborted; Another user signing in"));
this.whenKeysReadyPromise = null;
}
this.currentAccountState.abort();
this.currentAccountState = new AccountState(this);
},
signOut: function signOut() {
this.abortExistingFlow();
this.signedInUser = null; // clear in-memory cache
this.currentAccountState.signedInUser = null; // clear in-memory cache
return this.signedInUserStorage.set(null).then(() => {
this.notifyObservers(ONLOGOUT_NOTIFICATION);
});
@ -330,38 +476,37 @@ FxAccountsInternal.prototype = {
* or null if no user is signed in
*/
getKeys: function() {
return this.getUserAccountData().then((data) => {
let currentState = this.currentAccountState;
return currentState.getUserAccountData().then((data) => {
if (!data) {
throw new Error("Can't get keys; User is not signed in");
}
if (data.kA && data.kB) {
return data;
}
if (!this.whenKeysReadyPromise) {
this.whenKeysReadyPromise = Promise.defer();
if (!currentState.whenKeysReadyPromise) {
currentState.whenKeysReadyPromise = Promise.defer();
this.fetchAndUnwrapKeys(data.keyFetchToken).then(data => {
if (this.whenKeysReadyPromise) {
this.whenKeysReadyPromise.resolve(data);
}
currentState.whenKeysReadyPromise.resolve(data);
});
}
return this.whenKeysReadyPromise.promise;
});
return currentState.whenKeysReadyPromise.promise;
}).then(result => currentState.resolve(result));
},
fetchAndUnwrapKeys: function(keyFetchToken) {
log.debug("fetchAndUnwrapKeys: token: " + keyFetchToken);
let currentState = this.currentAccountState;
return Task.spawn(function* task() {
// Sign out if we don't have a key fetch token.
if (!keyFetchToken) {
yield this.signOut();
return null;
}
let myGenerationCount = this.generationCount;
let {kA, wrapKB} = yield this.fetchKeys(keyFetchToken);
let data = yield this.getUserAccountData();
let data = yield currentState.getUserAccountData();
// Sanity check that the user hasn't changed out from under us
if (data.keyFetchToken !== keyFetchToken) {
@ -381,20 +526,13 @@ FxAccountsInternal.prototype = {
log.debug("Keys Obtained: kA=" + data.kA + ", kB=" + data.kB);
// Before writing any data, ensure that a new flow hasn't been
// started behind our backs.
if (this.generationCount !== myGenerationCount) {
return null;
}
yield this.setUserAccountData(data);
yield currentState.setUserAccountData(data);
// We are now ready for business. This should only be invoked once
// per setSignedInUser(), regardless of whether we've rebooted since
// setSignedInUser() was called.
this.notifyObservers(ONVERIFIED_NOTIFICATION);
return data;
}.bind(this));
}.bind(this)).then(result => currentState.resolve(result));
},
getAssertionFromCert: function(data, keyPair, cert, audience) {
@ -405,6 +543,7 @@ FxAccountsInternal.prototype = {
localtimeOffsetMsec: this.localtimeOffsetMsec,
now: this.now()
};
let currentState = this.currentAccountState;
// "audience" should look like "http://123done.org".
// The generated assertion will expire in two minutes.
jwcrypto.generateAssertion(cert, keyPair, audience, options, (err, signed) => {
@ -416,90 +555,20 @@ FxAccountsInternal.prototype = {
d.resolve(signed);
}
});
return d.promise;
},
getCertificate: function(data, keyPair, mustBeValidUntil) {
log.debug("getCertificate" + JSON.stringify(this.signedInUserStorage));
// TODO: get the lifetime from the cert's .exp field
if (this.cert && this.cert.validUntil > mustBeValidUntil) {
log.debug(" getCertificate already had one");
return Promise.resolve(this.cert.cert);
}
// else get our cert signed
let willBeValidUntil = this.now() + CERT_LIFETIME;
return this.getCertificateSigned(data.sessionToken,
keyPair.serializedPublicKey,
CERT_LIFETIME)
.then((cert) => {
this.cert = {
cert: cert,
validUntil: willBeValidUntil
};
return cert;
}
);
return d.promise.then(result => currentState.resolve(result));
},
getCertificateSigned: function(sessionToken, serializedPublicKey, lifetime) {
log.debug("getCertificateSigned: " + sessionToken + " " + serializedPublicKey);
return this.fxAccountsClient.signCertificate(sessionToken,
JSON.parse(serializedPublicKey),
lifetime);
},
getKeyPair: function(mustBeValidUntil) {
if (this.keyPair && (this.keyPair.validUntil > mustBeValidUntil)) {
log.debug("getKeyPair: already have a keyPair");
return Promise.resolve(this.keyPair.keyPair);
}
// Otherwse, create a keypair and set validity limit.
let willBeValidUntil = this.now() + KEY_LIFETIME;
let d = Promise.defer();
jwcrypto.generateKeyPair("DS160", (err, kp) => {
if (err) {
d.reject(err);
} else {
this.keyPair = {
keyPair: kp,
validUntil: willBeValidUntil
};
log.debug("got keyPair");
delete this.cert;
d.resolve(this.keyPair.keyPair);
}
});
return d.promise;
return this.fxAccountsClient.signCertificate(
sessionToken,
JSON.parse(serializedPublicKey),
lifetime
);
},
getUserAccountData: function() {
// Skip disk if user is cached.
if (this.signedInUser) {
return Promise.resolve(this.signedInUser.accountData);
}
let deferred = Promise.defer();
this.signedInUserStorage.get()
.then((user) => {
log.debug("getUserAccountData -> " + JSON.stringify(user));
if (user && user.version == this.version) {
log.debug("setting signed in user");
this.signedInUser = user;
}
deferred.resolve(user ? user.accountData : null);
},
(err) => {
if (err instanceof OS.File.Error && err.becauseNoSuchFile) {
// File hasn't been created yet. That will be done
// on the first call to getSignedInUser
deferred.resolve(null);
} else {
deferred.reject(err);
}
}
);
return deferred.promise;
return this.currentAccountState.getUserAccountData();
},
isUserEmailVerified: function isUserEmailVerified(data) {
@ -510,10 +579,11 @@ FxAccountsInternal.prototype = {
* Setup for and if necessary do email verification polling.
*/
loadAndPoll: function() {
return this.getUserAccountData()
let currentState = this.currentAccountState;
return currentState.getUserAccountData()
.then(data => {
if (data && !this.isUserEmailVerified(data)) {
this.pollEmailStatus(data.sessionToken, "start");
this.pollEmailStatus(currentState, data.sessionToken, "start");
}
return data;
});
@ -528,19 +598,22 @@ FxAccountsInternal.prototype = {
// obtains and stores kA and kB, it will fire the onverified observer
// notification.
return this.whenVerified(data)
.then((data) => this.getKeys(data));
.then(() => this.getKeys());
},
whenVerified: function(data) {
let currentState = this.currentAccountState;
if (data.verified) {
log.debug("already verified");
return Promise.resolve(data);
return currentState.resolve(data);
}
if (!this.whenVerifiedPromise) {
if (!currentState.whenVerifiedPromise) {
log.debug("whenVerified promise starts polling for verified email");
this.pollEmailStatus(data.sessionToken, "start");
this.pollEmailStatus(currentState, data.sessionToken, "start");
}
return this.whenVerifiedPromise.promise;
return currentState.whenVerifiedPromise.promise.then(
result => currentState.resolve(result)
);
},
notifyObservers: function(topic) {
@ -548,43 +621,35 @@ FxAccountsInternal.prototype = {
Services.obs.notifyObservers(null, topic, null);
},
pollEmailStatus: function pollEmailStatus(sessionToken, why) {
let myGenerationCount = this.generationCount;
log.debug("entering pollEmailStatus: " + why + " " + myGenerationCount);
// XXX - pollEmailStatus should maybe be on the AccountState object?
pollEmailStatus: function pollEmailStatus(currentState, sessionToken, why) {
log.debug("entering pollEmailStatus: " + why);
if (why == "start") {
// If we were already polling, stop and start again. This could happen
// if the user requested the verification email to be resent while we
// were already polling for receipt of an earlier email.
this.pollTimeRemaining = this.POLL_SESSION;
if (!this.whenVerifiedPromise) {
this.whenVerifiedPromise = Promise.defer();
if (!currentState.whenVerifiedPromise) {
currentState.whenVerifiedPromise = Promise.defer();
}
}
this.checkEmailStatus(sessionToken)
.then((response) => {
log.debug("checkEmailStatus -> " + JSON.stringify(response));
// Check to see if we're still current.
// If for some ghastly reason we are not, stop processing.
if (this.generationCount !== myGenerationCount) {
log.debug("generation count differs from " + this.generationCount + " - aborting");
log.debug("sessionToken on abort is " + sessionToken);
return;
}
if (response && response.verified) {
// Bug 947056 - Server should be able to tell FxAccounts.jsm to back
// off or stop polling altogether
this.getUserAccountData()
currentState.getUserAccountData()
.then((data) => {
data.verified = true;
return this.setUserAccountData(data);
return currentState.setUserAccountData(data);
})
.then((data) => {
// Now that the user is verified, we can proceed to fetch keys
if (this.whenVerifiedPromise) {
this.whenVerifiedPromise.resolve(data);
delete this.whenVerifiedPromise;
if (currentState.whenVerifiedPromise) {
currentState.whenVerifiedPromise.resolve(data);
delete currentState.whenVerifiedPromise;
}
});
} else {
@ -593,29 +658,20 @@ FxAccountsInternal.prototype = {
log.debug("time remaining: " + this.pollTimeRemaining);
if (this.pollTimeRemaining > 0) {
this.currentTimer = setTimeout(() => {
this.pollEmailStatus(sessionToken, "timer")}, this.POLL_STEP);
this.pollEmailStatus(currentState, sessionToken, "timer")}, this.POLL_STEP);
log.debug("started timer " + this.currentTimer);
} else {
if (this.whenVerifiedPromise) {
this.whenVerifiedPromise.reject(
if (currentState.whenVerifiedPromise) {
currentState.whenVerifiedPromise.reject(
new Error("User email verification timed out.")
);
delete this.whenVerifiedPromise;
delete currentState.whenVerifiedPromise;
}
}
}
});
},
setUserAccountData: function(accountData) {
return this.signedInUserStorage.get().then(record => {
record.accountData = accountData;
this.signedInUser = record;
return this.signedInUserStorage.set(record)
.then(() => accountData);
});
},
// Return the URI of the remote UI flows.
getAccountsURI: function() {
let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.uri");
@ -641,6 +697,7 @@ FxAccountsInternal.prototype = {
if (!/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
throw new Error("Firefox Accounts server must use HTTPS");
}
let currentState = this.currentAccountState;
// but we need to append the email address onto a query string.
return this.getSignedInUser().then(accountData => {
if (!accountData) {
@ -649,7 +706,7 @@ FxAccountsInternal.prototype = {
let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&";
newQueryPortion += "email=" + encodeURIComponent(accountData.email);
return url + newQueryPortion;
});
}).then(result => currentState.resolve(result));
}
};

View File

@ -402,8 +402,10 @@ add_task(function test_getAssertion() {
_("ASSERTION: " + assertion + "\n");
let pieces = assertion.split("~");
do_check_eq(pieces[0], "cert1");
do_check_neq(fxa.internal.keyPair, undefined);
_(fxa.internal.keyPair.validUntil + "\n");
let keyPair = fxa.internal.currentAccountState.keyPair;
let cert = fxa.internal.currentAccountState.cert;
do_check_neq(keyPair, undefined);
_(keyPair.validUntil + "\n");
let p2 = pieces[1].split(".");
let header = JSON.parse(atob(p2[0]));
_("HEADER: " + JSON.stringify(header) + "\n");
@ -411,8 +413,8 @@ add_task(function test_getAssertion() {
let payload = JSON.parse(atob(p2[1]));
_("PAYLOAD: " + JSON.stringify(payload) + "\n");
do_check_eq(payload.aud, "audience.example.com");
do_check_eq(fxa.internal.keyPair.validUntil, start + KEY_LIFETIME);
do_check_eq(fxa.internal.cert.validUntil, start + CERT_LIFETIME);
do_check_eq(keyPair.validUntil, start + KEY_LIFETIME);
do_check_eq(cert.validUntil, start + CERT_LIFETIME);
_("delta: " + Date.parse(payload.exp - start) + "\n");
let exp = Number(payload.exp);
@ -449,8 +451,10 @@ add_task(function test_getAssertion() {
// the initial start time, to which they are relative, not the current value
// of "now".
do_check_eq(fxa.internal.keyPair.validUntil, start + KEY_LIFETIME);
do_check_eq(fxa.internal.cert.validUntil, start + CERT_LIFETIME);
keyPair = fxa.internal.currentAccountState.keyPair;
cert = fxa.internal.currentAccountState.cert;
do_check_eq(keyPair.validUntil, start + KEY_LIFETIME);
do_check_eq(cert.validUntil, start + CERT_LIFETIME);
exp = Number(payload.exp);
do_check_eq(exp, now + TWO_MINUTES_MS);
@ -469,8 +473,10 @@ add_task(function test_getAssertion() {
header = JSON.parse(atob(p2[0]));
payload = JSON.parse(atob(p2[1]));
do_check_eq(payload.aud, "fourth.example.com");
do_check_eq(fxa.internal.keyPair.validUntil, now + KEY_LIFETIME);
do_check_eq(fxa.internal.cert.validUntil, now + CERT_LIFETIME);
keyPair = fxa.internal.currentAccountState.keyPair;
cert = fxa.internal.currentAccountState.cert;
do_check_eq(keyPair.validUntil, now + KEY_LIFETIME);
do_check_eq(cert.validUntil, now + CERT_LIFETIME);
exp = Number(payload.exp);
do_check_eq(exp, now + TWO_MINUTES_MS);
@ -494,14 +500,15 @@ add_test(function test_resend_email() {
let fxa = new MockFxAccounts();
let alice = getTestUser("alice");
do_check_eq(fxa.internal.generationCount, 0);
let initialState = fxa.internal.currentAccountState;
// Alice is the user signing in; her email is unverified.
fxa.setSignedInUser(alice).then(() => {
log.debug("Alice signing in");
// We're polling for the first email
do_check_eq(fxa.internal.generationCount, 1);
do_check_true(fxa.internal.currentAccountState !== initialState);
let aliceState = fxa.internal.currentAccountState;
// The polling timer is ticking
do_check_true(fxa.internal.currentTimer > 0);
@ -517,7 +524,7 @@ add_test(function test_resend_email() {
do_check_eq(result, "alice's session token");
// Timer was not restarted
do_check_eq(fxa.internal.generationCount, 1);
do_check_true(fxa.internal.currentAccountState === aliceState);
// Timer is still ticking
do_check_true(fxa.internal.currentTimer > 0);

View File

@ -114,21 +114,21 @@ this.makeIdentityConfig = function(overrides) {
// config (or the default config if not specified).
this.configureFxAccountIdentity = function(authService,
config = makeIdentityConfig()) {
let MockInternal = {
signedInUser: {
version: DATA_FORMAT_VERSION,
accountData: config.fxaccount.user
},
getCertificate: function(data, keyPair, mustBeValidUntil) {
this.cert = {
validUntil: Date.now() + CERT_LIFETIME,
cert: "certificate",
};
return Promise.resolve(this.cert.cert);
},
};
let MockInternal = {};
let fxa = new FxAccounts(MockInternal);
fxa.internal.currentAccountState.signedInUser = {
version: DATA_FORMAT_VERSION,
accountData: config.fxaccount.user
};
fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) {
this.cert = {
validUntil: fxa.internal.now() + CERT_LIFETIME,
cert: "certificate",
};
return Promise.resolve(this.cert.cert);
};
let mockTSC = { // TokenServerClient
getTokenFromBrowserIDAssertion: function(uri, assertion, cb) {
config.fxaccount.token.uid = config.username;

View File

@ -55,11 +55,19 @@ function deriveKeyBundle(kB) {
/*
General authentication error for abstracting authentication
errors from multiple sources (e.g., from FxAccounts, TokenServer)
'message' is a string with a description of the error
errors from multiple sources (e.g., from FxAccounts, TokenServer).
details is additional details about the error - it might be a string, or
some other error object (which should do the right thing when toString() is
called on it)
*/
function AuthenticationError(message) {
this.message = message || "";
function AuthenticationError(details) {
this.details = details;
}
AuthenticationError.prototype = {
toString: function() {
return "AuthenticationError(" + this.details + ")";
}
}
this.BrowserIDManager = function BrowserIDManager() {
@ -162,11 +170,11 @@ this.BrowserIDManager.prototype = {
this._shouldHaveSyncKeyBundle = true; // but we probably don't have one...
this.whenReadyToAuthenticate.reject(err);
// report what failed...
this._log.error("Background fetch for key bundle failed: " + err.message);
this._log.error("Background fetch for key bundle failed: " + err);
});
// and we are done - the fetch continues on in the background...
}).then(null, err => {
this._log.error("Processing logged in account: " + err.message);
this._log.error("Processing logged in account: " + err);
});
},
@ -425,7 +433,7 @@ this.BrowserIDManager.prototype = {
let cb = function (err, token) {
if (err) {
log.info("TokenServerClient.getTokenFromBrowserIDAssertion() failed with: " + err.message);
return deferred.reject(new AuthenticationError(err.message));
return deferred.reject(new AuthenticationError(err));
} else {
log.debug("Successfully got a sync token");
return deferred.resolve(token);
@ -465,7 +473,7 @@ this.BrowserIDManager.prototype = {
// properly: auth error getting assertion, auth error getting token (invalid generation
// and client-state error)
if (err instanceof AuthenticationError) {
this._log.error("Authentication error in _fetchTokenForUser: " + err.message);
this._log.error("Authentication error in _fetchTokenForUser: " + err);
// Drop the sync key bundle, but still expect to have one.
// This will arrange for us to be in the right 'currentAuthState'
// such that UI will show the right error.

View File

@ -35,23 +35,23 @@ MockFxAccountsClient.prototype = {
};
function MockFxAccounts() {
return new FxAccounts({
let fxa = new FxAccounts({
_now_is: Date.now(),
now: function () {
return this._now_is;
},
getCertificate: function(data, keyPair, mustBeValidUntil) {
this.cert = {
validUntil: Date.now() + CERT_LIFETIME,
cert: "certificate",
};
return Promise.resolve(this.cert.cert);
},
fxAccountsClient: new MockFxAccountsClient()
});
fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) {
this.cert = {
validUntil: fxa.internal.now() + CERT_LIFETIME,
cert: "certificate",
};
return Promise.resolve(this.cert.cert);
};
return fxa;
}
function run_test() {
@ -147,7 +147,7 @@ add_test(function test_resourceAuthenticatorSkew() {
configureFxAccountIdentity(browseridManager, identityConfig);
// Ensure the new FxAccounts mock has a signed-in user.
fxa.internal.signedInUser = browseridManager._fxaService.internal.signedInUser;
fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
browseridManager._fxaService = fxa;
@ -200,7 +200,7 @@ add_test(function test_RESTResourceAuthenticatorSkew() {
configureFxAccountIdentity(browseridManager, identityConfig);
// Ensure the new FxAccounts mock has a signed-in user.
fxa.internal.signedInUser = browseridManager._fxaService.internal.signedInUser;
fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
browseridManager._fxaService = fxa;

View File

@ -9,4 +9,5 @@
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"/>
<script type="application/javascript" src="chrome://mochikit/content/browser-test.js"/>
<script type="application/javascript" src="chrome://mochikit/content/cc-analyzer.js"/>
<script type="application/javascript" src="chrome://mochikit/content/mochitest-e10s-utils.js"/>
</overlay>

View File

@ -17,6 +17,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services",
XPCOMUtils.defineLazyModuleGetter(this, "BrowserNewTabPreloader",
"resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizationTabPreloader",
"resource:///modules/CustomizationTabPreloader.jsm", "CustomizationTabPreloader");
window.addEventListener("load", testOnLoad, false);
function testOnLoad() {
@ -59,6 +62,9 @@ function testOnLoad() {
messageManager.loadFrameScript(listener, true);
messageManager.addMessageListener("chromeEvent", messageHandler);
}
if (gConfig.e10s) {
e10s_init();
}
}
function Tester(aTests, aDumper, aCallback) {
@ -426,6 +432,7 @@ Tester.prototype = {
BackgroundPageThumbs._destroy();
BrowserNewTabPreloader.uninit();
CustomizationTabPreloader.uninit();
SocialFlyout.unload();
SocialShare.uninit();
TabView.uninit();

View File

@ -5,6 +5,8 @@ mochikit.jar:
content/browser-test-overlay.xul (browser-test-overlay.xul)
content/cc-analyzer.js (cc-analyzer.js)
content/chrome-harness.js (chrome-harness.js)
content/mochitest-e10s-utils.js (mochitest-e10s-utils.js)
content/mochitest-e10s-utils-content.js (mochitest-e10s-utils-content.js)
content/harness.xul (harness.xul)
content/redirect.html (redirect.html)
content/server.js (server.js)

View File

@ -0,0 +1,16 @@
// This is the content script for mochitest-e10s-utils
// We hook up some events and forward them back to the parent for the tests
// This is only a partial solution to tests using these events - tests which
// check, eg, event.target is the content window are still likely to be
// confused.
// But it's a good start...
["load", "DOMContentLoaded", "pageshow"].forEach(eventName => {
addEventListener(eventName, function eventHandler(event) {
// Some tests also rely on load events from, eg, iframes, so we should see
// if we can do something sane to support that too.
if (event.target == content.document) {
sendAsyncMessage("Test:Event", {name: event.type});
}
}, true);
});

View File

@ -0,0 +1,87 @@
// Utilities for running tests in an e10s environment.
// There are some tricks/shortcuts that test code takes that we don't see
// in the real browser code. These include setting content.location.href
// (which doesn't work in test code with e10s enabled as the document object
// is yet to be created), waiting for certain events the main browser doesn't
// care about and so doesn't normally get special support, eg, the "pageshow"
// or "load" events).
// So we make some hacks to pretend these work in the test suite.
// Ideally all these hacks could be removed, but this can only happen when
// the tests are refactored to not use these tricks. But this would be a huge
// amount of work and is unlikely to happen anytime soon...
const CONTENT_URL = "chrome://mochikit/content/mochitest-e10s-utils-content.js";
// This is an object that is used as the "location" on a remote document or
// window. It will be overwritten as the real document and window are made
// available.
let locationStub = function(browser) {
this.browser = browser;
};
locationStub.prototype = {
get href() {
return this.browser.webNavigation.currentURI.spec;
},
set href(val) {
this.browser.loadURI(val);
},
assign: function(url) {
this.href = url;
}
};
// This object is used in place of contentWindow while we wait for it to be
// overwritten as the real window becomes available.
let TemporaryWindowStub = function(browser) {
this._locationStub = new locationStub(browser);
};
TemporaryWindowStub.prototype = {
// save poor developers from getting confused about why the window isn't
// working like a window should..
toString: function() {
return "[Window Stub for e10s tests]";
},
get location() {
return this._locationStub;
},
set location(val) {
this._locationStub.href = val;
},
get document() {
// so tests can say: document.location....
return this;
}
};
// An observer called when a new remote browser element is created. We replace
// the _contentWindow in new browsers with our TemporaryWindowStub object.
function observeNewFrameloader(subject, topic, data) {
let browser = subject.QueryInterface(Ci.nsIFrameLoader).ownerElement;
browser._contentWindow = new TemporaryWindowStub(browser);
}
function e10s_init() {
// Use the global message manager to inject a content script into all browsers.
let globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
globalMM.loadFrameScript(CONTENT_URL, true);
globalMM.addMessageListener("Test:Event", function(message) {
let event = document.createEvent('HTMLEvents');
event.initEvent(message.data.name, true, true, {});
message.target.dispatchEvent(event);
});
// We add an observer so we can notice new <browser> elements created
Services.obs.addObserver(observeNewFrameloader, "remote-browser-shown", false);
// Listen for an 'oop-browser-crashed' event and log it so people analysing
// test logs have a clue about what is going on.
window.addEventListener("oop-browser-crashed", (event) => {
let uri = event.target.currentURI;
Cu.reportError("remote browser crashed while on " +
(uri ? uri.spec : "<unknown>") + "\n");
}, true);
}

View File

@ -246,12 +246,18 @@ function migrateSettings() {
defaultManifest = Services.prefs.getDefaultBranch(null)
.getComplexValue(prefname, Ci.nsISupportsString).data;
defaultManifest = JSON.parse(defaultManifest);
} catch(e) {
// not a built-in, continue
}
if (defaultManifest) {
if (defaultManifest.shareURL && !manifest.shareURL) {
manifest.shareURL = defaultManifest.shareURL;
needsUpdate = true;
}
} catch(e) {
// not a built-in, continue
if (defaultManifest.version && (!manifest.version || defaultManifest.version > manifest.version)) {
manifest = defaultManifest;
needsUpdate = true;
}
}
if (needsUpdate) {
// the provider was installed with an older build, so we will update the