Merge latest green inbound changeset and mozilla-central
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -129,11 +129,8 @@ add_task(function MoveWidgetsInTwoWindows() {
|
||||
checkPalette(widgetId, method);
|
||||
}
|
||||
}
|
||||
otherWin.close();
|
||||
yield promiseWindowClosed(otherWin);
|
||||
otherWin = null;
|
||||
if (otherWin) {
|
||||
otherWin.close();
|
||||
}
|
||||
yield endCustomizing();
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
});
|
||||
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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.
|
||||
});
|
||||
|
||||
|
@ -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"));
|
||||
|
@ -42,7 +42,7 @@ add_task(function() {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
otherWindow.close();
|
||||
yield promiseWindowClosed(otherWindow);
|
||||
}
|
||||
btn.remove();
|
||||
btn2.remove();
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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.
|
||||
|
@ -59,7 +59,7 @@ add_task(function() {
|
||||
"Widget should be in navbar in other window.");
|
||||
}
|
||||
}
|
||||
otherWin.close();
|
||||
yield promiseWindowClosed(otherWin);
|
||||
});
|
||||
|
||||
add_task(function asyncCleanup() {
|
||||
|
@ -38,7 +38,7 @@ add_task(function() {
|
||||
gNavToolbox.removeEventListener("customizationchange", handler);
|
||||
otherToolbox.removeEventListener("customizationchange", handler);
|
||||
|
||||
newWindow.close();
|
||||
yield promiseWindowClosed(newWindow);
|
||||
});
|
||||
|
||||
add_task(function asyncCleanup() {
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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.">
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
|
245
browser/modules/CustomizationTabPreloader.jsm
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
@ -11,6 +11,7 @@ EXTRA_JS_MODULES += [
|
||||
'BrowserUITelemetry.jsm',
|
||||
'ContentClick.jsm',
|
||||
'ContentLinkHandler.jsm',
|
||||
'CustomizationTabPreloader.jsm',
|
||||
'Feeds.jsm',
|
||||
'NetworkPrioritizer.jsm',
|
||||
'offlineAppCache.jsm',
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
BIN
browser/themes/linux/webRTC-shareMicrophone-16.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
browser/themes/linux/webRTC-shareMicrophone-64.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
browser/themes/linux/webRTC-sharingMicrophone-16.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
@ -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@
|
||||
|
@ -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)
|
||||
|
BIN
browser/themes/osx/webRTC-shareMicrophone-16.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
browser/themes/osx/webRTC-shareMicrophone-16@2x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
browser/themes/osx/webRTC-shareMicrophone-64.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
browser/themes/osx/webRTC-shareMicrophone-64@2x.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
browser/themes/osx/webRTC-sharingMicrophone-16.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
browser/themes/osx/webRTC-sharingMicrophone-16@2x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
BIN
browser/themes/windows/webRTC-shareMicrophone-16.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
browser/themes/windows/webRTC-shareMicrophone-64.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
browser/themes/windows/webRTC-sharingMicrophone-16.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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]
|
||||
|
@ -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 },
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
16
testing/mochitest/mochitest-e10s-utils-content.js
Normal 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);
|
||||
});
|
87
testing/mochitest/mochitest-e10s-utils.js
Normal 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);
|
||||
}
|
@ -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
|
||||
|