mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge m-c to inbound.
This commit is contained in:
commit
63ae93c3cb
@ -212,6 +212,7 @@ let FormAssistant = {
|
||||
scrollIntoViewTimeout: null,
|
||||
_focusedElement: null,
|
||||
_focusCounter: 0, // up one for every time we focus a new element
|
||||
_observer: null,
|
||||
_documentEncoder: null,
|
||||
_editor: null,
|
||||
_editing: false,
|
||||
@ -230,12 +231,18 @@ let FormAssistant = {
|
||||
},
|
||||
|
||||
setFocusedElement: function fa_setFocusedElement(element) {
|
||||
let self = this;
|
||||
|
||||
if (element === this.focusedElement)
|
||||
return;
|
||||
|
||||
if (this.focusedElement) {
|
||||
this.focusedElement.removeEventListener('mousedown', this);
|
||||
this.focusedElement.removeEventListener('mouseup', this);
|
||||
if (this._observer) {
|
||||
this._observer.disconnect();
|
||||
this._observer = null;
|
||||
}
|
||||
if (!element) {
|
||||
this.focusedElement.blur();
|
||||
}
|
||||
@ -265,6 +272,24 @@ let FormAssistant = {
|
||||
// element.
|
||||
this._editor.addEditorObserver(this);
|
||||
}
|
||||
|
||||
// If our focusedElement is removed from DOM we want to handle it properly
|
||||
let MutationObserver = element.ownerDocument.defaultView.MutationObserver;
|
||||
this._observer = new MutationObserver(function(mutations) {
|
||||
var del = [].some.call(mutations, function(m) {
|
||||
return [].some.call(m.removedNodes, function(n) {
|
||||
return n === element;
|
||||
});
|
||||
});
|
||||
if (del && element === this.focusedElement) {
|
||||
// item was deleted, fake a blur so all state gets set correctly
|
||||
self.handleEvent({ target: element, type: "blur" });
|
||||
}
|
||||
});
|
||||
|
||||
this._observer.observe(element.parentNode, {
|
||||
childList: true
|
||||
});
|
||||
}
|
||||
|
||||
this.focusedElement = element;
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "085648d44f2a1ad2c73b0eb4225c6448104e70f5",
|
||||
"revision": "e4f586d6a2c7a8ff3a676d2ddfecc5b2de94f429",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -336,7 +336,7 @@ pref("browser.download.manager.scanWhenDone", true);
|
||||
pref("browser.download.manager.resumeOnWakeDelay", 10000);
|
||||
|
||||
// Enables the asynchronous Downloads API in the Downloads Panel.
|
||||
pref("browser.download.useJSTransfer", false);
|
||||
pref("browser.download.useJSTransfer", true);
|
||||
|
||||
// This allows disabling the Downloads Panel in favor of the old interface.
|
||||
pref("browser.download.useToolkitUI", false);
|
||||
|
@ -112,10 +112,7 @@
|
||||
}
|
||||
|
||||
function reloadProvider() {
|
||||
Social.enabled = false;
|
||||
Services.tm.mainThread.dispatch(function() {
|
||||
Social.enabled = true;
|
||||
}, Components.interfaces.nsIThread.DISPATCH_NORMAL);
|
||||
Social.provider.reload();
|
||||
}
|
||||
|
||||
parseQueryString();
|
||||
|
@ -36,6 +36,7 @@ SocialUI = {
|
||||
Services.obs.addObserver(this, "social:frameworker-error", false);
|
||||
Services.obs.addObserver(this, "social:provider-set", false);
|
||||
Services.obs.addObserver(this, "social:providers-changed", false);
|
||||
Services.obs.addObserver(this, "social:provider-reload", false);
|
||||
|
||||
Services.prefs.addObserver("social.sidebar.open", this, false);
|
||||
Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
|
||||
@ -60,6 +61,7 @@ SocialUI = {
|
||||
Services.obs.removeObserver(this, "social:frameworker-error");
|
||||
Services.obs.removeObserver(this, "social:provider-set");
|
||||
Services.obs.removeObserver(this, "social:providers-changed");
|
||||
Services.obs.removeObserver(this, "social:provider-reload");
|
||||
|
||||
Services.prefs.removeObserver("social.sidebar.open", this);
|
||||
Services.prefs.removeObserver("social.toast-notifications.enabled", this);
|
||||
@ -74,6 +76,16 @@ SocialUI = {
|
||||
// manually :(
|
||||
try {
|
||||
switch (topic) {
|
||||
case "social:provider-reload":
|
||||
// if the reloaded provider is our current provider, fall through
|
||||
// to social:provider-set so the ui will be reset
|
||||
if (!Social.provider || Social.provider.origin != data)
|
||||
return;
|
||||
// be sure to unload the sidebar as it will not reload if the origin
|
||||
// has not changed, it will be loaded in provider-set below. Other
|
||||
// panels will be unloaded or handle reload.
|
||||
SocialSidebar.unloadSidebar();
|
||||
// fall through to social:provider-set
|
||||
case "social:provider-set":
|
||||
// Social.provider has changed (possibly to null), update any state
|
||||
// which depends on it.
|
||||
@ -142,7 +154,7 @@ SocialUI = {
|
||||
|
||||
// Miscellaneous helpers
|
||||
showProfile: function SocialUI_showProfile() {
|
||||
if (Social.haveLoggedInUser())
|
||||
if (Social.provider.haveLoggedInUser())
|
||||
openUILinkIn(Social.provider.profile.profileURL, "tab");
|
||||
else {
|
||||
// XXX Bug 789585 will implement an API for provider-specified login pages.
|
||||
@ -976,22 +988,20 @@ SocialToolbar = {
|
||||
let toggleNotificationsCommand = document.getElementById("Social:ToggleNotifications");
|
||||
toggleNotificationsCommand.setAttribute("hidden", !socialEnabled);
|
||||
|
||||
if (!Social.haveLoggedInUser() || !socialEnabled) {
|
||||
let parent = document.getElementById("social-notification-panel");
|
||||
while (parent.hasChildNodes()) {
|
||||
let frame = parent.firstChild;
|
||||
SharedFrame.forgetGroup(frame.id);
|
||||
parent.removeChild(frame);
|
||||
}
|
||||
let parent = document.getElementById("social-notification-panel");
|
||||
while (parent.hasChildNodes()) {
|
||||
let frame = parent.firstChild;
|
||||
SharedFrame.forgetGroup(frame.id);
|
||||
parent.removeChild(frame);
|
||||
}
|
||||
|
||||
let tbi = document.getElementById("social-toolbar-item");
|
||||
if (tbi) {
|
||||
// SocialMark is the last button allways
|
||||
let next = SocialMark.button.previousSibling;
|
||||
while (next != this.button) {
|
||||
tbi.removeChild(next);
|
||||
next = SocialMark.button.previousSibling;
|
||||
}
|
||||
let tbi = document.getElementById("social-toolbar-item");
|
||||
if (tbi) {
|
||||
// SocialMark is the last button allways
|
||||
let next = SocialMark.button.previousSibling;
|
||||
while (next != this.button) {
|
||||
tbi.removeChild(next);
|
||||
next = SocialMark.button.previousSibling;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1035,7 +1045,7 @@ SocialToolbar = {
|
||||
// provider.profile == undefined means no response yet from the provider
|
||||
// to tell us whether the user is logged in or not.
|
||||
if (!SocialUI.enabled ||
|
||||
(!Social.haveLoggedInUser() && Social.provider.profile !== undefined)) {
|
||||
(!Social.provider.haveLoggedInUser() && Social.provider.profile !== undefined)) {
|
||||
// Either no enabled provider, or there is a provider and it has
|
||||
// responded with a profile and the user isn't loggedin. The icons
|
||||
// etc have already been removed by updateButtonHiddenState, so we want
|
||||
|
@ -905,10 +905,7 @@ nsContextMenu.prototype = {
|
||||
reload: function(event) {
|
||||
if (this.onSocial) {
|
||||
// full reload of social provider
|
||||
Social.enabled = false;
|
||||
Services.tm.mainThread.dispatch(function() {
|
||||
Social.enabled = true;
|
||||
}, Components.interfaces.nsIThread.DISPATCH_NORMAL);
|
||||
Social.provider.reload();
|
||||
} else {
|
||||
BrowserReloadOrDuplicate(event);
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
|
||||
"resource:///modules/DownloadsCommon.jsm");
|
||||
|
||||
|
||||
function Sanitizer() {}
|
||||
Sanitizer.prototype = {
|
||||
// warning to the caller: this one may raise an exception (e.g. bug #265028)
|
||||
@ -37,14 +37,14 @@ Sanitizer.prototype = {
|
||||
aCallback(aItemName, canClear, aArg);
|
||||
return canClear;
|
||||
},
|
||||
|
||||
|
||||
prefDomain: "",
|
||||
|
||||
|
||||
getNameFromPreference: function (aPreferenceName)
|
||||
{
|
||||
return aPreferenceName.substr(this.prefDomain.length);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Deletes privacy sensitive data in a batch, according to user preferences.
|
||||
* Returns a promise which is resolved if no errors occurred. If an error
|
||||
@ -87,7 +87,8 @@ Sanitizer.prototype = {
|
||||
item.clear();
|
||||
} catch(er) {
|
||||
seenError = true;
|
||||
Cu.reportError("Error sanitizing " + itemName + ": " + er + "\n");
|
||||
Components.utils.reportError("Error sanitizing " + itemName +
|
||||
": " + er + "\n");
|
||||
}
|
||||
onItemComplete();
|
||||
};
|
||||
@ -99,7 +100,7 @@ Sanitizer.prototype = {
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
|
||||
// Time span only makes sense in certain cases. Consumers who want
|
||||
// to only clear some private data can opt in by setting this to false,
|
||||
// and can optionally specify a specific range. If timespan is not ignored,
|
||||
@ -107,7 +108,7 @@ Sanitizer.prototype = {
|
||||
// pref to determine a range
|
||||
ignoreTimespan : true,
|
||||
range : null,
|
||||
|
||||
|
||||
items: {
|
||||
cache: {
|
||||
clear: function ()
|
||||
@ -126,13 +127,13 @@ Sanitizer.prototype = {
|
||||
imageCache.clearCache(false); // true=chrome, false=content
|
||||
} catch(er) {}
|
||||
},
|
||||
|
||||
|
||||
get canClear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
cookies: {
|
||||
clear: function ()
|
||||
{
|
||||
@ -143,7 +144,7 @@ Sanitizer.prototype = {
|
||||
var cookiesEnum = cookieMgr.enumerator;
|
||||
while (cookiesEnum.hasMoreElements()) {
|
||||
var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
|
||||
|
||||
|
||||
if (cookie.creationTime > this.range[0])
|
||||
// This cookie was created after our cutoff, clear it
|
||||
cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
|
||||
@ -211,14 +212,14 @@ Sanitizer.prototype = {
|
||||
PlacesUtils.history.removeVisitsByTimeframe(this.range[0], this.range[1]);
|
||||
else
|
||||
PlacesUtils.history.removeAllPages();
|
||||
|
||||
|
||||
try {
|
||||
var os = Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
os.notifyObservers(null, "browser:purge-session-history", "");
|
||||
}
|
||||
catch (e) { }
|
||||
|
||||
|
||||
// Clear last URL of the Open Web Location dialog
|
||||
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
@ -227,7 +228,7 @@ Sanitizer.prototype = {
|
||||
}
|
||||
catch (e) { }
|
||||
},
|
||||
|
||||
|
||||
get canClear()
|
||||
{
|
||||
// bug 347231: Always allow clearing history due to dependencies on
|
||||
@ -235,7 +236,7 @@ Sanitizer.prototype = {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
formdata: {
|
||||
clear: function ()
|
||||
{
|
||||
@ -305,15 +306,20 @@ Sanitizer.prototype = {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
downloads: {
|
||||
clear: function ()
|
||||
{
|
||||
if (DownloadsCommon.useJSTransfer) {
|
||||
Task.spawn(function () {
|
||||
let filterByTime = this.range ?
|
||||
(download => download.startTime >= this.range[0] &&
|
||||
download.startTime <= this.range[1]) : null;
|
||||
let filterByTime = null;
|
||||
if (this.range) {
|
||||
// Convert microseconds back to milliseconds for date comparisons.
|
||||
let rangeBeginMs = this.range[0] / 1000;
|
||||
let rangeEndMs = this.range[1] / 1000;
|
||||
filterByTime = download => download.startTime >= rangeBeginMs &&
|
||||
download.startTime <= rangeEndMs;
|
||||
}
|
||||
|
||||
// Clear all completed/cancelled downloads
|
||||
let publicList = yield Downloads.getPublicDownloadList();
|
||||
@ -321,7 +327,7 @@ Sanitizer.prototype = {
|
||||
|
||||
let privateList = yield Downloads.getPrivateDownloadList();
|
||||
privateList.removeFinished(filterByTime);
|
||||
}.bind(this)).then(null, Cu.reportError);
|
||||
}.bind(this)).then(null, Components.utils.reportError);
|
||||
}
|
||||
else {
|
||||
var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
|
||||
@ -352,7 +358,7 @@ Sanitizer.prototype = {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
passwords: {
|
||||
clear: function ()
|
||||
{
|
||||
@ -361,7 +367,7 @@ Sanitizer.prototype = {
|
||||
// Passwords are timeless, and don't respect the timeSpan setting
|
||||
pwmgr.removeAllLogins();
|
||||
},
|
||||
|
||||
|
||||
get canClear()
|
||||
{
|
||||
var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
|
||||
@ -370,7 +376,7 @@ Sanitizer.prototype = {
|
||||
return (count > 0);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
sessions: {
|
||||
clear: function ()
|
||||
{
|
||||
@ -384,13 +390,13 @@ Sanitizer.prototype = {
|
||||
.getService(Components.interfaces.nsIObserverService);
|
||||
os.notifyObservers(null, "net:clear-active-logins", null);
|
||||
},
|
||||
|
||||
|
||||
get canClear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
siteSettings: {
|
||||
clear: function ()
|
||||
{
|
||||
@ -398,12 +404,12 @@ Sanitizer.prototype = {
|
||||
var pm = Components.classes["@mozilla.org/permissionmanager;1"]
|
||||
.getService(Components.interfaces.nsIPermissionManager);
|
||||
pm.removeAll();
|
||||
|
||||
|
||||
// Clear site-specific settings like page-zoom level
|
||||
var cps = Components.classes["@mozilla.org/content-pref/service;1"]
|
||||
.getService(Components.interfaces.nsIContentPrefService2);
|
||||
cps.removeAllDomains(null);
|
||||
|
||||
|
||||
// Clear "Never remember passwords for this site", which is not handled by
|
||||
// the permission manager
|
||||
var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
|
||||
@ -413,7 +419,7 @@ Sanitizer.prototype = {
|
||||
pwmgr.setLoginSavingEnabled(host, true);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
get canClear()
|
||||
{
|
||||
return true;
|
||||
@ -446,7 +452,7 @@ Sanitizer.getClearRange = function (ts) {
|
||||
ts = Sanitizer.prefs.getIntPref("timeSpan");
|
||||
if (ts === Sanitizer.TIMESPAN_EVERYTHING)
|
||||
return null;
|
||||
|
||||
|
||||
// PRTime is microseconds while JS time is milliseconds
|
||||
var endDate = Date.now() * 1000;
|
||||
switch (ts) {
|
||||
@ -473,7 +479,7 @@ Sanitizer.getClearRange = function (ts) {
|
||||
};
|
||||
|
||||
Sanitizer._prefs = null;
|
||||
Sanitizer.__defineGetter__("prefs", function()
|
||||
Sanitizer.__defineGetter__("prefs", function()
|
||||
{
|
||||
return Sanitizer._prefs ? Sanitizer._prefs
|
||||
: Sanitizer._prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
@ -482,7 +488,7 @@ Sanitizer.__defineGetter__("prefs", function()
|
||||
});
|
||||
|
||||
// Shows sanitization UI
|
||||
Sanitizer.showUI = function(aParentWindow)
|
||||
Sanitizer.showUI = function(aParentWindow)
|
||||
{
|
||||
var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
|
||||
.getService(Components.interfaces.nsIWindowWatcher);
|
||||
@ -497,32 +503,32 @@ Sanitizer.showUI = function(aParentWindow)
|
||||
null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes privacy sensitive data in a batch, optionally showing the
|
||||
/**
|
||||
* Deletes privacy sensitive data in a batch, optionally showing the
|
||||
* sanitize UI, according to user preferences
|
||||
*/
|
||||
Sanitizer.sanitize = function(aParentWindow)
|
||||
Sanitizer.sanitize = function(aParentWindow)
|
||||
{
|
||||
Sanitizer.showUI(aParentWindow);
|
||||
};
|
||||
|
||||
Sanitizer.onStartup = function()
|
||||
Sanitizer.onStartup = function()
|
||||
{
|
||||
// we check for unclean exit with pending sanitization
|
||||
Sanitizer._checkAndSanitize();
|
||||
};
|
||||
|
||||
Sanitizer.onShutdown = function()
|
||||
Sanitizer.onShutdown = function()
|
||||
{
|
||||
// we check if sanitization is needed and perform it
|
||||
Sanitizer._checkAndSanitize();
|
||||
};
|
||||
|
||||
// this is called on startup and shutdown, to perform pending sanitizations
|
||||
Sanitizer._checkAndSanitize = function()
|
||||
Sanitizer._checkAndSanitize = function()
|
||||
{
|
||||
const prefs = Sanitizer.prefs;
|
||||
if (prefs.getBoolPref(Sanitizer.prefShutdown) &&
|
||||
if (prefs.getBoolPref(Sanitizer.prefShutdown) &&
|
||||
!prefs.prefHasUserValue(Sanitizer.prefDidShutdown)) {
|
||||
// this is a shutdown or a startup after an unclean exit
|
||||
var s = new Sanitizer();
|
||||
|
@ -2,10 +2,10 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// Bug 453440 - Test the timespan-based logic of the sanitizer code
|
||||
var now_uSec = Date.now() * 1000;
|
||||
|
||||
const dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
let now_mSec = Date.now();
|
||||
let now_uSec = now_mSec * 1000;
|
||||
|
||||
const kMsecPerMin = 60 * 1000;
|
||||
const kUsecPerMin = 60 * 1000000;
|
||||
|
||||
let tempScope = {};
|
||||
@ -14,6 +14,7 @@ Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
|
||||
let Sanitizer = tempScope.Sanitizer;
|
||||
|
||||
let FormHistory = (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
|
||||
let Downloads = (Components.utils.import("resource://gre/modules/Downloads.jsm", {})).Downloads;
|
||||
|
||||
function promiseFormHistoryRemoved() {
|
||||
let deferred = Promise.defer();
|
||||
@ -24,15 +25,30 @@ function promiseFormHistoryRemoved() {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function promiseDownloadRemoved(list) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let view = {
|
||||
onDownloadRemoved: function(download) {
|
||||
list.removeView(view);
|
||||
deferred.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
list.addView(view);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function() {
|
||||
setupDownloads();
|
||||
yield setupDownloads();
|
||||
yield setupFormHistory();
|
||||
yield setupHistory();
|
||||
yield onHistoryReady();
|
||||
}).then(finish);
|
||||
}).then(null, ex => ok(false, ex)).then(finish);
|
||||
}
|
||||
|
||||
function countEntries(name, message, check) {
|
||||
@ -80,12 +96,16 @@ function onHistoryReady() {
|
||||
itemPrefs.setBoolPref("sessions", false);
|
||||
itemPrefs.setBoolPref("siteSettings", false);
|
||||
|
||||
let publicList = yield Downloads.getPublicDownloadList();
|
||||
let downloadPromise = promiseDownloadRemoved(publicList);
|
||||
|
||||
// Clear 10 minutes ago
|
||||
s.range = [now_uSec - 10*60*1000000, now_uSec];
|
||||
s.sanitize();
|
||||
s.range = null;
|
||||
|
||||
yield promiseFormHistoryRemoved();
|
||||
yield downloadPromise;
|
||||
|
||||
ok(!(yield promiseIsURIVisited(makeURI("http://10minutes.com"))),
|
||||
"Pretend visit to 10minutes.com should now be deleted");
|
||||
@ -122,23 +142,26 @@ function onHistoryReady() {
|
||||
yield countEntries("today", "today form entry should still exist", checkOne);
|
||||
yield countEntries("b4today", "b4today form entry should still exist", checkOne);
|
||||
|
||||
ok(!downloadExists(5555555), "10 minute download should now be deleted");
|
||||
ok(downloadExists(5555551), "<1 hour download should still be present");
|
||||
ok(downloadExists(5555556), "1 hour 10 minute download should still be present");
|
||||
ok(downloadExists(5555550), "Year old download should still be present");
|
||||
ok(downloadExists(5555552), "<2 hour old download should still be present");
|
||||
ok(downloadExists(5555557), "2 hour 10 minute download should still be present");
|
||||
ok(downloadExists(5555553), "<4 hour old download should still be present");
|
||||
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
|
||||
ok(!(yield downloadExists(publicList, "fakefile-10-minutes")), "10 minute download should now be deleted");
|
||||
ok((yield downloadExists(publicList, "fakefile-1-hour")), "<1 hour download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
|
||||
|
||||
if (minutesSinceMidnight > 10)
|
||||
ok(downloadExists(5555554), "'Today' download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
|
||||
|
||||
downloadPromise = promiseDownloadRemoved(publicList);
|
||||
|
||||
// Clear 1 hour
|
||||
Sanitizer.prefs.setIntPref("timeSpan", 1);
|
||||
s.sanitize();
|
||||
|
||||
yield promiseFormHistoryRemoved();
|
||||
yield downloadPromise;
|
||||
|
||||
ok(!(yield promiseIsURIVisited(makeURI("http://1hour.com"))),
|
||||
"Pretend visit to 1hour.com should now be deleted");
|
||||
@ -169,23 +192,26 @@ function onHistoryReady() {
|
||||
yield countEntries("today", "today form entry should still exist", checkOne);
|
||||
yield countEntries("b4today", "b4today form entry should still exist", checkOne);
|
||||
|
||||
ok(!downloadExists(5555551), "<1 hour download should now be deleted");
|
||||
ok(downloadExists(5555556), "1 hour 10 minute download should still be present");
|
||||
ok(downloadExists(5555550), "Year old download should still be present");
|
||||
ok(downloadExists(5555552), "<2 hour old download should still be present");
|
||||
ok(downloadExists(5555557), "2 hour 10 minute download should still be present");
|
||||
ok(downloadExists(5555553), "<4 hour old download should still be present");
|
||||
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
|
||||
ok(!(yield downloadExists(publicList, "fakefile-1-hour")), "<1 hour download should now be deleted");
|
||||
ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
|
||||
|
||||
if (hoursSinceMidnight > 1)
|
||||
ok(downloadExists(5555554), "'Today' download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
|
||||
|
||||
downloadPromise = promiseDownloadRemoved(publicList);
|
||||
|
||||
// Clear 1 hour 10 minutes
|
||||
s.range = [now_uSec - 70*60*1000000, now_uSec];
|
||||
s.sanitize();
|
||||
s.range = null;
|
||||
|
||||
yield promiseFormHistoryRemoved();
|
||||
yield downloadPromise;
|
||||
|
||||
ok(!(yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
|
||||
"Pretend visit to 1hour10minutes.com should now be deleted");
|
||||
@ -213,20 +239,23 @@ function onHistoryReady() {
|
||||
yield countEntries("today", "today form entry should still exist", checkOne);
|
||||
yield countEntries("b4today", "b4today form entry should still exist", checkOne);
|
||||
|
||||
ok(!downloadExists(5555556), "1 hour 10 minute old download should now be deleted");
|
||||
ok(downloadExists(5555550), "Year old download should still be present");
|
||||
ok(downloadExists(5555552), "<2 hour old download should still be present");
|
||||
ok(downloadExists(5555557), "2 hour 10 minute download should still be present");
|
||||
ok(downloadExists(5555553), "<4 hour old download should still be present");
|
||||
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
|
||||
ok(!(yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute old download should now be deleted");
|
||||
ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
|
||||
if (minutesSinceMidnight > 70)
|
||||
ok(downloadExists(5555554), "'Today' download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
|
||||
|
||||
downloadPromise = promiseDownloadRemoved(publicList);
|
||||
|
||||
// Clear 2 hours
|
||||
Sanitizer.prefs.setIntPref("timeSpan", 2);
|
||||
s.sanitize();
|
||||
|
||||
yield promiseFormHistoryRemoved();
|
||||
yield downloadPromise;
|
||||
|
||||
ok(!(yield promiseIsURIVisited(makeURI("http://2hour.com"))),
|
||||
"Pretend visit to 2hour.com should now be deleted");
|
||||
@ -251,20 +280,23 @@ function onHistoryReady() {
|
||||
yield countEntries("today", "today form entry should still exist", checkOne);
|
||||
yield countEntries("b4today", "b4today form entry should still exist", checkOne);
|
||||
|
||||
ok(!downloadExists(5555552), "<2 hour old download should now be deleted");
|
||||
ok(downloadExists(5555550), "Year old download should still be present");
|
||||
ok(downloadExists(5555557), "2 hour 10 minute download should still be present");
|
||||
ok(downloadExists(5555553), "<4 hour old download should still be present");
|
||||
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
|
||||
ok(!(yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should now be deleted");
|
||||
ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
|
||||
if (hoursSinceMidnight > 2)
|
||||
ok(downloadExists(5555554), "'Today' download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
|
||||
|
||||
downloadPromise = promiseDownloadRemoved(publicList);
|
||||
|
||||
// Clear 2 hours 10 minutes
|
||||
s.range = [now_uSec - 130*60*1000000, now_uSec];
|
||||
s.sanitize();
|
||||
s.range = null;
|
||||
|
||||
yield promiseFormHistoryRemoved();
|
||||
yield downloadPromise;
|
||||
|
||||
ok(!(yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
|
||||
"Pretend visit to 2hour10minutes.com should now be deleted");
|
||||
@ -286,18 +318,21 @@ function onHistoryReady() {
|
||||
yield countEntries("today", "today form entry should still exist", checkOne);
|
||||
yield countEntries("b4today", "b4today form entry should still exist", checkOne);
|
||||
|
||||
ok(!downloadExists(5555557), "2 hour 10 minute old download should now be deleted");
|
||||
ok(downloadExists(5555553), "<4 hour old download should still be present");
|
||||
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
|
||||
ok(downloadExists(5555550), "Year old download should still be present");
|
||||
ok(!(yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute old download should now be deleted");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
|
||||
if (minutesSinceMidnight > 130)
|
||||
ok(downloadExists(5555554), "'Today' download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
|
||||
|
||||
downloadPromise = promiseDownloadRemoved(publicList);
|
||||
|
||||
// Clear 4 hours
|
||||
Sanitizer.prefs.setIntPref("timeSpan", 3);
|
||||
s.sanitize();
|
||||
|
||||
yield promiseFormHistoryRemoved();
|
||||
yield downloadPromise;
|
||||
|
||||
ok(!(yield promiseIsURIVisited(makeURI("http://4hour.com"))),
|
||||
"Pretend visit to 4hour.com should now be deleted");
|
||||
@ -316,11 +351,13 @@ function onHistoryReady() {
|
||||
yield countEntries("today", "today form entry should still exist", checkOne);
|
||||
yield countEntries("b4today", "b4today form entry should still exist", checkOne);
|
||||
|
||||
ok(!downloadExists(5555553), "<4 hour old download should now be deleted");
|
||||
ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
|
||||
ok(downloadExists(5555550), "Year old download should still be present");
|
||||
ok(!(yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should now be deleted");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
|
||||
if (hoursSinceMidnight > 4)
|
||||
ok(downloadExists(5555554), "'Today' download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
|
||||
|
||||
downloadPromise = promiseDownloadRemoved(publicList);
|
||||
|
||||
// Clear 4 hours 10 minutes
|
||||
s.range = [now_uSec - 250*60*1000000, now_uSec];
|
||||
@ -328,6 +365,7 @@ function onHistoryReady() {
|
||||
s.range = null;
|
||||
|
||||
yield promiseFormHistoryRemoved();
|
||||
yield downloadPromise;
|
||||
|
||||
ok(!(yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
|
||||
"Pretend visit to 4hour10minutes.com should now be deleted");
|
||||
@ -343,48 +381,60 @@ function onHistoryReady() {
|
||||
yield countEntries("today", "today form entry should still exist", checkOne);
|
||||
yield countEntries("b4today", "b4today form entry should still exist", checkOne);
|
||||
|
||||
ok(!downloadExists(5555558), "4 hour 10 minute download should now be deleted");
|
||||
ok(downloadExists(5555550), "Year old download should still be present");
|
||||
ok(!(yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should now be deleted");
|
||||
ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
|
||||
if (minutesSinceMidnight > 250)
|
||||
ok(downloadExists(5555554), "'Today' download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
|
||||
|
||||
// The 'Today' download might have been already deleted, in which case we
|
||||
// should not wait for a download removal notification.
|
||||
if (minutesSinceMidnight > 250) {
|
||||
downloadPromise = promiseDownloadRemoved(publicList);
|
||||
} else {
|
||||
downloadPromise = Promise.resolve();
|
||||
}
|
||||
|
||||
// Clear Today
|
||||
Sanitizer.prefs.setIntPref("timeSpan", 4);
|
||||
s.sanitize();
|
||||
|
||||
yield promiseFormHistoryRemoved();
|
||||
yield downloadPromise;
|
||||
|
||||
// Be careful. If we add our objectss just before midnight, and sanitize
|
||||
// runs immediately after, they won't be expired. This is expected, but
|
||||
// we should not test in that case. We cannot just test for opposite
|
||||
// condition because we could cross midnight just one moment after we
|
||||
// cache our time, then we would have an even worse random failure.
|
||||
var today = isToday(new Date(now_uSec/1000));
|
||||
var today = isToday(new Date(now_mSec));
|
||||
if (today) {
|
||||
ok(!(yield promiseIsURIVisited(makeURI("http://today.com"))),
|
||||
"Pretend visit to today.com should now be deleted");
|
||||
|
||||
yield countEntries("today", "today form entry should be deleted", checkZero);
|
||||
ok(!downloadExists(5555554), "'Today' download should now be deleted");
|
||||
ok(!(yield downloadExists(publicList, "fakefile-today")), "'Today' download should now be deleted");
|
||||
}
|
||||
|
||||
ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
|
||||
"Pretend visit to before-today.com should still exist");
|
||||
yield countEntries("b4today", "b4today form entry should still exist", checkOne);
|
||||
ok(downloadExists(5555550), "Year old download should still be present");
|
||||
ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
|
||||
|
||||
downloadPromise = promiseDownloadRemoved(publicList);
|
||||
|
||||
// Choose everything
|
||||
Sanitizer.prefs.setIntPref("timeSpan", 0);
|
||||
s.sanitize();
|
||||
|
||||
yield promiseFormHistoryRemoved();
|
||||
yield downloadPromise;
|
||||
|
||||
ok(!(yield promiseIsURIVisited(makeURI("http://before-today.com"))),
|
||||
"Pretend visit to before-today.com should now be deleted");
|
||||
|
||||
yield countEntries("b4today", "b4today form entry should be deleted", checkZero);
|
||||
|
||||
ok(!downloadExists(5555550), "Year old download should now be deleted");
|
||||
ok(!(yield downloadExists(publicList, "fakefile-old")), "Year old download should now be deleted");
|
||||
}
|
||||
|
||||
function setupHistory() {
|
||||
@ -562,227 +612,103 @@ function setupFormHistory() {
|
||||
|
||||
function setupDownloads() {
|
||||
|
||||
// Add 10-minutes download to DB
|
||||
let data = {
|
||||
id: "5555555",
|
||||
name: "fakefile-10-minutes",
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
|
||||
target: "fakefile-10-minutes",
|
||||
startTime: now_uSec - 10 * kUsecPerMin, // 10 minutes ago, in uSec
|
||||
endTime: now_uSec - 11 * kUsecPerMin, // 1 minute later
|
||||
state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
|
||||
currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
|
||||
guid: "a1bcD23eF4g5"
|
||||
};
|
||||
let publicList = yield Downloads.getPublicDownloadList();
|
||||
|
||||
let db = dm.DBConnection;
|
||||
let stmt = db.createStatement(
|
||||
"INSERT INTO moz_downloads (id, name, source, target, startTime, endTime, " +
|
||||
"state, currBytes, maxBytes, preferredAction, autoResume, guid) " +
|
||||
"VALUES (:id, :name, :source, :target, :startTime, :endTime, :state, " +
|
||||
":currBytes, :maxBytes, :preferredAction, :autoResume, :guid)");
|
||||
try {
|
||||
for (let prop in data)
|
||||
stmt.params[prop] = data[prop];
|
||||
stmt.execute();
|
||||
}
|
||||
finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
// Add within-1-hour download to DB
|
||||
data = {
|
||||
id: "5555551",
|
||||
name: "fakefile-1-hour",
|
||||
let download = yield Downloads.createDownload({
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
|
||||
target: "fakefile-10-minutes"
|
||||
});
|
||||
download.startTime = new Date(now_mSec - 10 * kMsecPerMin), // 10 minutes ago
|
||||
download.canceled = true;
|
||||
publicList.add(download);
|
||||
|
||||
download = yield Downloads.createDownload({
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
|
||||
target: "fakefile-1-hour",
|
||||
startTime: now_uSec - 45 * kUsecPerMin, // 45 minutes ago, in uSec
|
||||
endTime: now_uSec - 44 * kUsecPerMin, // 1 minute later
|
||||
state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
|
||||
currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
|
||||
guid: "1bcD23eF4g5a"
|
||||
};
|
||||
target: "fakefile-1-hour"
|
||||
});
|
||||
download.startTime = new Date(now_mSec - 45 * kMsecPerMin), // 45 minutes ago
|
||||
download.canceled = true;
|
||||
publicList.add(download);
|
||||
|
||||
try {
|
||||
for (let prop in data)
|
||||
stmt.params[prop] = data[prop];
|
||||
stmt.execute();
|
||||
}
|
||||
finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
// Add 1-hour-10-minutes download to DB
|
||||
data = {
|
||||
id: "5555556",
|
||||
name: "fakefile-1-hour-10-minutes",
|
||||
download = yield Downloads.createDownload({
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
|
||||
target: "fakefile-1-hour-10-minutes",
|
||||
startTime: now_uSec - 70 * kUsecPerMin, // 70 minutes ago, in uSec
|
||||
endTime: now_uSec - 71 * kUsecPerMin, // 1 minute later
|
||||
state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
|
||||
currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
|
||||
guid: "a1cbD23e4Fg5"
|
||||
};
|
||||
target: "fakefile-1-hour-10-minutes"
|
||||
});
|
||||
download.startTime = new Date(now_mSec - 70 * kMsecPerMin), // 70 minutes ago
|
||||
download.canceled = true;
|
||||
publicList.add(download);
|
||||
|
||||
try {
|
||||
for (let prop in data)
|
||||
stmt.params[prop] = data[prop];
|
||||
stmt.execute();
|
||||
}
|
||||
finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
// Add within-2-hour download
|
||||
data = {
|
||||
id: "5555552",
|
||||
name: "fakefile-2-hour",
|
||||
download = yield Downloads.createDownload({
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
|
||||
target: "fakefile-2-hour",
|
||||
startTime: now_uSec - 90 * kUsecPerMin, // 90 minutes ago, in uSec
|
||||
endTime: now_uSec - 89 * kUsecPerMin, // 1 minute later
|
||||
state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
|
||||
currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
|
||||
guid: "b1aDc23eFg54"
|
||||
};
|
||||
target: "fakefile-2-hour"
|
||||
});
|
||||
download.startTime = new Date(now_mSec - 90 * kMsecPerMin), // 90 minutes ago
|
||||
download.canceled = true;
|
||||
publicList.add(download);
|
||||
|
||||
try {
|
||||
for (let prop in data)
|
||||
stmt.params[prop] = data[prop];
|
||||
stmt.execute();
|
||||
}
|
||||
finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
// Add 2-hour-10-minutes download
|
||||
data = {
|
||||
id: "5555557",
|
||||
name: "fakefile-2-hour-10-minutes",
|
||||
download = yield Downloads.createDownload({
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
|
||||
target: "fakefile-2-hour-10-minutes",
|
||||
startTime: now_uSec - 130 * kUsecPerMin, // 130 minutes ago, in uSec
|
||||
endTime: now_uSec - 131 * kUsecPerMin, // 1 minute later
|
||||
state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
|
||||
currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
|
||||
guid: "z1bcD23eF4g5"
|
||||
};
|
||||
target: "fakefile-2-hour-10-minutes"
|
||||
});
|
||||
download.startTime = new Date(now_mSec - 130 * kMsecPerMin), // 130 minutes ago
|
||||
download.canceled = true;
|
||||
publicList.add(download);
|
||||
|
||||
try {
|
||||
for (let prop in data)
|
||||
stmt.params[prop] = data[prop];
|
||||
stmt.execute();
|
||||
}
|
||||
finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
// Add within-4-hour download
|
||||
data = {
|
||||
id: "5555553",
|
||||
name: "fakefile-4-hour",
|
||||
download = yield Downloads.createDownload({
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
|
||||
target: "fakefile-4-hour",
|
||||
startTime: now_uSec - 180 * kUsecPerMin, // 180 minutes ago, in uSec
|
||||
endTime: now_uSec - 179 * kUsecPerMin, // 1 minute later
|
||||
state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
|
||||
currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
|
||||
guid: "zzzcD23eF4g5"
|
||||
};
|
||||
|
||||
try {
|
||||
for (let prop in data)
|
||||
stmt.params[prop] = data[prop];
|
||||
stmt.execute();
|
||||
}
|
||||
finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
// Add 4-hour-10-minutes download
|
||||
data = {
|
||||
id: "5555558",
|
||||
name: "fakefile-4-hour-10-minutes",
|
||||
target: "fakefile-4-hour"
|
||||
});
|
||||
download.startTime = new Date(now_mSec - 180 * kMsecPerMin), // 180 minutes ago
|
||||
download.canceled = true;
|
||||
publicList.add(download);
|
||||
|
||||
download = yield Downloads.createDownload({
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
|
||||
target: "fakefile-4-hour-10-minutes",
|
||||
startTime: now_uSec - 250 * kUsecPerMin, // 250 minutes ago, in uSec
|
||||
endTime: now_uSec - 251 * kUsecPerMin, // 1 minute later
|
||||
state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
|
||||
currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
|
||||
guid: "z1bzz23eF4gz"
|
||||
};
|
||||
|
||||
try {
|
||||
for (let prop in data)
|
||||
stmt.params[prop] = data[prop];
|
||||
stmt.execute();
|
||||
}
|
||||
finally {
|
||||
stmt.reset();
|
||||
}
|
||||
target: "fakefile-4-hour-10-minutes"
|
||||
});
|
||||
download.startTime = new Date(now_mSec - 250 * kMsecPerMin), // 250 minutes ago
|
||||
download.canceled = true;
|
||||
publicList.add(download);
|
||||
|
||||
// Add "today" download
|
||||
let today = new Date();
|
||||
today.setHours(0);
|
||||
today.setMinutes(0);
|
||||
today.setSeconds(1);
|
||||
|
||||
data = {
|
||||
id: "5555554",
|
||||
name: "fakefile-today",
|
||||
|
||||
download = yield Downloads.createDownload({
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
|
||||
target: "fakefile-today",
|
||||
startTime: today.getTime() * 1000, // 12:00:30am this morning, in uSec
|
||||
endTime: (today.getTime() + 1000) * 1000, // 1 second later
|
||||
state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
|
||||
currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
|
||||
guid: "ffffD23eF4g5"
|
||||
};
|
||||
|
||||
try {
|
||||
for (let prop in data)
|
||||
stmt.params[prop] = data[prop];
|
||||
stmt.execute();
|
||||
}
|
||||
finally {
|
||||
stmt.reset();
|
||||
}
|
||||
target: "fakefile-today"
|
||||
});
|
||||
download.startTime = today, // 12:00:01 AM this morning
|
||||
download.canceled = true;
|
||||
publicList.add(download);
|
||||
|
||||
// Add "before today" download
|
||||
let lastYear = new Date();
|
||||
lastYear.setFullYear(lastYear.getFullYear() - 1);
|
||||
data = {
|
||||
id: "5555550",
|
||||
name: "fakefile-old",
|
||||
|
||||
download = yield Downloads.createDownload({
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
|
||||
target: "fakefile-old",
|
||||
startTime: lastYear.getTime() * 1000, // 1 year ago, in uSec
|
||||
endTime: (lastYear.getTime() + 1000) * 1000, // 1 second later
|
||||
state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
|
||||
currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
|
||||
guid: "ggggg23eF4g5"
|
||||
};
|
||||
|
||||
try {
|
||||
for (let prop in data)
|
||||
stmt.params[prop] = data[prop];
|
||||
stmt.execute();
|
||||
}
|
||||
finally {
|
||||
stmt.finalize();
|
||||
}
|
||||
target: "fakefile-old"
|
||||
});
|
||||
download.startTime = lastYear,
|
||||
download.canceled = true;
|
||||
publicList.add(download);
|
||||
|
||||
// Confirm everything worked
|
||||
ok(downloadExists(5555550), "Pretend download for everything case should exist");
|
||||
ok(downloadExists(5555555), "Pretend download for 10-minutes case should exist");
|
||||
ok(downloadExists(5555551), "Pretend download for 1-hour case should exist");
|
||||
ok(downloadExists(5555556), "Pretend download for 1-hour-10-minutes case should exist");
|
||||
ok(downloadExists(5555552), "Pretend download for 2-hour case should exist");
|
||||
ok(downloadExists(5555557), "Pretend download for 2-hour-10-minutes case should exist");
|
||||
ok(downloadExists(5555553), "Pretend download for 4-hour case should exist");
|
||||
ok(downloadExists(5555558), "Pretend download for 4-hour-10-minutes case should exist");
|
||||
ok(downloadExists(5555554), "Pretend download for Today case should exist");
|
||||
let downloads = yield publicList.getAll();
|
||||
is(downloads.length, 9, "9 Pretend downloads added");
|
||||
|
||||
ok((yield downloadExists(publicList, "fakefile-old")), "Pretend download for everything case should exist");
|
||||
ok((yield downloadExists(publicList, "fakefile-10-minutes")), "Pretend download for 10-minutes case should exist");
|
||||
ok((yield downloadExists(publicList, "fakefile-1-hour")), "Pretend download for 1-hour case should exist");
|
||||
ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "Pretend download for 1-hour-10-minutes case should exist");
|
||||
ok((yield downloadExists(publicList, "fakefile-2-hour")), "Pretend download for 2-hour case should exist");
|
||||
ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "Pretend download for 2-hour-10-minutes case should exist");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour")), "Pretend download for 4-hour case should exist");
|
||||
ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "Pretend download for 4-hour-10-minutes case should exist");
|
||||
ok((yield downloadExists(publicList, "fakefile-today")), "Pretend download for Today case should exist");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -791,18 +717,12 @@ function setupDownloads() {
|
||||
* @param aID
|
||||
* The ids of the downloads to check.
|
||||
*/
|
||||
function downloadExists(aID)
|
||||
function downloadExists(list, path)
|
||||
{
|
||||
let db = dm.DBConnection;
|
||||
let stmt = db.createStatement(
|
||||
"SELECT * " +
|
||||
"FROM moz_downloads " +
|
||||
"WHERE id = :id"
|
||||
);
|
||||
stmt.params.id = aID;
|
||||
var rows = stmt.executeStep();
|
||||
stmt.finalize();
|
||||
return rows;
|
||||
return Task.spawn(function() {
|
||||
let listArray = yield list.getAll();
|
||||
throw new Task.Result(listArray.some(i => i.target.path == path));
|
||||
});
|
||||
}
|
||||
|
||||
function isToday(aDate) {
|
||||
|
@ -21,18 +21,18 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
|
||||
"resource://gre/modules/FormHistory.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
|
||||
"resource://gre/modules/Downloads.jsm");
|
||||
|
||||
let tempScope = {};
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://browser/content/sanitize.js", tempScope);
|
||||
let Sanitizer = tempScope.Sanitizer;
|
||||
|
||||
const dm = Cc["@mozilla.org/download-manager;1"].
|
||||
getService(Ci.nsIDownloadManager);
|
||||
|
||||
const kMsecPerMin = 60 * 1000;
|
||||
const kUsecPerMin = 60 * 1000000;
|
||||
|
||||
let formEntries;
|
||||
let formEntries, downloadIDs, olderDownloadIDs;
|
||||
|
||||
// Add tests here. Each is a function that's called by doNextTest().
|
||||
var gAllTests = [
|
||||
@ -92,6 +92,23 @@ var gAllTests = [
|
||||
});
|
||||
},
|
||||
|
||||
function () {
|
||||
// Add downloads (within the past hour).
|
||||
Task.spawn(function () {
|
||||
downloadIDs = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
yield addDownloadWithMinutesAgo(downloadIDs, i);
|
||||
}
|
||||
// Add downloads (over an hour ago).
|
||||
olderDownloadIDs = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
yield addDownloadWithMinutesAgo(olderDownloadIDs, 61 + i);
|
||||
}
|
||||
|
||||
doNextTest();
|
||||
}).then(null, Components.utils.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures that the combined history-downloads checkbox clears both history
|
||||
* visits and downloads when checked; the dialog respects simple timespan.
|
||||
@ -115,16 +132,6 @@ var gAllTests = [
|
||||
}
|
||||
|
||||
addVisits(places, function() {
|
||||
// Add downloads (within the past hour).
|
||||
let downloadIDs = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
downloadIDs.push(addDownloadWithMinutesAgo(i));
|
||||
}
|
||||
// Add downloads (over an hour ago).
|
||||
let olderDownloadIDs = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
|
||||
}
|
||||
let totalHistoryVisits = uris.length + olderURIs.length;
|
||||
|
||||
let wh = new WindowHelper();
|
||||
@ -146,16 +153,16 @@ var gAllTests = [
|
||||
wh.onunload = function () {
|
||||
// History visits and downloads within one hour should be cleared.
|
||||
yield promiseHistoryClearedState(uris, true);
|
||||
ensureDownloadsClearedState(downloadIDs, true);
|
||||
yield ensureDownloadsClearedState(downloadIDs, true);
|
||||
|
||||
// Visits and downloads > 1 hour should still exist.
|
||||
yield promiseHistoryClearedState(olderURIs, false);
|
||||
ensureDownloadsClearedState(olderDownloadIDs, false);
|
||||
yield ensureDownloadsClearedState(olderDownloadIDs, false);
|
||||
|
||||
// OK, done, cleanup after ourselves.
|
||||
yield blankSlate();
|
||||
yield promiseHistoryClearedState(olderURIs, true);
|
||||
ensureDownloadsClearedState(olderDownloadIDs, true);
|
||||
yield ensureDownloadsClearedState(olderDownloadIDs, true);
|
||||
};
|
||||
wh.open();
|
||||
});
|
||||
@ -178,6 +185,18 @@ var gAllTests = [
|
||||
iter.next();
|
||||
},
|
||||
|
||||
function () {
|
||||
// Add downloads (within the past hour).
|
||||
Task.spawn(function () {
|
||||
downloadIDs = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
yield addDownloadWithMinutesAgo(downloadIDs, i);
|
||||
}
|
||||
|
||||
doNextTest();
|
||||
}).then(null, Components.utils.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures that the combined history-downloads checkbox removes neither
|
||||
* history visits nor downloads when not checked.
|
||||
@ -194,11 +213,6 @@ var gAllTests = [
|
||||
}
|
||||
|
||||
addVisits(places, function() {
|
||||
let downloadIDs = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
downloadIDs.push(addDownloadWithMinutesAgo(i));
|
||||
}
|
||||
|
||||
let wh = new WindowHelper();
|
||||
wh.onload = function () {
|
||||
is(this.isWarningPanelVisible(), false,
|
||||
@ -224,7 +238,7 @@ var gAllTests = [
|
||||
wh.onunload = function () {
|
||||
// Of the three only form entries should be cleared.
|
||||
yield promiseHistoryClearedState(uris, false);
|
||||
ensureDownloadsClearedState(downloadIDs, false);
|
||||
yield ensureDownloadsClearedState(downloadIDs, false);
|
||||
|
||||
formEntries.forEach(function (entry) {
|
||||
let exists = yield formNameExists(entry);
|
||||
@ -234,7 +248,7 @@ var gAllTests = [
|
||||
// OK, done, cleanup after ourselves.
|
||||
yield blankSlate();
|
||||
yield promiseHistoryClearedState(uris, true);
|
||||
ensureDownloadsClearedState(downloadIDs, true);
|
||||
yield ensureDownloadsClearedState(downloadIDs, true);
|
||||
};
|
||||
wh.open();
|
||||
});
|
||||
@ -639,15 +653,12 @@ var gAllTests = [
|
||||
}
|
||||
];
|
||||
|
||||
// Used as the download database ID for a new download. Incremented for each
|
||||
// new download. See addDownloadWithMinutesAgo().
|
||||
var gDownloadId = 5555551;
|
||||
|
||||
// Index in gAllTests of the test currently being run. Incremented for each
|
||||
// test run. See doNextTest().
|
||||
var gCurrTest = 0;
|
||||
|
||||
var now_uSec = Date.now() * 1000;
|
||||
let now_mSec = Date.now();
|
||||
let now_uSec = now_mSec * 1000;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -847,7 +858,7 @@ WindowHelper.prototype = {
|
||||
if (wh.onunload) {
|
||||
Task.spawn(wh.onunload).then(function() {
|
||||
waitForAsyncUpdates(doNextTest);
|
||||
});
|
||||
}).then(null, Components.utils.reportError);
|
||||
} else {
|
||||
waitForAsyncUpdates(doNextTest);
|
||||
}
|
||||
@ -900,40 +911,23 @@ WindowHelper.prototype = {
|
||||
* @param aMinutesAgo
|
||||
* The download will be downloaded this many minutes ago
|
||||
*/
|
||||
function addDownloadWithMinutesAgo(aMinutesAgo) {
|
||||
function addDownloadWithMinutesAgo(aExpectedPathList, aMinutesAgo) {
|
||||
let publicList = yield Downloads.getPublicDownloadList();
|
||||
|
||||
let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
|
||||
let data = {
|
||||
id: gDownloadId,
|
||||
name: name,
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
|
||||
target: name,
|
||||
startTime: now_uSec - (aMinutesAgo * kUsecPerMin),
|
||||
endTime: now_uSec - ((aMinutesAgo + 1) * kUsecPerMin),
|
||||
state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
|
||||
currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0
|
||||
};
|
||||
let download = yield Downloads.createDownload({
|
||||
source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
|
||||
target: name
|
||||
});
|
||||
download.startTime = new Date(now_mSec - (aMinutesAgo * kMsecPerMin));
|
||||
download.canceled = true;
|
||||
publicList.add(download);
|
||||
|
||||
let db = dm.DBConnection;
|
||||
let stmt = db.createStatement(
|
||||
"INSERT INTO moz_downloads (id, name, source, target, startTime, endTime, " +
|
||||
"state, currBytes, maxBytes, preferredAction, autoResume) " +
|
||||
"VALUES (:id, :name, :source, :target, :startTime, :endTime, :state, " +
|
||||
":currBytes, :maxBytes, :preferredAction, :autoResume)");
|
||||
try {
|
||||
for (let prop in data) {
|
||||
stmt.params[prop] = data[prop];
|
||||
}
|
||||
stmt.execute();
|
||||
}
|
||||
finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
is(downloadExists(gDownloadId), true,
|
||||
"Sanity check: download " + gDownloadId +
|
||||
ok((yield downloadExists(name)),
|
||||
"Sanity check: download " + name +
|
||||
" should exist after creating it");
|
||||
|
||||
return gDownloadId++;
|
||||
aExpectedPathList.push(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -984,15 +978,37 @@ function formNameExists(name)
|
||||
*/
|
||||
function blankSlate() {
|
||||
PlacesUtils.bhistory.removeAllPages();
|
||||
dm.cleanUp();
|
||||
|
||||
// The promise is resolved only when removing both downloads and form history are done.
|
||||
let deferred = Promise.defer();
|
||||
let formHistoryDone = false, downloadsDone = false;
|
||||
|
||||
Task.spawn(function deleteAllDownloads() {
|
||||
let publicList = yield Downloads.getPublicDownloadList();
|
||||
let downloads = yield publicList.getAll();
|
||||
for (let download of downloads) {
|
||||
publicList.remove(download);
|
||||
yield download.finalize(true);
|
||||
}
|
||||
downloadsDone = true;
|
||||
if (formHistoryDone) {
|
||||
deferred.resolve();
|
||||
}
|
||||
}).then(null, Components.utils.reportError);
|
||||
|
||||
FormHistory.update({ op: "remove" },
|
||||
{ handleError: function (error) {
|
||||
do_throw("Error occurred updating form history: " + error);
|
||||
deferred.reject(error);
|
||||
},
|
||||
handleCompletion: function (reason) { if (!reason) deferred.resolve(); }
|
||||
handleCompletion: function (reason) {
|
||||
if (!reason) {
|
||||
formHistoryDone = true;
|
||||
if (downloadsDone) {
|
||||
deferred.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
@ -1012,24 +1028,19 @@ function boolPrefIs(aPrefName, aExpectedVal, aMsg) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the download with the specified ID exists.
|
||||
* Checks to see if the download with the specified path exists.
|
||||
*
|
||||
* @param aID
|
||||
* The ID of the download to check
|
||||
* @param aPath
|
||||
* The path of the download to check
|
||||
* @return True if the download exists, false otherwise
|
||||
*/
|
||||
function downloadExists(aID)
|
||||
function downloadExists(aPath)
|
||||
{
|
||||
let db = dm.DBConnection;
|
||||
let stmt = db.createStatement(
|
||||
"SELECT * " +
|
||||
"FROM moz_downloads " +
|
||||
"WHERE id = :id"
|
||||
);
|
||||
stmt.params.id = aID;
|
||||
let rows = stmt.executeStep();
|
||||
stmt.finalize();
|
||||
return !!rows;
|
||||
return Task.spawn(function() {
|
||||
let publicList = yield Downloads.getPublicDownloadList();
|
||||
let listArray = yield publicList.getAll();
|
||||
throw new Task.Result(listArray.some(i => i.target.path == aPath));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1059,7 +1070,7 @@ function doNextTest() {
|
||||
function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
|
||||
let niceStr = aShouldBeCleared ? "no longer" : "still";
|
||||
aDownloadIDs.forEach(function (id) {
|
||||
is(downloadExists(id), !aShouldBeCleared,
|
||||
is((yield downloadExists(id)), !aShouldBeCleared,
|
||||
"download " + id + " should " + niceStr + " exist");
|
||||
});
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ MOCHITEST_BROWSER_FILES = \
|
||||
browser_newtab_block.js \
|
||||
browser_newtab_disable.js \
|
||||
browser_newtab_drag_drop.js \
|
||||
browser_newtab_drag_drop_ext.js \
|
||||
browser_newtab_drop_preview.js \
|
||||
browser_newtab_focus.js \
|
||||
browser_newtab_reset.js \
|
||||
|
@ -71,47 +71,4 @@ function runTests() {
|
||||
|
||||
yield simulateDrop(0, 4);
|
||||
checkGrid("3,1p,2p,4,0p,5p,6,7,8");
|
||||
|
||||
// drag a new site onto the very first cell
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,7,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
|
||||
yield simulateExternalDrop(0);
|
||||
checkGrid("99p,0,1,2,3,4,5,7p,8p");
|
||||
|
||||
// drag a new site onto the grid and make sure that pinned cells don't get
|
||||
// pushed out
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,7,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
|
||||
yield simulateExternalDrop(7);
|
||||
checkGrid("0,1,2,3,4,5,7p,99p,8p");
|
||||
|
||||
// drag a new site beneath a pinned cell and make sure the pinned cell is
|
||||
// not moved
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8p");
|
||||
|
||||
yield simulateExternalDrop(7);
|
||||
checkGrid("0,1,2,3,4,5,6,99p,8p");
|
||||
|
||||
// drag a new site onto a block of pinned sites and make sure they're shifted
|
||||
// around accordingly
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("0,1,2,,,,,,");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1p,2p");
|
||||
|
||||
yield simulateExternalDrop(1);
|
||||
checkGrid("0p,99p,1p,2p,3,4,5,6,7");
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* These tests make sure that dragging and dropping sites works as expected.
|
||||
* Sites contained in the grid need to shift around to indicate the result
|
||||
* of the drag-and-drop operation. If the grid is full and we're dragging
|
||||
* a new site into it another one gets pushed out.
|
||||
* This is a continuation of browser_newtab_drag_drop.js
|
||||
* to decrease test run time, focusing on external sites.
|
||||
*/
|
||||
function runTests() {
|
||||
// drag a new site onto the very first cell
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,7,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
|
||||
yield simulateExternalDrop(0);
|
||||
checkGrid("99p,0,1,2,3,4,5,7p,8p");
|
||||
|
||||
// drag a new site onto the grid and make sure that pinned cells don't get
|
||||
// pushed out
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,7,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
|
||||
yield simulateExternalDrop(7);
|
||||
checkGrid("0,1,2,3,4,5,7p,99p,8p");
|
||||
|
||||
// drag a new site beneath a pinned cell and make sure the pinned cell is
|
||||
// not moved
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8p");
|
||||
|
||||
yield simulateExternalDrop(7);
|
||||
checkGrid("0,1,2,3,4,5,6,99p,8p");
|
||||
|
||||
// drag a new site onto a block of pinned sites and make sure they're shifted
|
||||
// around accordingly
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("0,1,2,,,,,,");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1p,2p");
|
||||
|
||||
yield simulateExternalDrop(1);
|
||||
checkGrid("0p,99p,1p,2p,3,4,5,6,7");
|
||||
}
|
@ -30,6 +30,7 @@ MOCHITEST_BROWSER_FILES = \
|
||||
browser_social_chatwindow_resize.js \
|
||||
browser_social_chatwindowfocus.js \
|
||||
browser_social_multiprovider.js \
|
||||
browser_social_multiworker.js \
|
||||
browser_social_errorPage.js \
|
||||
browser_social_window.js \
|
||||
social_activate.html \
|
||||
|
@ -2,17 +2,71 @@
|
||||
* 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/. */
|
||||
|
||||
let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
|
||||
|
||||
let manifests = [
|
||||
{
|
||||
name: "provider@example.com",
|
||||
origin: "https://example.com",
|
||||
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html?example.com",
|
||||
workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
|
||||
iconURL: "chrome://branding/content/icon48.png"
|
||||
},
|
||||
{
|
||||
name: "provider@test1",
|
||||
origin: "https://test1.example.com",
|
||||
sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?test1",
|
||||
workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
|
||||
iconURL: "chrome://branding/content/icon48.png"
|
||||
},
|
||||
{
|
||||
name: "provider@test2",
|
||||
origin: "https://test2.example.com",
|
||||
sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html?test2",
|
||||
workerURL: "https://test2.example.com/browser/browser/base/content/test/social/social_worker.js",
|
||||
iconURL: "chrome://branding/content/icon48.png"
|
||||
}
|
||||
];
|
||||
|
||||
let chatId = 0;
|
||||
function openChat(provider, callback) {
|
||||
let chatUrl = provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
|
||||
let port = provider.getWorkerPort();
|
||||
port.onmessage = function(e) {
|
||||
if (e.data.topic == "got-chatbox-message") {
|
||||
port.close();
|
||||
callback();
|
||||
}
|
||||
}
|
||||
let url = chatUrl + "?" + (chatId++);
|
||||
port.postMessage({topic: "test-init"});
|
||||
port.postMessage({topic: "test-worker-chat", data: url});
|
||||
gURLsNotRemembered.push(url);
|
||||
}
|
||||
|
||||
function waitPrefChange(cb) {
|
||||
Services.prefs.addObserver("social.enabled", function prefObserver(subject, topic, data) {
|
||||
Services.prefs.removeObserver("social.enabled", prefObserver);
|
||||
executeSoon(cb);
|
||||
}, false);
|
||||
}
|
||||
|
||||
function setWorkerMode(multiple, cb) {
|
||||
waitPrefChange(function() {
|
||||
if (multiple)
|
||||
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
|
||||
else
|
||||
Services.prefs.clearUserPref("social.allowMultipleWorkers");
|
||||
waitPrefChange(cb);
|
||||
Social.enabled = true;
|
||||
});
|
||||
Social.enabled = false;
|
||||
}
|
||||
|
||||
function test() {
|
||||
requestLongerTimeout(2); // only debug builds seem to need more time...
|
||||
waitForExplicitFinish();
|
||||
|
||||
let manifest = { // normal provider
|
||||
name: "provider 1",
|
||||
origin: "https://example.com",
|
||||
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
|
||||
workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
|
||||
iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
|
||||
};
|
||||
let oldwidth = window.outerWidth; // we futz with these, so we restore them
|
||||
let oldleft = window.screenX;
|
||||
window.moveTo(0, window.screenY)
|
||||
@ -21,7 +75,7 @@ function test() {
|
||||
ok(chats.children.length == 0, "no chatty children left behind");
|
||||
cb();
|
||||
};
|
||||
runSocialTestWithProvider(manifest, function (finishcb) {
|
||||
runSocialTestWithProvider(manifests, function (finishcb) {
|
||||
runSocialTests(tests, undefined, postSubTest, function() {
|
||||
window.moveTo(oldleft, window.screenY)
|
||||
window.resizeTo(oldwidth, window.outerHeight);
|
||||
@ -147,7 +201,7 @@ var tests = {
|
||||
maybeOpenAnother();
|
||||
},
|
||||
testWorkerChatWindow: function(next) {
|
||||
const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
|
||||
const chatUrl = Social.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
|
||||
let chats = document.getElementById("pinnedchats");
|
||||
let port = Social.provider.getWorkerPort();
|
||||
ok(port, "provider has a port");
|
||||
@ -384,7 +438,7 @@ var tests = {
|
||||
|
||||
testSecondTopLevelWindow: function(next) {
|
||||
// Bug 817782 - check chats work in new top-level windows.
|
||||
const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
|
||||
const chatUrl = Social.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
|
||||
let port = Social.provider.getWorkerPort();
|
||||
let secondWindow;
|
||||
port.onmessage = function(e) {
|
||||
@ -407,23 +461,9 @@ var tests = {
|
||||
testChatWindowChooser: function(next) {
|
||||
// Tests that when a worker creates a chat, it is opened in the correct
|
||||
// window.
|
||||
const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
|
||||
let chatId = 1;
|
||||
let port = Social.provider.getWorkerPort();
|
||||
port.postMessage({topic: "test-init"});
|
||||
|
||||
function openChat(callback) {
|
||||
port.onmessage = function(e) {
|
||||
if (e.data.topic == "got-chatbox-message")
|
||||
callback();
|
||||
}
|
||||
let url = chatUrl + "?" + (chatId++);
|
||||
port.postMessage({topic: "test-worker-chat", data: url});
|
||||
}
|
||||
|
||||
// open a chat (it will open in the main window)
|
||||
ok(!window.SocialChatBar.hasChats, "first window should start with no chats");
|
||||
openChat(function() {
|
||||
openChat(Social.provider, function() {
|
||||
ok(window.SocialChatBar.hasChats, "first window has the chat");
|
||||
// create a second window - this will be the "most recent" and will
|
||||
// therefore be the window that hosts the new chat (see bug 835111)
|
||||
@ -431,27 +471,55 @@ var tests = {
|
||||
secondWindow.addEventListener("load", function loadListener() {
|
||||
secondWindow.removeEventListener("load", loadListener);
|
||||
ok(!secondWindow.SocialChatBar.hasChats, "second window has no chats");
|
||||
openChat(function() {
|
||||
openChat(Social.provider, function() {
|
||||
ok(secondWindow.SocialChatBar.hasChats, "second window now has chats");
|
||||
is(window.SocialChatBar.chatbar.childElementCount, 1, "first window still has 1 chat");
|
||||
window.SocialChatBar.chatbar.removeAll();
|
||||
// now open another chat - it should still open in the second.
|
||||
openChat(function() {
|
||||
openChat(Social.provider, function() {
|
||||
ok(!window.SocialChatBar.hasChats, "first window has no chats");
|
||||
ok(secondWindow.SocialChatBar.hasChats, "second window has a chat");
|
||||
secondWindow.close();
|
||||
port.close();
|
||||
next();
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
},
|
||||
testMultipleProviderChat: function(next) {
|
||||
// while pref'd off, we need to set the worker mode to multiple providers
|
||||
setWorkerMode(true, function() {
|
||||
// test incomming chats from all providers
|
||||
openChat(Social.providers[0], function() {
|
||||
openChat(Social.providers[1], function() {
|
||||
openChat(Social.providers[2], function() {
|
||||
let chats = document.getElementById("pinnedchats");
|
||||
waitForCondition(function() chats.children.length == Social.providers.length,
|
||||
function() {
|
||||
ok(true, "one chat window per provider opened");
|
||||
// test logout of a single provider
|
||||
let provider = Social.providers[0];
|
||||
let port = provider.getWorkerPort();
|
||||
port.postMessage({topic: "test-logout"});
|
||||
waitForCondition(function() chats.children.length == Social.providers.length - 1,
|
||||
function() {
|
||||
port.close();
|
||||
chats.removeAll();
|
||||
ok(!chats.selectedChat, "chats are all closed");
|
||||
setWorkerMode(false, next);
|
||||
},
|
||||
"chat window didn't close");
|
||||
}, "chat windows did not open");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// XXX - note this must be the last test until we restore the login state
|
||||
// between tests...
|
||||
testCloseOnLogout: function(next) {
|
||||
const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
|
||||
const chatUrl = Social.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
|
||||
let port = Social.provider.getWorkerPort();
|
||||
ok(port, "provider has a port");
|
||||
let opened = false;
|
||||
|
101
browser/base/content/test/social/browser_social_multiworker.js
Normal file
101
browser/base/content/test/social/browser_social_multiworker.js
Normal file
@ -0,0 +1,101 @@
|
||||
/* 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/. */
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
|
||||
runSocialTestWithProvider(gProviders, function (finishcb) {
|
||||
runSocialTests(tests, undefined, undefined, function() {
|
||||
Services.prefs.clearUserPref("social.allowMultipleWorkers");
|
||||
finishcb();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let gProviders = [
|
||||
{
|
||||
name: "provider 1",
|
||||
origin: "https://example.com",
|
||||
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html?provider1",
|
||||
workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
|
||||
iconURL: "chrome://branding/content/icon48.png"
|
||||
},
|
||||
{
|
||||
name: "provider 2",
|
||||
origin: "https://test1.example.com",
|
||||
sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
|
||||
workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
|
||||
iconURL: "chrome://branding/content/icon48.png"
|
||||
}
|
||||
];
|
||||
|
||||
var tests = {
|
||||
testWorkersAlive: function(next) {
|
||||
// verify we can get a message from all providers that are enabled
|
||||
let messageReceived = 0;
|
||||
function oneWorkerTest(provider) {
|
||||
let port = provider.getWorkerPort();
|
||||
port.onmessage = function (e) {
|
||||
let topic = e.data.topic;
|
||||
switch (topic) {
|
||||
case "test-init-done":
|
||||
ok(true, "got message from provider " + provider.name);
|
||||
port.close();
|
||||
messageReceived++;
|
||||
break;
|
||||
}
|
||||
};
|
||||
port.postMessage({topic: "test-init"});
|
||||
}
|
||||
|
||||
for (let p of Social.providers) {
|
||||
oneWorkerTest(p);
|
||||
}
|
||||
|
||||
waitForCondition(function() messageReceived == Social.providers.length,
|
||||
next, "received messages from all workers");
|
||||
},
|
||||
testWorkerDisabling: function(next) {
|
||||
Social.enabled = false;
|
||||
is(Social.providers.length, gProviders.length, "providers still available");
|
||||
for (let p of Social.providers) {
|
||||
ok(!p.enabled, "provider disabled");
|
||||
ok(!p.getWorkerPort(), "worker disabled");
|
||||
}
|
||||
next();
|
||||
},
|
||||
|
||||
testSingleWorkerEnabling: function(next) {
|
||||
// test that only one worker is enabled when we limit workers
|
||||
Services.prefs.setBoolPref("social.allowMultipleWorkers", false);
|
||||
Social.enabled = true;
|
||||
for (let p of Social.providers) {
|
||||
if (p == Social.provider) {
|
||||
ok(p.enabled, "primary provider enabled");
|
||||
let port = p.getWorkerPort();
|
||||
ok(port, "primary worker enabled");
|
||||
port.close();
|
||||
} else {
|
||||
ok(!p.enabled, "secondary provider is not enabled");
|
||||
ok(!p.getWorkerPort(), "secondary worker disabled");
|
||||
}
|
||||
}
|
||||
next();
|
||||
},
|
||||
|
||||
testMultipleWorkerEnabling: function(next) {
|
||||
// test that all workers are enabled when we allow multiple workers
|
||||
Social.enabled = false;
|
||||
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
|
||||
Social.enabled = true;
|
||||
for (let p of Social.providers) {
|
||||
ok(p.enabled, "provider enabled");
|
||||
let port = p.getWorkerPort();
|
||||
ok(port, "worker enabled");
|
||||
port.close();
|
||||
}
|
||||
next();
|
||||
}
|
||||
}
|
@ -417,8 +417,8 @@ function get3ChatsForCollapsing(mode, cb) {
|
||||
|
||||
function makeChat(mode, uniqueid, cb) {
|
||||
info("making a chat window '" + uniqueid +"'");
|
||||
const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
|
||||
let provider = Social.provider;
|
||||
const chatUrl = provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
|
||||
let isOpened = window.SocialChatBar.openChat(provider, chatUrl + "?id=" + uniqueid, function(chat) {
|
||||
info("chat window has opened");
|
||||
// we can't callback immediately or we might close the chat during
|
||||
|
@ -7,39 +7,33 @@
|
||||
* Make sure the downloads panel can display items in the right order and
|
||||
* contains the expected data.
|
||||
*/
|
||||
function gen_test()
|
||||
function test_task()
|
||||
{
|
||||
// Display one of each download state.
|
||||
const DownloadData = [
|
||||
{ endTime: 1180493839859239, state: nsIDM.DOWNLOAD_NOTSTARTED },
|
||||
{ endTime: 1180493839859238, state: nsIDM.DOWNLOAD_DOWNLOADING },
|
||||
{ endTime: 1180493839859237, state: nsIDM.DOWNLOAD_PAUSED },
|
||||
{ endTime: 1180493839859236, state: nsIDM.DOWNLOAD_SCANNING },
|
||||
{ endTime: 1180493839859235, state: nsIDM.DOWNLOAD_QUEUED },
|
||||
{ endTime: 1180493839859234, state: nsIDM.DOWNLOAD_FINISHED },
|
||||
{ endTime: 1180493839859233, state: nsIDM.DOWNLOAD_FAILED },
|
||||
{ endTime: 1180493839859232, state: nsIDM.DOWNLOAD_CANCELED },
|
||||
{ endTime: 1180493839859231, state: nsIDM.DOWNLOAD_BLOCKED_PARENTAL },
|
||||
{ endTime: 1180493839859230, state: nsIDM.DOWNLOAD_DIRTY },
|
||||
{ endTime: 1180493839859229, state: nsIDM.DOWNLOAD_BLOCKED_POLICY },
|
||||
{ state: nsIDM.DOWNLOAD_NOTSTARTED },
|
||||
{ state: nsIDM.DOWNLOAD_PAUSED },
|
||||
{ state: nsIDM.DOWNLOAD_FINISHED },
|
||||
{ state: nsIDM.DOWNLOAD_FAILED },
|
||||
{ state: nsIDM.DOWNLOAD_CANCELED },
|
||||
];
|
||||
|
||||
// For testing purposes, show all the download items at once.
|
||||
var originalCountLimit = DownloadsView.kItemCountLimit;
|
||||
DownloadsView.kItemCountLimit = DownloadData.length;
|
||||
registerCleanupFunction(function () {
|
||||
DownloadsView.kItemCountLimit = originalCountLimit;
|
||||
});
|
||||
|
||||
try {
|
||||
// Ensure that state is reset in case previous tests didn't finish.
|
||||
for (let yy in gen_resetState(DownloadsCommon.getData(window))) yield undefined;
|
||||
yield task_resetState();
|
||||
|
||||
// For testing purposes, show all the download items at once.
|
||||
var originalCountLimit = DownloadsView.kItemCountLimit;
|
||||
DownloadsView.kItemCountLimit = DownloadData.length;
|
||||
registerCleanupFunction(function () {
|
||||
DownloadsView.kItemCountLimit = originalCountLimit;
|
||||
});
|
||||
|
||||
// Populate the downloads database with the data required by this test.
|
||||
for (let yy in gen_addDownloadRows(DownloadData)) yield undefined;
|
||||
yield task_addDownloads(DownloadData);
|
||||
|
||||
// Open the user interface and wait for data to be fully loaded.
|
||||
for (let yy in gen_openPanel(DownloadsCommon.getData(window))) yield undefined;
|
||||
yield task_openPanel();
|
||||
|
||||
// Test item data and count. This also tests the ordering of the display.
|
||||
let richlistbox = document.getElementById("downloadsListBox");
|
||||
@ -47,16 +41,14 @@ function gen_test()
|
||||
is(richlistbox.children.length, DownloadData.length,
|
||||
"There is the correct number of richlistitems");
|
||||
*/
|
||||
for (let i = 0; i < richlistbox.children.length; i++) {
|
||||
let element = richlistbox.children[i];
|
||||
let itemCount = richlistbox.children.length;
|
||||
for (let i = 0; i < itemCount; i++) {
|
||||
let element = richlistbox.children[itemCount - i - 1];
|
||||
let dataItem = new DownloadsViewItemController(element).dataItem;
|
||||
is(dataItem.target, DownloadData[i].name, "Download names match up");
|
||||
is(dataItem.state, DownloadData[i].state, "Download states match up");
|
||||
is(dataItem.file, DownloadData[i].target, "Download targets match up");
|
||||
is(dataItem.uri, DownloadData[i].source, "Download sources match up");
|
||||
}
|
||||
} finally {
|
||||
// Clean up when the test finishes.
|
||||
for (let yy in gen_resetState(DownloadsCommon.getData(window))) yield undefined;
|
||||
yield task_resetState();
|
||||
}
|
||||
}
|
||||
|
@ -8,19 +8,19 @@
|
||||
* download it notices. All subsequent downloads, even across sessions, should
|
||||
* not open the panel automatically.
|
||||
*/
|
||||
function gen_test()
|
||||
function test_task()
|
||||
{
|
||||
try {
|
||||
// Ensure that state is reset in case previous tests didn't finish.
|
||||
for (let yy in gen_resetState(DownloadsCommon.getData(window))) yield undefined;
|
||||
yield task_resetState();
|
||||
|
||||
// With this set to false, we should automatically open the panel
|
||||
// the first time a download is started.
|
||||
// With this set to false, we should automatically open the panel the first
|
||||
// time a download is started.
|
||||
DownloadsCommon.getData(window).panelHasShownBefore = false;
|
||||
|
||||
prepareForPanelOpen();
|
||||
let promise = promisePanelOpened();
|
||||
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
|
||||
yield undefined;
|
||||
yield promise;
|
||||
|
||||
// If we got here, that means the panel opened.
|
||||
DownloadsPanel.hidePanel();
|
||||
@ -28,29 +28,26 @@ function gen_test()
|
||||
ok(DownloadsCommon.getData(window).panelHasShownBefore,
|
||||
"Should have recorded that the panel was opened on a download.")
|
||||
|
||||
// Next, make sure that if we start another download, we don't open
|
||||
// the panel automatically.
|
||||
panelShouldNotOpen();
|
||||
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
|
||||
yield waitFor(2);
|
||||
} catch(e) {
|
||||
ok(false, e);
|
||||
// Next, make sure that if we start another download, we don't open the
|
||||
// panel automatically.
|
||||
let originalOnPopupShown = DownloadsPanel.onPopupShown;
|
||||
DownloadsPanel.onPopupShown = function () {
|
||||
originalOnPopupShown.apply(this, arguments);
|
||||
ok(false, "Should not have opened the downloads panel.");
|
||||
};
|
||||
|
||||
try {
|
||||
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
|
||||
|
||||
// Wait 2 seconds to ensure that the panel does not open.
|
||||
let deferTimeout = Promise.defer();
|
||||
setTimeout(deferTimeout.resolve, 2000);
|
||||
yield deferTimeout.promise;
|
||||
} finally {
|
||||
DownloadsPanel.onPopupShown = originalOnPopupShown;
|
||||
}
|
||||
} finally {
|
||||
// Clean up when the test finishes.
|
||||
for (let yy in gen_resetState(DownloadsCommon.getData(window))) yield undefined;
|
||||
yield task_resetState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to record a test failure for the next time the downloads panel
|
||||
* opens.
|
||||
*/
|
||||
function panelShouldNotOpen()
|
||||
{
|
||||
// Hook to wait until the test data has been loaded.
|
||||
let originalOnViewLoadCompleted = DownloadsPanel.onViewLoadCompleted;
|
||||
DownloadsPanel.onViewLoadCompleted = function () {
|
||||
DownloadsPanel.onViewLoadCompleted = originalOnViewLoadCompleted;
|
||||
ok(false, "Should not have opened the downloads panel.");
|
||||
};
|
||||
}
|
||||
|
@ -10,10 +10,16 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Globals
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
|
||||
"resource://gre/modules/Downloads.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
|
||||
"resource:///modules/DownloadsCommon.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
const nsIDM = Ci.nsIDownloadManager;
|
||||
|
||||
let gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]);
|
||||
@ -22,253 +28,85 @@ registerCleanupFunction(function () {
|
||||
gTestTargetFile.remove(false);
|
||||
});
|
||||
|
||||
/**
|
||||
* This objects contains a property for each column in the downloads table.
|
||||
*/
|
||||
let gDownloadRowTemplate = {
|
||||
name: "test-download.txt",
|
||||
source: "http://www.example.com/test-download.txt",
|
||||
target: NetUtil.newURI(gTestTargetFile).spec,
|
||||
startTime: 1180493839859230,
|
||||
endTime: 1180493839859234,
|
||||
state: nsIDM.DOWNLOAD_FINISHED,
|
||||
currBytes: 0,
|
||||
maxBytes: -1,
|
||||
preferredAction: 0,
|
||||
autoResume: 0
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Infrastructure
|
||||
|
||||
// All test are run through the test runner.
|
||||
function test()
|
||||
{
|
||||
testRunner.runTest(this.gen_test);
|
||||
waitForExplicitFinish();
|
||||
Task.spawn(test_task).then(null, ex => ok(false, ex)).then(finish);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a browser-chrome test defined through a generator function.
|
||||
*
|
||||
* This object is a singleton, initialized automatically when this script is
|
||||
* included. Every browser-chrome test file includes a new copy of this object.
|
||||
*/
|
||||
var testRunner = {
|
||||
_testIterator: null,
|
||||
_lastEventResult: undefined,
|
||||
_testRunning: false,
|
||||
_eventRaised: false,
|
||||
|
||||
// --- Main test runner ---
|
||||
|
||||
/**
|
||||
* Runs the test described by the provided generator function asynchronously.
|
||||
*
|
||||
* Calling yield in the generator will cause it to wait until continueTest is
|
||||
* called. The parameter provided to continueTest will be the return value of
|
||||
* the yield operator.
|
||||
*
|
||||
* @param aGenerator
|
||||
* Test generator function. The function will be called with no
|
||||
* arguments to retrieve its iterator.
|
||||
*/
|
||||
runTest: function TR_runTest(aGenerator) {
|
||||
waitForExplicitFinish();
|
||||
testRunner._testIterator = aGenerator();
|
||||
testRunner.continueTest();
|
||||
},
|
||||
|
||||
/**
|
||||
* Continues the currently running test.
|
||||
*
|
||||
* @param aEventResult
|
||||
* This will be the return value of the yield operator in the test.
|
||||
*/
|
||||
continueTest: function TR_continueTest(aEventResult) {
|
||||
// Store the last event result, or set it to undefined.
|
||||
testRunner._lastEventResult = aEventResult;
|
||||
|
||||
// Never reenter the main loop, but notify that the event has been raised.
|
||||
if (testRunner._testRunning) {
|
||||
testRunner._eventRaised = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Enter the main iteration loop.
|
||||
testRunner._testRunning = true;
|
||||
try {
|
||||
do {
|
||||
// Call the iterator, but don't leave the loop if the expected event is
|
||||
// raised during the execution of the generator.
|
||||
testRunner._eventRaised = false;
|
||||
testRunner._testIterator.send(testRunner._lastEventResult);
|
||||
} while (testRunner._eventRaised);
|
||||
}
|
||||
catch (e) {
|
||||
// This block catches exceptions raised by the generator, including the
|
||||
// normal StopIteration exception. Unexpected exceptions are reported as
|
||||
// test failures.
|
||||
if (!(e instanceof StopIteration))
|
||||
ok(false, e);
|
||||
// In any case, stop the tests in this file.
|
||||
finish();
|
||||
}
|
||||
|
||||
// Wait for the next event or finish.
|
||||
testRunner._testRunning = false;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Asynchronous generator-based support subroutines
|
||||
//// Asynchronous support subroutines
|
||||
|
||||
//
|
||||
// The following functions are all generators that can be used inside the main
|
||||
// test generator to perform specific tasks asynchronously. To invoke these
|
||||
// subroutines correctly, an iteration syntax should be used:
|
||||
//
|
||||
// for (let yy in gen_example("Parameter")) yield undefined;
|
||||
//
|
||||
|
||||
function gen_resetState(aData)
|
||||
function promiseFocus()
|
||||
{
|
||||
let statement = Services.downloads.DBConnection.createAsyncStatement(
|
||||
"DELETE FROM moz_downloads");
|
||||
try {
|
||||
statement.executeAsync({
|
||||
handleResult: function(aResultSet) { },
|
||||
handleError: function(aError)
|
||||
{
|
||||
Cu.reportError(aError);
|
||||
},
|
||||
handleCompletion: function(aReason)
|
||||
{
|
||||
testRunner.continueTest();
|
||||
}
|
||||
});
|
||||
yield undefined;
|
||||
} finally {
|
||||
statement.finalize();
|
||||
let deferred = Promise.defer();
|
||||
waitForFocus(deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function promisePanelOpened()
|
||||
{
|
||||
let deferred = Promise.defer();
|
||||
|
||||
// Hook to wait until the panel is shown.
|
||||
let originalOnPopupShown = DownloadsPanel.onPopupShown;
|
||||
DownloadsPanel.onPopupShown = function () {
|
||||
DownloadsPanel.onPopupShown = originalOnPopupShown;
|
||||
originalOnPopupShown.apply(this, arguments);
|
||||
|
||||
// Defer to the next tick of the event loop so that we don't continue
|
||||
// processing during the DOM event handler itself.
|
||||
setTimeout(deferred.resolve, 0);
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function task_resetState()
|
||||
{
|
||||
// Remove all downloads.
|
||||
let publicList = yield Downloads.getPublicDownloadList();
|
||||
let downloads = yield publicList.getAll();
|
||||
for (let download of downloads) {
|
||||
publicList.remove(download);
|
||||
yield download.finalize(true);
|
||||
}
|
||||
|
||||
// Reset any prefs that might have been changed.
|
||||
Services.prefs.clearUserPref("browser.download.panel.shown");
|
||||
|
||||
// Ensure that the panel is closed and data is unloaded.
|
||||
aData.clear();
|
||||
aData._loadState = aData.kLoadNone;
|
||||
DownloadsPanel.hidePanel();
|
||||
|
||||
// Wait for focus on the main window.
|
||||
waitForFocus(testRunner.continueTest);
|
||||
yield undefined;
|
||||
yield promiseFocus();
|
||||
}
|
||||
|
||||
function gen_addDownloadRows(aDataRows)
|
||||
function task_addDownloads(aItems)
|
||||
{
|
||||
let columnNames = Object.keys(gDownloadRowTemplate).join(", ");
|
||||
let parameterNames = Object.keys(gDownloadRowTemplate)
|
||||
.map(function(n) ":" + n)
|
||||
.join(", ");
|
||||
let statement = Services.downloads.DBConnection.createAsyncStatement(
|
||||
"INSERT INTO moz_downloads (" + columnNames +
|
||||
", guid) VALUES(" + parameterNames + ", GENERATE_GUID())");
|
||||
try {
|
||||
// Execute the statement for each of the provided downloads in reverse.
|
||||
for (let i = aDataRows.length - 1; i >= 0; i--) {
|
||||
let dataRow = aDataRows[i];
|
||||
let startTimeMs = Date.now();
|
||||
|
||||
// Populate insert parameters from the provided data.
|
||||
for (let columnName in gDownloadRowTemplate) {
|
||||
if (!(columnName in dataRow)) {
|
||||
// Update the provided row object with data from the global template,
|
||||
// for columns whose value is not provided explicitly.
|
||||
dataRow[columnName] = gDownloadRowTemplate[columnName];
|
||||
}
|
||||
statement.params[columnName] = dataRow[columnName];
|
||||
}
|
||||
|
||||
// Run the statement asynchronously and wait.
|
||||
statement.executeAsync({
|
||||
handleResult: function(aResultSet) { },
|
||||
handleError: function(aError)
|
||||
{
|
||||
Cu.reportError(aError.message + " (Result = " + aError.result + ")");
|
||||
},
|
||||
handleCompletion: function(aReason)
|
||||
{
|
||||
testRunner.continueTest();
|
||||
}
|
||||
});
|
||||
yield undefined;
|
||||
|
||||
// At each iteration, ensure that the start and end time in the global
|
||||
// template is distinct, as these column are used to sort each download
|
||||
// in its category.
|
||||
gDownloadRowTemplate.startTime++;
|
||||
gDownloadRowTemplate.endTime++;
|
||||
}
|
||||
} finally {
|
||||
statement.finalize();
|
||||
let publicList = yield Downloads.getPublicDownloadList();
|
||||
for (let item of aItems) {
|
||||
publicList.add(yield Downloads.createDownload({
|
||||
source: "http://www.example.com/test-download.txt",
|
||||
target: gTestTargetFile,
|
||||
succeeded: item.state == nsIDM.DOWNLOAD_FINISHED,
|
||||
canceled: item.state == nsIDM.DOWNLOAD_CANCELED ||
|
||||
item.state == nsIDM.DOWNLOAD_PAUSED,
|
||||
error: item.state == nsIDM.DOWNLOAD_FAILED ? new Error("Failed.") : null,
|
||||
hasPartialData: item.state == nsIDM.DOWNLOAD_PAUSED,
|
||||
startTime: new Date(startTimeMs++),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function gen_openPanel(aData)
|
||||
function task_openPanel()
|
||||
{
|
||||
// Hook to wait until the test data has been loaded.
|
||||
let originalOnViewLoadCompleted = DownloadsPanel.onViewLoadCompleted;
|
||||
DownloadsPanel.onViewLoadCompleted = function () {
|
||||
DownloadsPanel.onViewLoadCompleted = originalOnViewLoadCompleted;
|
||||
originalOnViewLoadCompleted.apply(this);
|
||||
testRunner.continueTest();
|
||||
};
|
||||
yield promiseFocus();
|
||||
|
||||
// Start loading all the downloads from the database asynchronously.
|
||||
aData.ensurePersistentDataLoaded(false);
|
||||
|
||||
// Wait for focus on the main window.
|
||||
waitForFocus(testRunner.continueTest);
|
||||
yield undefined;
|
||||
|
||||
// Open the downloads panel, waiting until loading is completed.
|
||||
let promise = promisePanelOpened();
|
||||
DownloadsPanel.showPanel();
|
||||
yield undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spin the event loop for aSeconds seconds, and then signal the test to
|
||||
* continue.
|
||||
*
|
||||
* @param aSeconds the number of seconds to wait.
|
||||
* @note This helper should _only_ be used when there's no valid event to
|
||||
* listen to and one can't be made.
|
||||
*/
|
||||
function waitFor(aSeconds)
|
||||
{
|
||||
setTimeout(function() {
|
||||
testRunner.continueTest();
|
||||
}, aSeconds * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make it so that the next time the downloads panel opens, we signal to
|
||||
* continue the test. This function needs to be called each time you want
|
||||
* to wait for the panel to open.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* prepareForPanelOpen();
|
||||
* // Do something to open the panel
|
||||
* yield undefined;
|
||||
* // We can assume the panel is open now.
|
||||
*/
|
||||
function prepareForPanelOpen()
|
||||
{
|
||||
// Hook to wait until the test data has been loaded.
|
||||
let originalOnPopupShown = DownloadsPanel.onPopupShown;
|
||||
DownloadsPanel.onPopupShown = function (aEvent) {
|
||||
DownloadsPanel.onPopupShown = originalOnPopupShown;
|
||||
DownloadsPanel.onPopupShown.apply(this, [aEvent]);
|
||||
testRunner.continueTest();
|
||||
};
|
||||
yield promise;
|
||||
}
|
||||
|
@ -262,6 +262,12 @@ let SessionSaverInternal = {
|
||||
return;
|
||||
}
|
||||
|
||||
// We update the time stamp before writing so that we don't write again
|
||||
// too soon, if saving is requested before the write completes. Without
|
||||
// this update we may save repeatedly if actions cause a runDelayed
|
||||
// before writing has completed. See Bug 902280
|
||||
this.updateLastSaveTime();
|
||||
|
||||
// Write (atomically) to a session file, using a tmp file. Once the session
|
||||
// file is successfully updated, save the time stamp of the last save and
|
||||
// notify the observers.
|
||||
|
@ -75,7 +75,7 @@ this._SessionFile = {
|
||||
* state. This must only be called once, it will throw an error otherwise.
|
||||
*/
|
||||
writeLoadStateOnceAfterStartup: function (aLoadState) {
|
||||
return SessionFileInternal.writeLoadStateOnceAfterStartup(aLoadState);
|
||||
SessionFileInternal.writeLoadStateOnceAfterStartup(aLoadState);
|
||||
},
|
||||
/**
|
||||
* Create a backup copy, asynchronously.
|
||||
@ -95,7 +95,7 @@ this._SessionFile = {
|
||||
* Wipe the contents of the session file, asynchronously.
|
||||
*/
|
||||
wipe: function () {
|
||||
return SessionFileInternal.wipe();
|
||||
SessionFileInternal.wipe();
|
||||
}
|
||||
};
|
||||
|
||||
@ -231,7 +231,7 @@ let SessionFileInternal = {
|
||||
},
|
||||
|
||||
writeLoadStateOnceAfterStartup: function (aLoadState) {
|
||||
return SessionWorker.post("writeLoadStateOnceAfterStartup", [aLoadState]).then(msg => {
|
||||
SessionWorker.post("writeLoadStateOnceAfterStartup", [aLoadState]).then(msg => {
|
||||
this._recordTelemetry(msg.telemetry);
|
||||
return msg;
|
||||
});
|
||||
@ -246,7 +246,7 @@ let SessionFileInternal = {
|
||||
},
|
||||
|
||||
wipe: function () {
|
||||
return SessionWorker.post("wipe");
|
||||
SessionWorker.post("wipe");
|
||||
},
|
||||
|
||||
_recordTelemetry: function(telemetry) {
|
||||
|
@ -91,6 +91,11 @@ this.Social = {
|
||||
providers: [],
|
||||
_disabledForSafeMode: false,
|
||||
|
||||
get allowMultipleWorkers() {
|
||||
return Services.prefs.prefHasUserValue("social.allowMultipleWorkers") &&
|
||||
Services.prefs.getBoolPref("social.allowMultipleWorkers");
|
||||
},
|
||||
|
||||
get _currentProviderPref() {
|
||||
try {
|
||||
return Services.prefs.getComplexValue("social.provider.current",
|
||||
@ -114,15 +119,14 @@ this.Social = {
|
||||
this._setProvider(val);
|
||||
},
|
||||
|
||||
// Sets the current provider and enables it. Also disables the
|
||||
// previously set provider, and notifies observers of the change.
|
||||
// Sets the current provider and notifies observers of the change.
|
||||
_setProvider: function (provider) {
|
||||
if (this._provider == provider)
|
||||
return;
|
||||
|
||||
// Disable the previous provider, if any, since we want only one provider to
|
||||
// be enabled at once.
|
||||
if (this._provider)
|
||||
// Disable the previous provider, if we are not allowing multiple workers,
|
||||
// since we want only one provider to be enabled at once.
|
||||
if (this._provider && !Social.allowMultipleWorkers)
|
||||
this._provider.enabled = false;
|
||||
|
||||
this._provider = provider;
|
||||
@ -134,7 +138,6 @@ this.Social = {
|
||||
let enabled = !!provider;
|
||||
if (enabled != SocialService.enabled) {
|
||||
SocialService.enabled = enabled;
|
||||
Services.prefs.setBoolPref("social.enabled", enabled);
|
||||
}
|
||||
|
||||
let origin = this._provider && this._provider.origin;
|
||||
@ -159,31 +162,40 @@ this.Social = {
|
||||
if (SocialService.enabled) {
|
||||
// Retrieve the current set of providers, and set the current provider.
|
||||
SocialService.getOrderedProviderList(function (providers) {
|
||||
this._updateProviderCache(providers);
|
||||
}.bind(this));
|
||||
Social._updateProviderCache(providers);
|
||||
Social._updateWorkerState(true);
|
||||
});
|
||||
}
|
||||
|
||||
// Register an observer for changes to the provider list
|
||||
SocialService.registerProviderListener(function providerListener(topic, data) {
|
||||
// An engine change caused by adding/removing a provider should notify
|
||||
// An engine change caused by adding/removing a provider should notify.
|
||||
// any providers we receive are enabled in the AddonsManager
|
||||
if (topic == "provider-added" || topic == "provider-removed") {
|
||||
this._updateProviderCache(data);
|
||||
Social._updateProviderCache(data);
|
||||
Social._updateWorkerState(true);
|
||||
Services.obs.notifyObservers(null, "social:providers-changed", null);
|
||||
return;
|
||||
}
|
||||
if (topic == "provider-update") {
|
||||
// a provider has self-updated its manifest, we need to update our
|
||||
// cache and possibly reload if it was the current provider.
|
||||
// a provider has self-updated its manifest, we need to update our cache
|
||||
// and reload the provider.
|
||||
let provider = data;
|
||||
// if we need a reload, do it now
|
||||
if (provider.enabled) {
|
||||
Social.enabled = false;
|
||||
Services.tm.mainThread.dispatch(function() {
|
||||
Social.enabled = true;
|
||||
}, Components.interfaces.nsIThread.DISPATCH_NORMAL);
|
||||
}
|
||||
SocialService.getOrderedProviderList(function(providers) {
|
||||
Social._updateProviderCache(providers);
|
||||
provider.reload();
|
||||
Services.obs.notifyObservers(null, "social:providers-changed", null);
|
||||
});
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
|
||||
_updateWorkerState: function(enable) {
|
||||
// ensure that our providers are all disabled, and enabled if we allow
|
||||
// multiple workers
|
||||
if (enable && !Social.allowMultipleWorkers)
|
||||
return;
|
||||
[p.enabled = enable for (p of Social.providers) if (p.enabled != enable)];
|
||||
},
|
||||
|
||||
// Called to update our cache of providers and set the current provider
|
||||
@ -203,6 +215,9 @@ this.Social = {
|
||||
set enabled(val) {
|
||||
// Setting .enabled is just a shortcut for setting the provider to either
|
||||
// the default provider or null...
|
||||
|
||||
this._updateWorkerState(val);
|
||||
|
||||
if (val) {
|
||||
if (!this.provider)
|
||||
this.provider = this.defaultProvider;
|
||||
@ -210,6 +225,7 @@ this.Social = {
|
||||
this.provider = null;
|
||||
}
|
||||
},
|
||||
|
||||
get enabled() {
|
||||
return this.provider != null;
|
||||
},
|
||||
@ -229,10 +245,6 @@ this.Social = {
|
||||
Services.prefs.setBoolPref("social.toast-notifications.enabled", !prefValue);
|
||||
},
|
||||
|
||||
haveLoggedInUser: function () {
|
||||
return !!(this.provider && this.provider.profile && this.provider.profile.userName);
|
||||
},
|
||||
|
||||
setProviderByOrigin: function (origin) {
|
||||
this.provider = this._getProviderFromOrigin(origin);
|
||||
},
|
||||
|
@ -711,6 +711,7 @@ GK_ATOM(onerror, "onerror")
|
||||
GK_ATOM(onfailed, "onfailed")
|
||||
GK_ATOM(onfocus, "onfocus")
|
||||
GK_ATOM(onget, "onget")
|
||||
GK_ATOM(ongroupchange, "ongroupchange")
|
||||
GK_ATOM(onhashchange, "onhashchange")
|
||||
GK_ATOM(onheadphoneschange, "onheadphoneschange")
|
||||
GK_ATOM(onheld, "onheld")
|
||||
|
@ -1162,6 +1162,11 @@ DOMInterfaces = {
|
||||
'headerFile': 'TelephonyCall.h',
|
||||
},
|
||||
|
||||
'TelephonyCallGroup' : {
|
||||
'nativeType': 'mozilla::dom::telephony::TelephonyCallGroup',
|
||||
'headerFile': 'TelephonyCallGroup.h',
|
||||
},
|
||||
|
||||
'Text': {
|
||||
# Total hack to allow binding code to realize that nsTextNode can
|
||||
# in fact be cast to Text.
|
||||
|
@ -32,7 +32,8 @@ TelephonyListener::CallStateChanged(uint32_t aCallIndex,
|
||||
const nsAString& aNumber,
|
||||
bool aIsActive,
|
||||
bool aIsOutgoing,
|
||||
bool aIsEmergency)
|
||||
bool aIsEmergency,
|
||||
bool aIsConference)
|
||||
{
|
||||
BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
|
||||
hfp->HandleCallStateChanged(aCallIndex, aCallState, EmptyString(), aNumber,
|
||||
@ -41,6 +42,12 @@ TelephonyListener::CallStateChanged(uint32_t aCallIndex,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelephonyListener::ConferenceCallStateChanged(uint16_t aCallState)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelephonyListener::EnumerateCallStateComplete()
|
||||
{
|
||||
@ -54,6 +61,7 @@ TelephonyListener::EnumerateCallState(uint32_t aCallIndex,
|
||||
bool aIsActive,
|
||||
bool aIsOutgoing,
|
||||
bool aIsEmergency,
|
||||
bool aIsConference,
|
||||
bool* aResult)
|
||||
{
|
||||
BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
|
||||
|
@ -108,7 +108,8 @@ const RIL_IPC_MSG_NAMES = [
|
||||
"RIL:CdmaCallWaiting",
|
||||
"RIL:ExitEmergencyCbMode",
|
||||
"RIL:SetVoicePrivacyMode",
|
||||
"RIL:GetVoicePrivacyMode"
|
||||
"RIL:GetVoicePrivacyMode",
|
||||
"RIL:ConferenceCallStateChanged"
|
||||
];
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
||||
@ -1392,6 +1393,31 @@ RILContentHelper.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
conferenceCall: function conferenceCall() {
|
||||
cpmm.sendAsyncMessage("RIL:ConferenceCall", {
|
||||
clientId: 0
|
||||
});
|
||||
},
|
||||
|
||||
separateCall: function separateCall(callIndex) {
|
||||
cpmm.sendAsyncMessage("RIL:SeparateCall", {
|
||||
clientId: 0,
|
||||
data: callIndex
|
||||
});
|
||||
},
|
||||
|
||||
holdConference: function holdConference() {
|
||||
cpmm.sendAsyncMessage("RIL:HoldConference", {
|
||||
clientId: 0
|
||||
});
|
||||
},
|
||||
|
||||
resumeConference: function resumeConference() {
|
||||
cpmm.sendAsyncMessage("RIL:ResumeConference", {
|
||||
clientId: 0
|
||||
});
|
||||
},
|
||||
|
||||
get microphoneMuted() {
|
||||
return cpmm.sendSyncMessage("RIL:GetMicrophoneMuted", {clientId: 0})[0];
|
||||
},
|
||||
@ -1528,7 +1554,15 @@ RILContentHelper.prototype = {
|
||||
"callStateChanged",
|
||||
[data.callIndex, data.state,
|
||||
data.number, data.isActive,
|
||||
data.isOutgoing, data.isEmergency]);
|
||||
data.isOutgoing, data.isEmergency,
|
||||
data.isConference]);
|
||||
break;
|
||||
}
|
||||
case "RIL:ConferenceCallStateChanged": {
|
||||
let data = msg.json.data;
|
||||
this._deliverEvent("_telephonyListeners",
|
||||
"conferenceCallStateChanged",
|
||||
[data]);
|
||||
break;
|
||||
}
|
||||
case "RIL:CallError": {
|
||||
@ -1699,7 +1733,7 @@ RILContentHelper.prototype = {
|
||||
keepGoing =
|
||||
callback.enumerateCallState(call.callIndex, call.state, call.number,
|
||||
call.isActive, call.isOutgoing,
|
||||
call.isEmergency);
|
||||
call.isEmergency, call.isConference);
|
||||
} catch (e) {
|
||||
debug("callback handler for 'enumerateCallState' threw an " +
|
||||
" exception: " + e);
|
||||
|
@ -90,7 +90,11 @@ const RIL_IPC_TELEPHONY_MSG_NAMES = [
|
||||
"RIL:RejectCall",
|
||||
"RIL:HoldCall",
|
||||
"RIL:ResumeCall",
|
||||
"RIL:RegisterTelephonyMsg"
|
||||
"RIL:RegisterTelephonyMsg",
|
||||
"RIL:ConferenceCall",
|
||||
"RIL:SeparateCall",
|
||||
"RIL:HoldConference",
|
||||
"RIL:ResumeConference"
|
||||
];
|
||||
|
||||
const RIL_IPC_MOBILECONNECTION_MSG_NAMES = [
|
||||
@ -930,6 +934,19 @@ RadioInterface.prototype = {
|
||||
case "RIL:ResumeCall":
|
||||
this.workerMessenger.send("resumeCall", { callIndex: msg.json.data });
|
||||
break;
|
||||
case "RIL:ConferenceCall":
|
||||
this.workerMessenger.send("conferenceCall");
|
||||
break;
|
||||
case "RIL:SeparateCall":
|
||||
this.workerMessenger.send("separateCall",
|
||||
{ callIndex: msg.json.data });
|
||||
break;
|
||||
case "RIL:HoldConference":
|
||||
this.workerMessenger.send("holdConference");
|
||||
break;
|
||||
case "RIL:ResumeConference":
|
||||
this.workerMessenger.send("resumeConference");
|
||||
break;
|
||||
case "RIL:GetAvailableNetworks":
|
||||
this.workerMessenger.sendWithIPCMessage(msg, "getAvailableNetworks");
|
||||
break;
|
||||
@ -1046,6 +1063,9 @@ RadioInterface.prototype = {
|
||||
// This one will handle its own notifications.
|
||||
this.handleCallDisconnected(message.call);
|
||||
break;
|
||||
case "conferenceCallStateChanged":
|
||||
this.handleConferenceCallStateChanged(message.state);
|
||||
break;
|
||||
case "cdmaCallWaiting":
|
||||
gMessageManager.sendTelephonyMessage("RIL:CdmaCallWaiting",
|
||||
this.clientId, message.number);
|
||||
@ -1657,7 +1677,35 @@ RadioInterface.prototype = {
|
||||
* Track the active call and update the audio system as its state changes.
|
||||
*/
|
||||
_activeCall: null,
|
||||
updateCallAudioState: function updateCallAudioState(call) {
|
||||
updateCallAudioState: function updateCallAudioState(options) {
|
||||
if (options.conferenceState === nsITelephonyProvider.CALL_STATE_CONNECTED) {
|
||||
gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL;
|
||||
if (this.speakerEnabled) {
|
||||
gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION,
|
||||
nsIAudioManager.FORCE_SPEAKER);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (options.conferenceState === nsITelephonyProvider.CALL_STATE_UNKNOWN ||
|
||||
options.conferenceState === nsITelephonyProvider.CALL_STATE_HELD) {
|
||||
if (!this._activeCall) {
|
||||
gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.call) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.call.isConference) {
|
||||
if (this._activeCall && this._activeCall.callIndex == options.call.callIndex) {
|
||||
this._activeCall = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let call = options.call;
|
||||
switch (call.state) {
|
||||
case nsITelephonyProvider.CALL_STATE_DIALING: // Fall through...
|
||||
case nsITelephonyProvider.CALL_STATE_ALERTING:
|
||||
@ -1751,7 +1799,7 @@ RadioInterface.prototype = {
|
||||
if (call.state == nsITelephonyProvider.CALL_STATE_DIALING) {
|
||||
gSystemMessenger.broadcastMessage("telephony-new-call", {});
|
||||
}
|
||||
this.updateCallAudioState(call);
|
||||
this.updateCallAudioState({call: call});
|
||||
gMessageManager.sendTelephonyMessage("RIL:CallStateChanged",
|
||||
this.clientId, call);
|
||||
},
|
||||
@ -1770,11 +1818,20 @@ RadioInterface.prototype = {
|
||||
direction: call.isOutgoing ? "outgoing" : "incoming"
|
||||
};
|
||||
gSystemMessenger.broadcastMessage("telephony-call-ended", data);
|
||||
this.updateCallAudioState(call);
|
||||
this.updateCallAudioState({call: call});
|
||||
gMessageManager.sendTelephonyMessage("RIL:CallStateChanged",
|
||||
this.clientId, call);
|
||||
},
|
||||
|
||||
handleConferenceCallStateChanged: function handleConferenceCallStateChanged(state) {
|
||||
debug("handleConferenceCallStateChanged: " + state);
|
||||
state = state != null ? convertRILCallState(state) :
|
||||
nsITelephonyProvider.CALL_STATE_UNKNOWN;
|
||||
this.updateCallAudioState({conferenceState: state});
|
||||
gMessageManager.sendTelephonyMessage("RIL:ConferenceCallStateChanged",
|
||||
this.clientId, state);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update network selection mode
|
||||
*/
|
||||
|
@ -735,6 +735,11 @@ let RIL = {
|
||||
*/
|
||||
currentCalls: {},
|
||||
|
||||
/**
|
||||
* Existing conference call and its participants.
|
||||
*/
|
||||
currentConference: {state: null, participants: {}},
|
||||
|
||||
/**
|
||||
* Existing data calls.
|
||||
*/
|
||||
@ -2069,6 +2074,29 @@ let RIL = {
|
||||
}
|
||||
},
|
||||
|
||||
// Flag indicating whether user has requested making a conference call.
|
||||
_hasConferenceRequest: false,
|
||||
|
||||
conferenceCall: function conferenceCall(options) {
|
||||
this._hasConferenceRequest = true;
|
||||
Buf.simpleRequest(REQUEST_CONFERENCE, options);
|
||||
},
|
||||
|
||||
separateCall: function separateCall(options) {
|
||||
Buf.newParcel(REQUEST_SEPARATE_CONNECTION, options);
|
||||
Buf.writeUint32(1);
|
||||
Buf.writeUint32(options.callIndex);
|
||||
Buf.sendParcel();
|
||||
},
|
||||
|
||||
holdConference: function holdConference() {
|
||||
Buf.simpleRequest(REQUEST_SWITCH_HOLDING_AND_ACTIVE);
|
||||
},
|
||||
|
||||
resumeConference: function resumeConference() {
|
||||
Buf.simpleRequest(REQUEST_SWITCH_HOLDING_AND_ACTIVE);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send an SMS.
|
||||
*
|
||||
@ -3881,6 +3909,9 @@ let RIL = {
|
||||
* Helpers for processing call state and handle the active call.
|
||||
*/
|
||||
_processCalls: function _processCalls(newCalls) {
|
||||
let conferenceChanged = false;
|
||||
let clearConferenceRequest = false;
|
||||
|
||||
// Go through the calls we currently have on file and see if any of them
|
||||
// changed state. Remove them from the newCalls map as we deal with them
|
||||
// so that only new calls remain in the map after we're done.
|
||||
@ -3894,13 +3925,26 @@ let RIL = {
|
||||
if (!newCall) {
|
||||
// Call is no longer reported by the radio. Remove from our map and
|
||||
// send disconnected state change.
|
||||
delete this.currentCalls[currentCall.callIndex];
|
||||
this.getFailCauseCode(currentCall);
|
||||
|
||||
if (this.currentConference.participants[currentCall.callIndex]) {
|
||||
conferenceChanged = true;
|
||||
currentCall.isConference = false;
|
||||
delete this.currentConference.participants[currentCall.callIndex];
|
||||
delete this.currentCalls[currentCall.callIndex];
|
||||
// We don't query the fail cause here as it triggers another asynchrouns
|
||||
// request that leads to a problem of updating all conferece participants
|
||||
// in one task.
|
||||
this._handleDisconnectedCall(currentCall);
|
||||
} else {
|
||||
delete this.currentCalls[currentCall.callIndex];
|
||||
this.getFailCauseCode(currentCall);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Call is still valid.
|
||||
if (newCall.state == currentCall.state) {
|
||||
if (newCall.state == currentCall.state &&
|
||||
newCall.isMpty == currentCall.isMpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -3916,8 +3960,71 @@ let RIL = {
|
||||
if (!currentCall.started && newCall.state == CALL_STATE_ACTIVE) {
|
||||
currentCall.started = new Date().getTime();
|
||||
}
|
||||
currentCall.state = newCall.state;
|
||||
this._handleChangedCallState(currentCall);
|
||||
|
||||
if (currentCall.isMpty == newCall.isMpty &&
|
||||
newCall.state != currentCall.state) {
|
||||
currentCall.state = newCall.state;
|
||||
if (currentCall.isConference) {
|
||||
conferenceChanged = true;
|
||||
}
|
||||
this._handleChangedCallState(currentCall);
|
||||
continue;
|
||||
}
|
||||
|
||||
// '.isMpty' becomes false when the conference call is put on hold.
|
||||
// We need to introduce additional 'isConference' to correctly record the
|
||||
// real conference status
|
||||
|
||||
// Update a possible conference participant when .isMpty changes.
|
||||
if (!currentCall.isMpty && newCall.isMpty) {
|
||||
if (this._hasConferenceRequest) {
|
||||
conferenceChanged = true;
|
||||
clearConferenceRequest = true;
|
||||
currentCall.state = newCall.state;
|
||||
currentCall.isMpty = newCall.isMpty;
|
||||
currentCall.isConference = true;
|
||||
this.currentConference.participants[currentCall.callIndex] = currentCall;
|
||||
this._handleChangedCallState(currentCall);
|
||||
} else if (currentCall.isConference) {
|
||||
// The case happens when resuming a held conference call.
|
||||
conferenceChanged = true;
|
||||
currentCall.state = newCall.state;
|
||||
currentCall.isMpty = newCall.isMpty;
|
||||
this.currentConference.participants[currentCall.callIndex] = currentCall;
|
||||
this._handleChangedCallState(currentCall);
|
||||
} else {
|
||||
// Weird. This sometimes happens when we switch two calls, but it is
|
||||
// not a conference call.
|
||||
currentCall.state = newCall.state;
|
||||
this._handleChangedCallState(currentCall);
|
||||
}
|
||||
} else if (currentCall.isMpty && !newCall.isMpty) {
|
||||
if (!this.currentConference.participants[newCall.callIndex]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// '.isMpty' of a conference participant is set to false by rild when
|
||||
// the conference call is put on hold. We don't actually know if the call
|
||||
// still attends the conference until updating all calls finishes. We
|
||||
// cache it for further determination.
|
||||
if (newCall.state != CALL_STATE_HOLDING) {
|
||||
delete this.currentConference.participants[newCall.callIndex];
|
||||
currentCall.state = newCall.state;
|
||||
currentCall.isMpty = newCall.isMpty;
|
||||
currentCall.isConference = false;
|
||||
conferenceChanged = true;
|
||||
this._handleChangedCallState(currentCall);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.currentConference.cache) {
|
||||
this.currentConference.cache = {};
|
||||
}
|
||||
this.currentConference.cache[currentCall.callIndex] = newCall;
|
||||
currentCall.state = newCall.state;
|
||||
currentCall.isMpty = newCall.isMpty;
|
||||
conferenceChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Go through any remaining calls that are new to us.
|
||||
@ -3944,16 +4051,69 @@ let RIL = {
|
||||
}
|
||||
|
||||
// Add to our map.
|
||||
this.currentCalls[newCall.callIndex] = newCall;
|
||||
if (newCall.isMpty) {
|
||||
conferenceChanged = true;
|
||||
newCall.isConference = true;
|
||||
this.currentConference.participants[newCall.callIndex] = newCall;
|
||||
} else {
|
||||
newCall.isConference = false;
|
||||
}
|
||||
this._handleChangedCallState(newCall);
|
||||
this.currentCalls[newCall.callIndex] = newCall;
|
||||
}
|
||||
}
|
||||
|
||||
if (clearConferenceRequest) {
|
||||
this._hasConferenceRequest = false;
|
||||
}
|
||||
if (conferenceChanged) {
|
||||
this._ensureConference();
|
||||
}
|
||||
|
||||
// Update our mute status. If there is anything in our currentCalls map then
|
||||
// we know it's a voice call and we should leave audio on.
|
||||
this.muted = (Object.getOwnPropertyNames(this.currentCalls).length === 0);
|
||||
},
|
||||
|
||||
_ensureConference: function _ensureConference() {
|
||||
let oldState = this.currentConference.state;
|
||||
let remaining = Object.keys(this.currentConference.participants);
|
||||
|
||||
if (remaining.length == 1) {
|
||||
// Remove that if only does one remain in a conference call.
|
||||
let call = this.currentCalls[remaining[0]];
|
||||
call.isConference = false;
|
||||
this._handleChangedCallState(call);
|
||||
delete this.currentConference.participants[call.callIndex];
|
||||
} else if (remaining.length > 1) {
|
||||
for each (let call in this.currentConference.cache) {
|
||||
call.isConference = true;
|
||||
this.currentConference.participants[call.callIndex] = call;
|
||||
this.currentCalls[call.callIndex] = call;
|
||||
this._handleChangedCallState(call);
|
||||
}
|
||||
}
|
||||
delete this.currentConference.cache;
|
||||
|
||||
// Update the conference call's state.
|
||||
let state = null;
|
||||
for each (let call in this.currentConference.participants) {
|
||||
if (state && state != call.state) {
|
||||
// Each participant should have the same state, otherwise something
|
||||
// wrong happens.
|
||||
state = null;
|
||||
break;
|
||||
}
|
||||
state = call.state;
|
||||
}
|
||||
if (oldState != state) {
|
||||
this.currentConference.state = state;
|
||||
let message = {rilMessageType: "conferenceCallStateChanged",
|
||||
state: state};
|
||||
this.sendChromeMessage(message);
|
||||
}
|
||||
},
|
||||
|
||||
_handleChangedCallState: function _handleChangedCallState(changedCall) {
|
||||
let message = {rilMessageType: "callStateChange",
|
||||
call: changedCall};
|
||||
@ -5276,7 +5436,12 @@ RIL[REQUEST_SWITCH_HOLDING_AND_ACTIVE] = function REQUEST_SWITCH_HOLDING_AND_ACT
|
||||
// this.getCurrentCalls() helps update the call state actively.
|
||||
this.getCurrentCalls();
|
||||
};
|
||||
RIL[REQUEST_CONFERENCE] = null;
|
||||
RIL[REQUEST_CONFERENCE] = function REQUEST_CONFERENCE(length, options) {
|
||||
if (options.rilRequestError) {
|
||||
this._hasConferenceRequest = false;
|
||||
return;
|
||||
}
|
||||
};
|
||||
RIL[REQUEST_UDUB] = null;
|
||||
RIL[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) {
|
||||
let num = 0;
|
||||
|
@ -9,11 +9,14 @@
|
||||
|
||||
#include "Telephony.h"
|
||||
#include "TelephonyCall.h"
|
||||
#include "TelephonyCallGroup.h"
|
||||
|
||||
USING_TELEPHONY_NAMESPACE
|
||||
using namespace mozilla::dom;
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(CallsList, mTelephony)
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(CallsList,
|
||||
mTelephony,
|
||||
mGroup)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(CallsList)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(CallsList)
|
||||
@ -23,8 +26,8 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallsList)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
CallsList::CallsList(Telephony* aTelephony)
|
||||
: mTelephony(aTelephony)
|
||||
CallsList::CallsList(Telephony* aTelephony, TelephonyCallGroup* aGroup)
|
||||
: mTelephony(aTelephony), mGroup(aGroup)
|
||||
{
|
||||
MOZ_ASSERT(mTelephony);
|
||||
|
||||
@ -50,20 +53,27 @@ CallsList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
|
||||
already_AddRefed<TelephonyCall>
|
||||
CallsList::Item(uint32_t aIndex) const
|
||||
{
|
||||
nsRefPtr<TelephonyCall> call = mTelephony->CallsArray().SafeElementAt(aIndex);
|
||||
nsRefPtr<TelephonyCall> call;
|
||||
call = mGroup ? mGroup->CallsArray().SafeElementAt(aIndex) :
|
||||
mTelephony->CallsArray().SafeElementAt(aIndex);
|
||||
|
||||
return call.forget();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
CallsList::Length() const
|
||||
{
|
||||
return mTelephony->CallsArray().Length();
|
||||
return mGroup ? mGroup->CallsArray().Length() :
|
||||
mTelephony->CallsArray().Length();
|
||||
}
|
||||
|
||||
already_AddRefed<TelephonyCall>
|
||||
CallsList::IndexedGetter(uint32_t aIndex, bool& aFound) const
|
||||
{
|
||||
nsRefPtr<TelephonyCall> call = mTelephony->CallsArray().SafeElementAt(aIndex);
|
||||
nsRefPtr<TelephonyCall> call;
|
||||
call = mGroup ? mGroup->CallsArray().SafeElementAt(aIndex) :
|
||||
mTelephony->CallsArray().SafeElementAt(aIndex);
|
||||
aFound = call ? true : false;
|
||||
|
||||
return call.forget();
|
||||
}
|
||||
|
@ -17,12 +17,13 @@ class CallsList MOZ_FINAL : public nsISupports,
|
||||
public nsWrapperCache
|
||||
{
|
||||
nsRefPtr<Telephony> mTelephony;
|
||||
nsRefPtr<TelephonyCallGroup> mGroup;
|
||||
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallsList)
|
||||
|
||||
CallsList(Telephony* aTelephony);
|
||||
CallsList(Telephony* aTelephony, TelephonyCallGroup* aGroup = nullptr);
|
||||
|
||||
nsPIDOMWindow*
|
||||
GetParentObject() const;
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsIPermissionManager.h"
|
||||
|
||||
#include "mozilla/dom/UnionTypes.h"
|
||||
#include "nsCharSeparatedTokenizer.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsCxPusher.h"
|
||||
@ -21,6 +22,7 @@
|
||||
#include "CallEvent.h"
|
||||
#include "CallsList.h"
|
||||
#include "TelephonyCall.h"
|
||||
#include "TelephonyCallGroup.h"
|
||||
|
||||
#define NS_RILCONTENTHELPER_CONTRACTID "@mozilla.org/ril/content-helper;1"
|
||||
|
||||
@ -89,13 +91,7 @@ Telephony::Telephony()
|
||||
|
||||
Telephony::~Telephony()
|
||||
{
|
||||
if (mListener) {
|
||||
mListener->Disconnect();
|
||||
|
||||
if (mProvider) {
|
||||
mProvider->UnregisterTelephonyMsg(mListener);
|
||||
}
|
||||
}
|
||||
Shutdown();
|
||||
|
||||
NS_ASSERTION(gTelephonyList, "This should never be null!");
|
||||
NS_ASSERTION(gTelephonyList->Contains(this), "Should be in the list!");
|
||||
@ -103,12 +99,26 @@ Telephony::~Telephony()
|
||||
if (gTelephonyList->Length() == 1) {
|
||||
delete gTelephonyList;
|
||||
gTelephonyList = nullptr;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
gTelephonyList->RemoveElement(this);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Telephony::Shutdown()
|
||||
{
|
||||
if (mListener) {
|
||||
mListener->Disconnect();
|
||||
|
||||
if (mProvider) {
|
||||
mProvider->UnregisterTelephonyMsg(mListener);
|
||||
mProvider = nullptr;
|
||||
}
|
||||
|
||||
mListener = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
JSObject*
|
||||
Telephony::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
|
||||
{
|
||||
@ -147,6 +157,7 @@ Telephony::Create(nsPIDOMWindow* aOwner, ErrorResult& aRv)
|
||||
telephony->mProvider = ril;
|
||||
telephony->mListener = new Listener(telephony);
|
||||
telephony->mCallsList = new CallsList(telephony);
|
||||
telephony->mGroup = TelephonyCallGroup::Create(telephony);
|
||||
|
||||
nsresult rv = ril->EnumerateCalls(telephony->mListener);
|
||||
if (NS_FAILED(rv)) {
|
||||
@ -186,17 +197,6 @@ Telephony::NoteDialedCallFromOtherInstance(const nsAString& aNumber)
|
||||
nsresult
|
||||
Telephony::NotifyCallsChanged(TelephonyCall* aCall)
|
||||
{
|
||||
if (aCall) {
|
||||
if (aCall->CallState() == nsITelephonyProvider::CALL_STATE_DIALING ||
|
||||
aCall->CallState() == nsITelephonyProvider::CALL_STATE_ALERTING ||
|
||||
aCall->CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED) {
|
||||
NS_ASSERTION(!mActiveCall, "Already have an active call!");
|
||||
mActiveCall = aCall;
|
||||
} else if (mActiveCall && mActiveCall->CallIndex() == aCall->CallIndex()) {
|
||||
mActiveCall = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return DispatchCallEvent(NS_LITERAL_STRING("callschanged"), aCall);
|
||||
}
|
||||
|
||||
@ -247,19 +247,81 @@ Telephony::DialInternal(bool isEmergency,
|
||||
return call.forget();
|
||||
}
|
||||
|
||||
void
|
||||
Telephony::UpdateActiveCall(TelephonyCall* aCall, bool aIsAdding)
|
||||
{
|
||||
if (aIsAdding) {
|
||||
if (aCall->CallState() == nsITelephonyProvider::CALL_STATE_DIALING ||
|
||||
aCall->CallState() == nsITelephonyProvider::CALL_STATE_ALERTING ||
|
||||
aCall->CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED) {
|
||||
NS_ASSERTION(!mActiveCall, "Already have an active call!");
|
||||
mActiveCall = aCall;
|
||||
}
|
||||
} else if (mActiveCall && mActiveCall->CallIndex() == aCall->CallIndex()) {
|
||||
mActiveCall = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<TelephonyCall>
|
||||
Telephony::GetCall(uint32_t aCallIndex)
|
||||
{
|
||||
nsRefPtr<TelephonyCall> call;
|
||||
|
||||
for (uint32_t index = 0; index < mCalls.Length(); index++) {
|
||||
nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
|
||||
if (tempCall->CallIndex() == aCallIndex) {
|
||||
call = tempCall;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return call.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
Telephony::MoveCall(uint32_t aCallIndex, bool aIsConference)
|
||||
{
|
||||
nsRefPtr<TelephonyCall> call;
|
||||
|
||||
// Move a call to mGroup.
|
||||
if (aIsConference) {
|
||||
call = GetCall(aCallIndex);
|
||||
if (call) {
|
||||
RemoveCall(call);
|
||||
mGroup->AddCall(call);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove a call from mGroup.
|
||||
call = mGroup->GetCall(aCallIndex);
|
||||
if (call) {
|
||||
mGroup->RemoveCall(call);
|
||||
AddCall(call);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(Telephony)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Telephony,
|
||||
nsDOMEventTargetHelper)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCalls)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallsList)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGroup)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Telephony,
|
||||
nsDOMEventTargetHelper)
|
||||
tmp->Shutdown();
|
||||
tmp->mActiveCall = nullptr;
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCalls)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallsList)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGroup)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Telephony)
|
||||
@ -316,11 +378,16 @@ Telephony::SetSpeakerEnabled(bool aEnabled, ErrorResult& aRv)
|
||||
aRv = mProvider->SetSpeakerEnabled(aEnabled);
|
||||
}
|
||||
|
||||
already_AddRefed<TelephonyCall>
|
||||
Telephony::GetActive() const
|
||||
void
|
||||
Telephony::GetActive(Nullable<TelephonyCallOrTelephonyCallGroupReturnValue>& aValue)
|
||||
{
|
||||
nsCOMPtr<TelephonyCall> activeCall = mActiveCall;
|
||||
return activeCall.forget();
|
||||
if (mActiveCall) {
|
||||
aValue.SetValue().SetAsTelephonyCall() = mActiveCall;
|
||||
} else if (mGroup->CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED) {
|
||||
aValue.SetValue().SetAsTelephonyCallGroup() = mGroup;
|
||||
} else {
|
||||
aValue.SetNull();
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<CallsList>
|
||||
@ -330,6 +397,13 @@ Telephony::Calls() const
|
||||
return list.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<TelephonyCallGroup>
|
||||
Telephony::ConferenceGroup() const
|
||||
{
|
||||
nsRefPtr<TelephonyCallGroup> group = mGroup;
|
||||
return group.forget();
|
||||
}
|
||||
|
||||
void
|
||||
Telephony::StartTone(const nsAString& aDTMFChar, ErrorResult& aRv)
|
||||
{
|
||||
@ -368,7 +442,8 @@ Telephony::EventListenerAdded(nsIAtom* aType)
|
||||
NS_IMETHODIMP
|
||||
Telephony::CallStateChanged(uint32_t aCallIndex, uint16_t aCallState,
|
||||
const nsAString& aNumber, bool aIsActive,
|
||||
bool aIsOutgoing, bool aIsEmergency)
|
||||
bool aIsOutgoing, bool aIsEmergency,
|
||||
bool aIsConference)
|
||||
{
|
||||
NS_ASSERTION(aCallIndex != kOutgoingPlaceholderCallIndex,
|
||||
"This should never happen!");
|
||||
@ -376,6 +451,57 @@ Telephony::CallStateChanged(uint32_t aCallIndex, uint16_t aCallState,
|
||||
nsRefPtr<TelephonyCall> modifiedCall;
|
||||
nsRefPtr<TelephonyCall> outgoingCall;
|
||||
|
||||
// Update calls array first then state of a call in mCalls.
|
||||
|
||||
if (aIsConference) {
|
||||
// Add the call into mGroup if it hasn't been there, otherwise we simply
|
||||
// update its call state. We don't fire the statechange event on a call in
|
||||
// conference here. Instead, the event will be fired later in
|
||||
// TelephonyCallGroup::ChangeState(). Thus the sequence of firing the
|
||||
// statechange events is guaranteed: first on TelephonyCallGroup then on
|
||||
// individual TelephonyCall objects.
|
||||
|
||||
modifiedCall = mGroup->GetCall(aCallIndex);
|
||||
if (modifiedCall) {
|
||||
modifiedCall->ChangeStateInternal(aCallState, false);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// The call becomes a conference call. Remove it from Telephony::mCalls and
|
||||
// add it to mGroup.
|
||||
modifiedCall = GetCall(aCallIndex);
|
||||
if (modifiedCall) {
|
||||
modifiedCall->ChangeStateInternal(aCallState, false);
|
||||
mGroup->AddCall(modifiedCall);
|
||||
RemoveCall(modifiedCall);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Didn't find this call in mCalls or mGroup. Create a new call.
|
||||
nsRefPtr<TelephonyCall> call =
|
||||
TelephonyCall::Create(this, aNumber, aCallState, aCallIndex,
|
||||
aIsEmergency, aIsConference);
|
||||
NS_ASSERTION(call, "This should never fail!");
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Not a conference call. Remove the call from mGroup if it has been there.
|
||||
modifiedCall = mGroup->GetCall(aCallIndex);
|
||||
if (modifiedCall) {
|
||||
if (aCallState != nsITelephonyProvider::CALL_STATE_DISCONNECTED) {
|
||||
if (modifiedCall->CallState() != aCallState) {
|
||||
modifiedCall->ChangeState(aCallState);
|
||||
}
|
||||
mGroup->RemoveCall(modifiedCall);
|
||||
AddCall(modifiedCall);
|
||||
} else {
|
||||
modifiedCall->ChangeState(aCallState);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Update calls in mCalls.
|
||||
for (uint32_t index = 0; index < mCalls.Length(); index++) {
|
||||
nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
|
||||
if (tempCall->CallIndex() == kOutgoingPlaceholderCallIndex) {
|
||||
@ -441,6 +567,13 @@ Telephony::CallStateChanged(uint32_t aCallIndex, uint16_t aCallState,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Telephony::ConferenceCallStateChanged(uint16_t aCallState)
|
||||
{
|
||||
mGroup->ChangeState(aCallState);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Telephony::EnumerateCallStateComplete()
|
||||
{
|
||||
@ -458,23 +591,36 @@ NS_IMETHODIMP
|
||||
Telephony::EnumerateCallState(uint32_t aCallIndex, uint16_t aCallState,
|
||||
const nsAString& aNumber, bool aIsActive,
|
||||
bool aIsOutgoing, bool aIsEmergency,
|
||||
bool* aContinue)
|
||||
bool aIsConference, bool* aContinue)
|
||||
{
|
||||
// Make sure we don't somehow add duplicates.
|
||||
for (uint32_t index = 0; index < mCalls.Length(); index++) {
|
||||
nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
|
||||
if (tempCall->CallIndex() == aCallIndex) {
|
||||
// We have the call already. Skip it.
|
||||
*aContinue = true;
|
||||
return NS_OK;
|
||||
}
|
||||
nsRefPtr<TelephonyCall> call;
|
||||
|
||||
// We request calls enumeration in constructor, and the asynchronous result
|
||||
// will be sent back through the callback function EnumerateCallState().
|
||||
// However, it is likely to have call state changes, i.e. CallStateChanged()
|
||||
// being called, before the enumeration result comes back. We'd make sure
|
||||
// we don't somehow add duplicates due to the race condition.
|
||||
call = aIsConference ? mGroup->GetCall(aCallIndex) : GetCall(aCallIndex);
|
||||
if (call) {
|
||||
// We have the call either in mCalls or in mGroup. Skip it.
|
||||
*aContinue = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsRefPtr<TelephonyCall> call =
|
||||
TelephonyCall::Create(this, aNumber, aCallState, aCallIndex, aIsEmergency);
|
||||
if (MoveCall(aCallIndex, aIsConference)) {
|
||||
*aContinue = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Didn't know anything about this call before now.
|
||||
|
||||
call = TelephonyCall::Create(this, aNumber, aCallState, aCallIndex,
|
||||
aIsEmergency, aIsConference);
|
||||
NS_ASSERTION(call, "This should never fail!");
|
||||
|
||||
NS_ASSERTION(mCalls.Contains(call), "Should have auto-added new call!");
|
||||
NS_ASSERTION(aIsConference ? mGroup->CallsArray().Contains(call) :
|
||||
mCalls.Contains(call),
|
||||
"Should have auto-added new call!");
|
||||
|
||||
*aContinue = true;
|
||||
return NS_OK;
|
||||
@ -486,13 +632,7 @@ Telephony::SupplementaryServiceNotification(int32_t aCallIndex,
|
||||
{
|
||||
nsRefPtr<TelephonyCall> associatedCall;
|
||||
if (!mCalls.IsEmpty() && aCallIndex != -1) {
|
||||
for (uint32_t index = 0; index < mCalls.Length(); index++) {
|
||||
nsRefPtr<TelephonyCall>& call = mCalls[index];
|
||||
if (call->CallIndex() == uint32_t(aCallIndex)) {
|
||||
associatedCall = call;
|
||||
break;
|
||||
}
|
||||
}
|
||||
associatedCall = GetCall(aCallIndex);
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
@ -526,13 +666,7 @@ Telephony::NotifyError(int32_t aCallIndex,
|
||||
}
|
||||
} else {
|
||||
// The connection has been established. Get the failed call.
|
||||
for (uint32_t index = 0; index < mCalls.Length(); index++) {
|
||||
nsRefPtr<TelephonyCall>& call = mCalls[index];
|
||||
if (call->CallIndex() == aCallIndex) {
|
||||
callToNotify = call;
|
||||
break;
|
||||
}
|
||||
}
|
||||
callToNotify = GetCall(aCallIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,14 +8,21 @@
|
||||
#define mozilla_dom_telephony_telephony_h__
|
||||
|
||||
#include "TelephonyCommon.h"
|
||||
|
||||
#include "nsITelephonyProvider.h"
|
||||
|
||||
// Need to include TelephonyCall.h because we have inline methods that
|
||||
// assume they see the definition of TelephonyCall.
|
||||
#include "TelephonyCall.h"
|
||||
|
||||
#include "nsITelephonyProvider.h"
|
||||
|
||||
class nsPIDOMWindow;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
class TelephonyCallOrTelephonyCallGroupReturnValue;
|
||||
}
|
||||
}
|
||||
|
||||
BEGIN_TELEPHONY_NAMESPACE
|
||||
|
||||
class Telephony MOZ_FINAL : public nsDOMEventTargetHelper
|
||||
@ -39,6 +46,8 @@ class Telephony MOZ_FINAL : public nsDOMEventTargetHelper
|
||||
nsTArray<nsRefPtr<TelephonyCall> > mCalls;
|
||||
nsRefPtr<CallsList> mCallsList;
|
||||
|
||||
nsRefPtr<TelephonyCallGroup> mGroup;
|
||||
|
||||
bool mEnumerated;
|
||||
|
||||
public:
|
||||
@ -77,12 +86,15 @@ public:
|
||||
void
|
||||
SetSpeakerEnabled(bool aEnabled, ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<TelephonyCall>
|
||||
GetActive() const;
|
||||
void
|
||||
GetActive(Nullable<TelephonyCallOrTelephonyCallGroupReturnValue>& aValue);
|
||||
|
||||
already_AddRefed<CallsList>
|
||||
Calls() const;
|
||||
|
||||
already_AddRefed<TelephonyCallGroup>
|
||||
ConferenceGroup() const;
|
||||
|
||||
void
|
||||
StartTone(const nsAString& aDTMF, ErrorResult& aRv);
|
||||
|
||||
@ -104,6 +116,7 @@ public:
|
||||
{
|
||||
NS_ASSERTION(!mCalls.Contains(aCall), "Already know about this one!");
|
||||
mCalls.AppendElement(aCall);
|
||||
UpdateActiveCall(aCall, true);
|
||||
NotifyCallsChanged(aCall);
|
||||
}
|
||||
|
||||
@ -112,6 +125,7 @@ public:
|
||||
{
|
||||
NS_ASSERTION(mCalls.Contains(aCall), "Didn't know about this one!");
|
||||
mCalls.RemoveElement(aCall);
|
||||
UpdateActiveCall(aCall, false);
|
||||
NotifyCallsChanged(aCall);
|
||||
}
|
||||
|
||||
@ -153,6 +167,18 @@ private:
|
||||
|
||||
void
|
||||
EnqueueEnumerationAck();
|
||||
|
||||
void
|
||||
UpdateActiveCall(TelephonyCall* aCall, bool aIsAdding);
|
||||
|
||||
already_AddRefed<TelephonyCall>
|
||||
GetCall(uint32_t aCallIndex);
|
||||
|
||||
bool
|
||||
MoveCall(uint32_t aCallIndex, bool aIsConference);
|
||||
|
||||
void
|
||||
Shutdown();
|
||||
};
|
||||
|
||||
END_TELEPHONY_NAMESPACE
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "CallEvent.h"
|
||||
#include "Telephony.h"
|
||||
#include "TelephonyCallGroup.h"
|
||||
|
||||
USING_TELEPHONY_NAMESPACE
|
||||
using namespace mozilla::dom;
|
||||
@ -18,7 +19,8 @@ using namespace mozilla::dom;
|
||||
// static
|
||||
already_AddRefed<TelephonyCall>
|
||||
TelephonyCall::Create(Telephony* aTelephony, const nsAString& aNumber,
|
||||
uint16_t aCallState, uint32_t aCallIndex, bool aEmergency)
|
||||
uint16_t aCallState, uint32_t aCallIndex,
|
||||
bool aEmergency, bool aIsConference)
|
||||
{
|
||||
NS_ASSERTION(aTelephony, "Null pointer!");
|
||||
NS_ASSERTION(!aNumber.IsEmpty(), "Empty number!");
|
||||
@ -33,6 +35,7 @@ TelephonyCall::Create(Telephony* aTelephony, const nsAString& aNumber,
|
||||
call->mCallIndex = aCallIndex;
|
||||
call->mError = nullptr;
|
||||
call->mEmergency = aEmergency;
|
||||
call->mGroup = aIsConference ? aTelephony->ConferenceGroup() : nullptr;
|
||||
|
||||
call->ChangeStateInternal(aCallState, false);
|
||||
|
||||
@ -108,10 +111,18 @@ TelephonyCall::ChangeStateInternal(uint16_t aCallState, bool aFireEvents)
|
||||
|
||||
if (aCallState == nsITelephonyProvider::CALL_STATE_DISCONNECTED) {
|
||||
NS_ASSERTION(mLive, "Should be live!");
|
||||
mTelephony->RemoveCall(this);
|
||||
if (mGroup) {
|
||||
mGroup->RemoveCall(this);
|
||||
} else {
|
||||
mTelephony->RemoveCall(this);
|
||||
}
|
||||
mLive = false;
|
||||
} else if (!mLive) {
|
||||
mTelephony->AddCall(this);
|
||||
if (mGroup) {
|
||||
mGroup->AddCall(this);
|
||||
} else {
|
||||
mTelephony->AddCall(this);
|
||||
}
|
||||
mLive = true;
|
||||
}
|
||||
|
||||
@ -160,10 +171,22 @@ TelephonyCall::NotifyError(const nsAString& aError)
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED_2(TelephonyCall,
|
||||
void
|
||||
TelephonyCall::ChangeGroup(TelephonyCallGroup* aGroup)
|
||||
{
|
||||
mGroup = aGroup;
|
||||
|
||||
nsresult rv = DispatchCallEvent(NS_LITERAL_STRING("groupchange"), this);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to dispatch error event!");
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED_3(TelephonyCall,
|
||||
nsDOMEventTargetHelper,
|
||||
mTelephony,
|
||||
mError);
|
||||
mError,
|
||||
mGroup);
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TelephonyCall)
|
||||
NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
|
||||
@ -180,6 +203,13 @@ TelephonyCall::GetError() const
|
||||
return error.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<TelephonyCallGroup>
|
||||
TelephonyCall::GetGroup() const
|
||||
{
|
||||
nsRefPtr<TelephonyCallGroup> group = mGroup;
|
||||
return group.forget();
|
||||
}
|
||||
|
||||
void
|
||||
TelephonyCall::Answer(ErrorResult& aRv)
|
||||
{
|
||||
@ -225,6 +255,11 @@ TelephonyCall::Hold(ErrorResult& aRv)
|
||||
return;
|
||||
}
|
||||
|
||||
if (mGroup) {
|
||||
NS_WARNING("Hold a call in conference ignored!");
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = mTelephony->Provider()->HoldCall(mCallIndex);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
@ -242,6 +277,11 @@ TelephonyCall::Resume(ErrorResult& aRv)
|
||||
return;
|
||||
}
|
||||
|
||||
if (mGroup) {
|
||||
NS_WARNING("Resume a call in conference ignored!");
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = mTelephony->Provider()->ResumeCall(mCallIndex);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
|
@ -18,6 +18,7 @@ BEGIN_TELEPHONY_NAMESPACE
|
||||
class TelephonyCall MOZ_FINAL : public nsDOMEventTargetHelper
|
||||
{
|
||||
nsRefPtr<Telephony> mTelephony;
|
||||
nsRefPtr<TelephonyCallGroup> mGroup;
|
||||
|
||||
nsString mNumber;
|
||||
nsString mSecondNumber;
|
||||
@ -36,6 +37,8 @@ public:
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TelephonyCall,
|
||||
nsDOMEventTargetHelper)
|
||||
|
||||
friend class Telephony;
|
||||
|
||||
nsPIDOMWindow*
|
||||
GetParentObject() const
|
||||
{
|
||||
@ -74,6 +77,9 @@ public:
|
||||
already_AddRefed<DOMError>
|
||||
GetError() const;
|
||||
|
||||
already_AddRefed<TelephonyCallGroup>
|
||||
GetGroup() const;
|
||||
|
||||
void
|
||||
Answer(ErrorResult& aRv);
|
||||
|
||||
@ -97,11 +103,12 @@ public:
|
||||
IMPL_EVENT_HANDLER(held)
|
||||
IMPL_EVENT_HANDLER(resuming)
|
||||
IMPL_EVENT_HANDLER(error)
|
||||
IMPL_EVENT_HANDLER(groupchange)
|
||||
|
||||
static already_AddRefed<TelephonyCall>
|
||||
Create(Telephony* aTelephony, const nsAString& aNumber, uint16_t aCallState,
|
||||
uint32_t aCallIndex = kOutgoingPlaceholderCallIndex,
|
||||
bool aEmergency = false);
|
||||
bool aEmergency = false, bool aIsConference = false);
|
||||
|
||||
void
|
||||
ChangeState(uint16_t aCallState)
|
||||
@ -150,6 +157,9 @@ public:
|
||||
void
|
||||
NotifyError(const nsAString& aError);
|
||||
|
||||
void
|
||||
ChangeGroup(TelephonyCallGroup* aGroup);
|
||||
|
||||
private:
|
||||
TelephonyCall();
|
||||
|
||||
|
283
dom/telephony/TelephonyCallGroup.cpp
Normal file
283
dom/telephony/TelephonyCallGroup.cpp
Normal file
@ -0,0 +1,283 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "TelephonyCallGroup.h"
|
||||
#include "mozilla/dom/TelephonyCallGroupBinding.h"
|
||||
|
||||
#include "CallEvent.h"
|
||||
#include "CallsList.h"
|
||||
#include "Telephony.h"
|
||||
|
||||
USING_TELEPHONY_NAMESPACE
|
||||
using namespace mozilla::dom;
|
||||
|
||||
TelephonyCallGroup::TelephonyCallGroup()
|
||||
: mCallState(nsITelephonyProvider::CALL_STATE_UNKNOWN)
|
||||
{
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
|
||||
TelephonyCallGroup::~TelephonyCallGroup()
|
||||
{
|
||||
}
|
||||
|
||||
// static
|
||||
already_AddRefed<TelephonyCallGroup>
|
||||
TelephonyCallGroup::Create(Telephony* aTelephony)
|
||||
{
|
||||
NS_ASSERTION(aTelephony, "Null telephony!");
|
||||
|
||||
nsRefPtr<TelephonyCallGroup> group = new TelephonyCallGroup();
|
||||
|
||||
group->BindToOwner(aTelephony->GetOwner());
|
||||
|
||||
group->mTelephony = aTelephony;
|
||||
group->mCallsList = new CallsList(aTelephony, group);
|
||||
|
||||
return group.forget();
|
||||
}
|
||||
|
||||
JSObject*
|
||||
TelephonyCallGroup::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
|
||||
{
|
||||
return TelephonyCallGroupBinding::Wrap(aCx, aScope, this);
|
||||
}
|
||||
|
||||
void
|
||||
TelephonyCallGroup::AddCall(TelephonyCall* aCall)
|
||||
{
|
||||
NS_ASSERTION(!mCalls.Contains(aCall), "Already know about this one!");
|
||||
mCalls.AppendElement(aCall);
|
||||
aCall->ChangeGroup(this);
|
||||
NotifyCallsChanged(aCall);
|
||||
}
|
||||
|
||||
void
|
||||
TelephonyCallGroup::RemoveCall(TelephonyCall* aCall)
|
||||
{
|
||||
NS_ASSERTION(mCalls.Contains(aCall), "Didn't know about this one!");
|
||||
mCalls.RemoveElement(aCall);
|
||||
aCall->ChangeGroup(nullptr);
|
||||
NotifyCallsChanged(aCall);
|
||||
}
|
||||
|
||||
void
|
||||
TelephonyCallGroup::ChangeState(uint16_t aCallState)
|
||||
{
|
||||
if (mCallState == aCallState) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsString stateString;
|
||||
switch (aCallState) {
|
||||
case nsITelephonyProvider::CALL_STATE_UNKNOWN:
|
||||
break;
|
||||
case nsITelephonyProvider::CALL_STATE_CONNECTED:
|
||||
stateString.AssignLiteral("connected");
|
||||
break;
|
||||
case nsITelephonyProvider::CALL_STATE_HOLDING:
|
||||
stateString.AssignLiteral("holding");
|
||||
break;
|
||||
case nsITelephonyProvider::CALL_STATE_HELD:
|
||||
stateString.AssignLiteral("held");
|
||||
break;
|
||||
case nsITelephonyProvider::CALL_STATE_RESUMING:
|
||||
stateString.AssignLiteral("resuming");
|
||||
break;
|
||||
default:
|
||||
NS_NOTREACHED("Unknown state!");
|
||||
}
|
||||
|
||||
mState = stateString;
|
||||
mCallState = aCallState;
|
||||
|
||||
nsresult rv = DispatchCallEvent(NS_LITERAL_STRING("statechange"), nullptr);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to dispatch specific event!");
|
||||
}
|
||||
if (!stateString.IsEmpty()) {
|
||||
// This can change if the statechange handler called back here... Need to
|
||||
// figure out something smarter.
|
||||
if (mCallState == aCallState) {
|
||||
rv = DispatchCallEvent(stateString, nullptr);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to dispatch specific event!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t index = 0; index < mCalls.Length(); index++) {
|
||||
nsRefPtr<TelephonyCall> call = mCalls[index];
|
||||
call->ChangeState(aCallState);
|
||||
|
||||
MOZ_ASSERT(call->CallState() == aCallState);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
TelephonyCallGroup::NotifyCallsChanged(TelephonyCall* aCall)
|
||||
{
|
||||
return DispatchCallEvent(NS_LITERAL_STRING("callschanged"), aCall);
|
||||
}
|
||||
|
||||
nsresult
|
||||
TelephonyCallGroup::DispatchCallEvent(const nsAString& aType,
|
||||
TelephonyCall* aCall)
|
||||
{
|
||||
nsRefPtr<CallEvent> event = CallEvent::Create(this, aType, aCall, false, false);
|
||||
return DispatchTrustedEvent(event);
|
||||
}
|
||||
|
||||
bool
|
||||
TelephonyCallGroup::CanConference(const TelephonyCall& aCall,
|
||||
TelephonyCall* aSecondCall)
|
||||
{
|
||||
if (!aSecondCall) {
|
||||
MOZ_ASSERT(!mCalls.IsEmpty());
|
||||
|
||||
return (mCallState == nsITelephonyProvider::CALL_STATE_CONNECTED &&
|
||||
aCall.CallState() == nsITelephonyProvider::CALL_STATE_HELD) ||
|
||||
(mCallState == nsITelephonyProvider::CALL_STATE_HELD &&
|
||||
aCall.CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mCallState == nsITelephonyProvider::CALL_STATE_UNKNOWN);
|
||||
|
||||
return (aCall.CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED &&
|
||||
aSecondCall->CallState() == nsITelephonyProvider::CALL_STATE_HELD) ||
|
||||
(aCall.CallState() == nsITelephonyProvider::CALL_STATE_HELD &&
|
||||
aSecondCall->CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED);
|
||||
}
|
||||
|
||||
already_AddRefed<TelephonyCall>
|
||||
TelephonyCallGroup::GetCall(uint32_t aCallIndex)
|
||||
{
|
||||
nsRefPtr<TelephonyCall> call;
|
||||
|
||||
for (uint32_t index = 0; index < mCalls.Length(); index++) {
|
||||
nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
|
||||
if (tempCall->CallIndex() == aCallIndex) {
|
||||
call = tempCall;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return call.forget();
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(TelephonyCallGroup)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TelephonyCallGroup,
|
||||
nsDOMEventTargetHelper)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCalls)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallsList)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTelephony)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TelephonyCallGroup,
|
||||
nsDOMEventTargetHelper)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCalls)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallsList)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTelephony)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TelephonyCallGroup)
|
||||
NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(TelephonyCallGroup, nsDOMEventTargetHelper)
|
||||
NS_IMPL_RELEASE_INHERITED(TelephonyCallGroup, nsDOMEventTargetHelper)
|
||||
|
||||
// WebIDL
|
||||
already_AddRefed<CallsList>
|
||||
TelephonyCallGroup::Calls() const
|
||||
{
|
||||
nsRefPtr<CallsList> list = mCallsList;
|
||||
return list.forget();
|
||||
}
|
||||
|
||||
void
|
||||
TelephonyCallGroup::Add(TelephonyCall& aCall,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
if (!CanConference(aCall, nullptr)) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
aRv = mTelephony->Provider()->ConferenceCall();
|
||||
}
|
||||
|
||||
void
|
||||
TelephonyCallGroup::Add(TelephonyCall& aCall,
|
||||
TelephonyCall& aSecondCall,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
if (!CanConference(aCall, &aSecondCall)) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
aRv = mTelephony->Provider()->ConferenceCall();
|
||||
}
|
||||
|
||||
void
|
||||
TelephonyCallGroup::Remove(TelephonyCall& aCall, ErrorResult& aRv)
|
||||
{
|
||||
if (mCallState != nsITelephonyProvider::CALL_STATE_CONNECTED) {
|
||||
NS_WARNING("Remove call from a non-connected call group. Ignore!");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t callIndex = aCall.CallIndex();
|
||||
bool hasCallToRemove = false;
|
||||
for (uint32_t index = 0; index < mCalls.Length(); index++) {
|
||||
nsRefPtr<TelephonyCall>& call = mCalls[index];
|
||||
if (call->CallIndex() == callIndex) {
|
||||
hasCallToRemove = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCallToRemove) {
|
||||
aRv = mTelephony->Provider()->SeparateCall(callIndex);
|
||||
} else {
|
||||
NS_WARNING("Didn't have this call. Ignore!");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TelephonyCallGroup::Hold(ErrorResult& aRv)
|
||||
{
|
||||
if (mCallState != nsITelephonyProvider::CALL_STATE_CONNECTED) {
|
||||
NS_WARNING("Hold non-connected call ignored!");
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = mTelephony->Provider()->HoldConference();
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
|
||||
ChangeState(nsITelephonyProvider::CALL_STATE_HOLDING);
|
||||
}
|
||||
|
||||
void
|
||||
TelephonyCallGroup::Resume(ErrorResult& aRv)
|
||||
{
|
||||
if (mCallState != nsITelephonyProvider::CALL_STATE_HELD) {
|
||||
NS_WARNING("Resume non-held call ignored!");
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = mTelephony->Provider()->ResumeConference();
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
|
||||
ChangeState(nsITelephonyProvider::CALL_STATE_RESUMING);
|
||||
}
|
116
dom/telephony/TelephonyCallGroup.h
Normal file
116
dom/telephony/TelephonyCallGroup.h
Normal file
@ -0,0 +1,116 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_telephony_telephonycallgroup_h__
|
||||
#define mozilla_dom_telephony_telephonycallgroup_h__
|
||||
|
||||
#include "TelephonyCommon.h"
|
||||
|
||||
BEGIN_TELEPHONY_NAMESPACE
|
||||
|
||||
class TelephonyCallGroup MOZ_FINAL : public nsDOMEventTargetHelper
|
||||
{
|
||||
nsRefPtr<Telephony> mTelephony;
|
||||
|
||||
nsTArray<nsRefPtr<TelephonyCall> > mCalls;
|
||||
|
||||
nsRefPtr<CallsList> mCallsList;
|
||||
|
||||
nsString mState;
|
||||
|
||||
uint16_t mCallState;
|
||||
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TelephonyCallGroup,
|
||||
nsDOMEventTargetHelper)
|
||||
|
||||
nsPIDOMWindow*
|
||||
GetParentObject() const
|
||||
{
|
||||
return GetOwner();
|
||||
}
|
||||
|
||||
// WrapperCache
|
||||
virtual JSObject*
|
||||
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
|
||||
|
||||
// WebIDL interface
|
||||
already_AddRefed<CallsList>
|
||||
Calls() const;
|
||||
|
||||
void
|
||||
Add(TelephonyCall& aCall, ErrorResult& aRv);
|
||||
|
||||
void
|
||||
Add(TelephonyCall& aCall, TelephonyCall& aSecondCall, ErrorResult& aRv);
|
||||
|
||||
void
|
||||
Remove(TelephonyCall& aCall, ErrorResult& aRv);
|
||||
|
||||
void
|
||||
Hold(ErrorResult& aRv);
|
||||
|
||||
void
|
||||
Resume(ErrorResult& aRv);
|
||||
|
||||
void
|
||||
GetState(nsString& aState) const
|
||||
{
|
||||
aState = mState;
|
||||
}
|
||||
|
||||
IMPL_EVENT_HANDLER(statechange)
|
||||
IMPL_EVENT_HANDLER(connected)
|
||||
IMPL_EVENT_HANDLER(holding)
|
||||
IMPL_EVENT_HANDLER(held)
|
||||
IMPL_EVENT_HANDLER(resuming)
|
||||
IMPL_EVENT_HANDLER(callschanged)
|
||||
|
||||
static already_AddRefed<TelephonyCallGroup>
|
||||
Create(Telephony* aTelephony);
|
||||
|
||||
void
|
||||
AddCall(TelephonyCall* aCall);
|
||||
|
||||
void
|
||||
RemoveCall(TelephonyCall* aCall);
|
||||
|
||||
already_AddRefed<TelephonyCall>
|
||||
GetCall(uint32_t aCallIndex);
|
||||
|
||||
const nsTArray<nsRefPtr<TelephonyCall> >&
|
||||
CallsArray() const
|
||||
{
|
||||
return mCalls;
|
||||
}
|
||||
|
||||
void
|
||||
ChangeState(uint16_t aCallState);
|
||||
|
||||
uint16_t
|
||||
CallState() const
|
||||
{
|
||||
return mCallState;
|
||||
}
|
||||
|
||||
private:
|
||||
TelephonyCallGroup();
|
||||
~TelephonyCallGroup();
|
||||
|
||||
nsresult
|
||||
NotifyCallsChanged(TelephonyCall* aCall);
|
||||
|
||||
nsresult
|
||||
DispatchCallEvent(const nsAString& aType,
|
||||
TelephonyCall* aCall);
|
||||
|
||||
bool CanConference(const TelephonyCall& aCall, TelephonyCall* aSecondCall);
|
||||
};
|
||||
|
||||
END_TELEPHONY_NAMESPACE
|
||||
|
||||
#endif // mozilla_dom_telephony_telephonycallgroup_h__
|
@ -33,6 +33,7 @@ enum {
|
||||
class CallsList;
|
||||
class Telephony;
|
||||
class TelephonyCall;
|
||||
class TelephonyCallGroup;
|
||||
|
||||
END_TELEPHONY_NAMESPACE
|
||||
|
||||
|
@ -17,5 +17,6 @@ CPP_SOURCES += [
|
||||
'CallsList.cpp',
|
||||
'Telephony.cpp',
|
||||
'TelephonyCall.cpp',
|
||||
'TelephonyCallGroup.cpp',
|
||||
]
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(3fb573c3-6fc4-41d3-80c1-f0e60662691e)]
|
||||
[scriptable, uuid(a5818719-e1b6-4fdc-8551-006055fa9996)]
|
||||
interface nsITelephonyListener : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -22,13 +22,27 @@ interface nsITelephonyListener : nsISupports
|
||||
* Indicates whether this call is outgoing or incoming.
|
||||
* @param isEmergency
|
||||
* Indicates whether this call is an emergency call.
|
||||
* @param isConference
|
||||
* Indicates whether this call is a conference call.
|
||||
*/
|
||||
void callStateChanged(in unsigned long callIndex,
|
||||
in unsigned short callState,
|
||||
in AString number,
|
||||
in boolean isActive,
|
||||
in boolean isOutgoing,
|
||||
in boolean isEmergency);
|
||||
in boolean isEmergency,
|
||||
in boolean isConference);
|
||||
|
||||
/**
|
||||
* Called when participants of a conference call have been updated, and the
|
||||
* conference call state changes.
|
||||
*
|
||||
* @param callState
|
||||
* Possible values are: nsITelephonyProvider::CALL_STATE_UNKNOWN,
|
||||
* nsITelephonyProvider::CALL_STATE_HELD,
|
||||
* nsITelephonyProvider::CALL_STATE_CONNECTED.
|
||||
*/
|
||||
void conferenceCallStateChanged(in unsigned short callState);
|
||||
|
||||
/**
|
||||
* Called when enumeration asked by nsITelephonyProvider::enumerateCalls
|
||||
@ -51,7 +65,8 @@ interface nsITelephonyListener : nsISupports
|
||||
* Indicates whether this call is the active one.
|
||||
* @param isOutgoing
|
||||
* Indicates whether this call is outgoing or incoming.
|
||||
*
|
||||
* @param isConference
|
||||
* Indicates whether this call is a conference call.
|
||||
* @return true to continue enumeration or false to cancel.
|
||||
*/
|
||||
boolean enumerateCallState(in unsigned long callIndex,
|
||||
@ -59,7 +74,8 @@ interface nsITelephonyListener : nsISupports
|
||||
in AString number,
|
||||
in boolean isActive,
|
||||
in boolean isOutgoing,
|
||||
in boolean isEmergency);
|
||||
in boolean isEmergency,
|
||||
in boolean isConference);
|
||||
|
||||
/**
|
||||
* Notify when RIL receives supplementary service notification.
|
||||
@ -96,7 +112,7 @@ interface nsITelephonyListener : nsISupports
|
||||
* XPCOM component (in the content process) that provides the telephony
|
||||
* information.
|
||||
*/
|
||||
[scriptable, uuid(eddb7ff6-29af-4973-83de-1159365c7d7d)]
|
||||
[scriptable, uuid(45a2f856-4e07-499a-94c6-624f90c3345b)]
|
||||
interface nsITelephonyProvider : nsISupports
|
||||
{
|
||||
const unsigned short CALL_STATE_UNKNOWN = 0;
|
||||
@ -143,6 +159,11 @@ interface nsITelephonyProvider : nsISupports
|
||||
void holdCall(in unsigned long callIndex);
|
||||
void resumeCall(in unsigned long callIndex);
|
||||
|
||||
void conferenceCall();
|
||||
void separateCall(in unsigned long callIndex);
|
||||
void holdConference();
|
||||
void resumeConference();
|
||||
|
||||
attribute bool microphoneMuted;
|
||||
attribute bool speakerEnabled;
|
||||
};
|
||||
|
@ -15,9 +15,11 @@ interface Telephony : EventTarget {
|
||||
[Throws]
|
||||
attribute boolean speakerEnabled;
|
||||
|
||||
readonly attribute TelephonyCall? active;
|
||||
readonly attribute (TelephonyCall or TelephonyCallGroup)? active;
|
||||
|
||||
// A call is contained either in Telephony or in TelephonyCallGroup.
|
||||
readonly attribute CallsList calls;
|
||||
readonly attribute TelephonyCallGroup conferenceGroup;
|
||||
|
||||
[Throws]
|
||||
void startTone(DOMString tone);
|
||||
|
@ -20,6 +20,8 @@ interface TelephonyCall : EventTarget {
|
||||
|
||||
readonly attribute DOMError? error;
|
||||
|
||||
readonly attribute TelephonyCallGroup? group;
|
||||
|
||||
[Throws]
|
||||
void answer();
|
||||
[Throws]
|
||||
@ -51,4 +53,8 @@ interface TelephonyCall : EventTarget {
|
||||
attribute EventHandler onresuming;
|
||||
[SetterThrows]
|
||||
attribute EventHandler onerror;
|
||||
|
||||
// Fired whenever the group attribute changes.
|
||||
[SetterThrows]
|
||||
attribute EventHandler ongroupchange;
|
||||
};
|
||||
|
39
dom/webidl/TelephonyCallGroup.webidl
Normal file
39
dom/webidl/TelephonyCallGroup.webidl
Normal file
@ -0,0 +1,39 @@
|
||||
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=40: */
|
||||
/* 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/. */
|
||||
|
||||
interface TelephonyCallGroup : EventTarget {
|
||||
readonly attribute CallsList calls;
|
||||
|
||||
[Throws]
|
||||
void add(TelephonyCall call);
|
||||
|
||||
[Throws]
|
||||
void add(TelephonyCall call, TelephonyCall secondCall);
|
||||
|
||||
[Throws]
|
||||
void remove(TelephonyCall call);
|
||||
|
||||
[Throws]
|
||||
void hold();
|
||||
|
||||
[Throws]
|
||||
void resume();
|
||||
|
||||
readonly attribute DOMString state;
|
||||
|
||||
[SetterThrows]
|
||||
attribute EventHandler onstatechange;
|
||||
[SetterThrows]
|
||||
attribute EventHandler onconnected;
|
||||
[SetterThrows]
|
||||
attribute EventHandler onholding;
|
||||
[SetterThrows]
|
||||
attribute EventHandler onheld;
|
||||
[SetterThrows]
|
||||
attribute EventHandler onresuming;
|
||||
[SetterThrows]
|
||||
attribute EventHandler oncallschanged;
|
||||
};
|
@ -444,6 +444,7 @@ webidl_files += \
|
||||
MozStkCommandEvent.webidl \
|
||||
Telephony.webidl \
|
||||
TelephonyCall.webidl \
|
||||
TelephonyCallGroup.webidl \
|
||||
$(NULL)
|
||||
endif
|
||||
|
||||
|
@ -206,9 +206,9 @@ public:
|
||||
ViewID mScrollId;
|
||||
|
||||
// The scrollable bounds of a frame. This is determined by reflow.
|
||||
// For the top-level |window|,
|
||||
// { x = window.scrollX, y = window.scrollY, // could be 0, 0
|
||||
// width = window.innerWidth, height = window.innerHeight }
|
||||
// Ordinarily the x and y will be 0 and the width and height will be the
|
||||
// size of the element being scrolled. However for RTL pages or elements
|
||||
// the x value may be negative.
|
||||
//
|
||||
// This is relative to the document. It is in the same coordinate space as
|
||||
// |mScrollOffset|, but a different coordinate space than |mViewport| and
|
||||
|
@ -87,7 +87,7 @@ APZCTreeManager::UpdatePanZoomControllerTree(CompositorParent* aCompositor, Laye
|
||||
&apzcsToDestroy);
|
||||
}
|
||||
|
||||
for (int i = apzcsToDestroy.Length() - 1; i >= 0; i--) {
|
||||
for (size_t i = 0; i < apzcsToDestroy.Length(); i++) {
|
||||
APZC_LOG("Destroying APZC at %p\n", apzcsToDestroy[i].get());
|
||||
apzcsToDestroy[i]->Destroy();
|
||||
}
|
||||
@ -211,26 +211,41 @@ ApplyTransform(nsIntPoint* aPoint, const gfx3DMatrix& aMatrix)
|
||||
nsEventStatus
|
||||
APZCTreeManager::ReceiveInputEvent(const InputData& aEvent)
|
||||
{
|
||||
nsRefPtr<AsyncPanZoomController> apzc;
|
||||
gfx3DMatrix transformToApzc;
|
||||
gfx3DMatrix transformToScreen;
|
||||
switch (aEvent.mInputType) {
|
||||
case MULTITOUCH_INPUT: {
|
||||
const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
|
||||
apzc = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[0].mScreenPoint),
|
||||
transformToApzc, transformToScreen);
|
||||
if (apzc) {
|
||||
if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
|
||||
mApzcForInputBlock = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[0].mScreenPoint));
|
||||
for (size_t i = 1; i < multiTouchInput.mTouches.Length(); i++) {
|
||||
nsRefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[i].mScreenPoint));
|
||||
mApzcForInputBlock = CommonAncestor(mApzcForInputBlock.get(), apzc2.get());
|
||||
APZC_LOG("Using APZC %p as the common ancestor\n", mApzcForInputBlock.get());
|
||||
}
|
||||
} else if (mApzcForInputBlock) {
|
||||
APZC_LOG("Re-using APZC %p as continuation of event block\n", mApzcForInputBlock.get());
|
||||
// If we have an mApzcForInputBlock and it's the end of the touch sequence
|
||||
// then null it out so we don't keep a dangling reference and leak things.
|
||||
if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL ||
|
||||
(multiTouchInput.mType == MultiTouchInput::MULTITOUCH_END && multiTouchInput.mTouches.Length() == 1)) {
|
||||
mApzcForInputBlock = nullptr;
|
||||
}
|
||||
}
|
||||
if (mApzcForInputBlock) {
|
||||
GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToScreen);
|
||||
MultiTouchInput inputForApzc(multiTouchInput);
|
||||
for (int i = inputForApzc.mTouches.Length() - 1; i >= 0; i--) {
|
||||
for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) {
|
||||
ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc);
|
||||
}
|
||||
apzc->ReceiveInputEvent(inputForApzc);
|
||||
mApzcForInputBlock->ReceiveInputEvent(inputForApzc);
|
||||
}
|
||||
break;
|
||||
} case PINCHGESTURE_INPUT: {
|
||||
const PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
|
||||
apzc = GetTargetAPZC(pinchInput.mFocusPoint, transformToApzc, transformToScreen);
|
||||
nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(pinchInput.mFocusPoint);
|
||||
if (apzc) {
|
||||
GetInputTransforms(apzc, transformToApzc, transformToScreen);
|
||||
PinchGestureInput inputForApzc(pinchInput);
|
||||
ApplyTransform(&(inputForApzc.mFocusPoint), transformToApzc);
|
||||
apzc->ReceiveInputEvent(inputForApzc);
|
||||
@ -238,8 +253,9 @@ APZCTreeManager::ReceiveInputEvent(const InputData& aEvent)
|
||||
break;
|
||||
} case TAPGESTURE_INPUT: {
|
||||
const TapGestureInput& tapInput = aEvent.AsTapGestureInput();
|
||||
apzc = GetTargetAPZC(ScreenPoint(tapInput.mPoint), transformToApzc, transformToScreen);
|
||||
nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(ScreenPoint(tapInput.mPoint));
|
||||
if (apzc) {
|
||||
GetInputTransforms(apzc, transformToApzc, transformToScreen);
|
||||
TapGestureInput inputForApzc(tapInput);
|
||||
ApplyTransform(&(inputForApzc.mPoint), transformToApzc);
|
||||
apzc->ReceiveInputEvent(inputForApzc);
|
||||
@ -256,38 +272,54 @@ APZCTreeManager::ReceiveInputEvent(const nsInputEvent& aEvent,
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsRefPtr<AsyncPanZoomController> apzc;
|
||||
gfx3DMatrix transformToApzc;
|
||||
gfx3DMatrix transformToScreen;
|
||||
switch (aEvent.eventStructType) {
|
||||
case NS_TOUCH_EVENT: {
|
||||
const nsTouchEvent& touchEvent = static_cast<const nsTouchEvent&>(aEvent);
|
||||
if (touchEvent.touches.Length() > 0) {
|
||||
if (touchEvent.touches.Length() == 0) {
|
||||
break;
|
||||
}
|
||||
if (touchEvent.message == NS_TOUCH_START) {
|
||||
nsIntPoint point = touchEvent.touches[0]->mRefPoint;
|
||||
apzc = GetTargetAPZC(ScreenPoint::FromUnknownPoint(gfx::Point(point.x, point.y)),
|
||||
transformToApzc, transformToScreen);
|
||||
if (apzc) {
|
||||
MultiTouchInput inputForApzc(touchEvent);
|
||||
for (int i = inputForApzc.mTouches.Length() - 1; i >= 0; i--) {
|
||||
ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc);
|
||||
}
|
||||
|
||||
gfx3DMatrix outTransform = transformToApzc * transformToScreen;
|
||||
nsTouchEvent* outEvent = static_cast<nsTouchEvent*>(aOutEvent);
|
||||
for (int i = outEvent->touches.Length() - 1; i >= 0; i--) {
|
||||
ApplyTransform(&(outEvent->touches[i]->mRefPoint), outTransform);
|
||||
}
|
||||
|
||||
return apzc->ReceiveInputEvent(inputForApzc);
|
||||
mApzcForInputBlock = GetTargetAPZC(ScreenPoint(point.x, point.y));
|
||||
for (size_t i = 1; i < touchEvent.touches.Length(); i++) {
|
||||
point = touchEvent.touches[i]->mRefPoint;
|
||||
nsRefPtr<AsyncPanZoomController> apzc2 =
|
||||
GetTargetAPZC(ScreenPoint(point.x, point.y));
|
||||
mApzcForInputBlock = CommonAncestor(mApzcForInputBlock.get(), apzc2.get());
|
||||
APZC_LOG("Using APZC %p as the common ancestor\n", mApzcForInputBlock.get());
|
||||
}
|
||||
} else if (mApzcForInputBlock) {
|
||||
APZC_LOG("Re-using APZC %p as continuation of event block\n", mApzcForInputBlock.get());
|
||||
// If we have an mApzcForInputBlock and it's the end of the touch sequence
|
||||
// then null it out so we don't keep a dangling reference and leak things.
|
||||
if (touchEvent.message == NS_TOUCH_CANCEL ||
|
||||
(touchEvent.message == NS_TOUCH_END && touchEvent.touches.Length() == 1)) {
|
||||
mApzcForInputBlock = nullptr;
|
||||
}
|
||||
}
|
||||
if (mApzcForInputBlock) {
|
||||
GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToScreen);
|
||||
MultiTouchInput inputForApzc(touchEvent);
|
||||
for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) {
|
||||
ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc);
|
||||
}
|
||||
|
||||
gfx3DMatrix outTransform = transformToApzc * transformToScreen;
|
||||
nsTouchEvent* outEvent = static_cast<nsTouchEvent*>(aOutEvent);
|
||||
for (size_t i = 0; i < outEvent->touches.Length(); i++) {
|
||||
ApplyTransform(&(outEvent->touches[i]->mRefPoint), outTransform);
|
||||
}
|
||||
|
||||
return mApzcForInputBlock->ReceiveInputEvent(inputForApzc);
|
||||
}
|
||||
break;
|
||||
} case NS_MOUSE_EVENT: {
|
||||
const nsMouseEvent& mouseEvent = static_cast<const nsMouseEvent&>(aEvent);
|
||||
apzc = GetTargetAPZC(ScreenPoint::FromUnknownPoint(gfx::Point(mouseEvent.refPoint.x,
|
||||
mouseEvent.refPoint.y)),
|
||||
transformToApzc, transformToScreen);
|
||||
nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(ScreenPoint(mouseEvent.refPoint.x, mouseEvent.refPoint.y));
|
||||
if (apzc) {
|
||||
GetInputTransforms(apzc, transformToApzc, transformToScreen);
|
||||
MultiTouchInput inputForApzc(mouseEvent);
|
||||
ApplyTransform(&(inputForApzc.mTouches[0].mScreenPoint), transformToApzc);
|
||||
|
||||
@ -394,7 +426,7 @@ APZCTreeManager::ClearTree()
|
||||
// If this is too slow feel free to change it to a recursive walk.
|
||||
nsTArray< nsRefPtr<AsyncPanZoomController> > apzcsToDestroy;
|
||||
Collect(mRootApzc, &apzcsToDestroy);
|
||||
for (int i = apzcsToDestroy.Length() - 1; i >= 0; i--) {
|
||||
for (size_t i = 0; i < apzcsToDestroy.Length(); i++) {
|
||||
apzcsToDestroy[i]->Destroy();
|
||||
}
|
||||
mRootApzc = nullptr;
|
||||
@ -415,11 +447,78 @@ APZCTreeManager::GetTargetAPZC(const ScrollableLayerGuid& aGuid)
|
||||
return target.forget();
|
||||
}
|
||||
|
||||
/* This function returns the AsyncPanZoomController instance that hit testing determines
|
||||
is under the given ScreenPoint.
|
||||
already_AddRefed<AsyncPanZoomController>
|
||||
APZCTreeManager::GetTargetAPZC(const ScreenPoint& aPoint)
|
||||
{
|
||||
MonitorAutoLock lock(mTreeLock);
|
||||
nsRefPtr<AsyncPanZoomController> target;
|
||||
// The root may have siblings, so check those too
|
||||
gfxPoint point(aPoint.x, aPoint.y);
|
||||
for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) {
|
||||
target = GetAPZCAtPoint(apzc, point);
|
||||
if (target) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return target.forget();
|
||||
}
|
||||
|
||||
In addition, the aTransformToApzcOut and aTransformToScreenOut out-parameters are filled
|
||||
with some useful transformations that input events may need applied. This is best
|
||||
AsyncPanZoomController*
|
||||
APZCTreeManager::FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid) {
|
||||
// This walks the tree in depth-first, reverse order, so that it encounters
|
||||
// APZCs front-to-back on the screen.
|
||||
for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
|
||||
AsyncPanZoomController* match = FindTargetAPZC(child, aGuid);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
if (aApzc->Matches(aGuid)) {
|
||||
return aApzc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AsyncPanZoomController*
|
||||
APZCTreeManager::GetAPZCAtPoint(AsyncPanZoomController* aApzc, const gfxPoint& aHitTestPoint)
|
||||
{
|
||||
// The comments below assume there is a chain of layers L..R with L and P having APZC instances as
|
||||
// explained in the comment on GetInputTransforms. This function will recurse with aApzc at L and P, and the
|
||||
// comments explain what values are stored in the variables at these two levels. All the comments
|
||||
// use standard matrix notation where the leftmost matrix in a multiplication is applied first.
|
||||
|
||||
// ancestorUntransform is OC.Inverse() * NC.Inverse() * MC.Inverse() at recursion level for L,
|
||||
// and RC.Inverse() * QC.Inverse() at recursion level for P.
|
||||
gfx3DMatrix ancestorUntransform = aApzc->GetAncestorTransform().Inverse();
|
||||
// asyncUntransform is LA.Inverse() at recursion level for L,
|
||||
// and PA.Inverse() at recursion level for P.
|
||||
gfx3DMatrix asyncUntransform = gfx3DMatrix(aApzc->GetCurrentAsyncTransform()).Inverse();
|
||||
// untransformSinceLastApzc is OC.Inverse() * NC.Inverse() * MC.Inverse() * LA.Inverse() * LC.Inverse() at L,
|
||||
// and RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse() at P.
|
||||
gfx3DMatrix untransformSinceLastApzc = ancestorUntransform * asyncUntransform * aApzc->GetCSSTransform().Inverse();
|
||||
// untransformed is the user input in L's layer space at L,
|
||||
// and in P's layer space at P.
|
||||
gfxPoint untransformed = untransformSinceLastApzc.ProjectPoint(aHitTestPoint);
|
||||
APZC_LOG("Untransformed %f %f to %f %f for APZC %p\n", aHitTestPoint.x, aHitTestPoint.y, untransformed.x, untransformed.y, aApzc);
|
||||
|
||||
// This walks the tree in depth-first, reverse order, so that it encounters
|
||||
// APZCs front-to-back on the screen.
|
||||
for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
|
||||
AsyncPanZoomController* match = GetAPZCAtPoint(child, untransformed);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
if (aApzc->VisibleRegionContains(LayerPoint(untransformed.x, untransformed.y))) {
|
||||
APZC_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p\n", untransformed.x, untransformed.y, aApzc);
|
||||
return aApzc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* This function sets the aTransformToApzcOut and aTransformToScreenOut out-parameters
|
||||
to some useful transformations that input events may need applied. This is best
|
||||
illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L
|
||||
is the layer that corresponds to the returned APZC instance, and layer R is the root
|
||||
of the layer tree. Layer M is the parent of L, N is the parent of M, and so on.
|
||||
@ -478,93 +577,84 @@ APZCTreeManager::GetTargetAPZC(const ScrollableLayerGuid& aGuid)
|
||||
layer L would store LC and (MC * NC * OC), and the layer P would store PC and (QC * RC).
|
||||
The APZCs also obviously have LA and PA, so all of the above transformation combinations
|
||||
required can be generated.
|
||||
|
||||
Note that this function may return null, in which case the matrix out-parameters are
|
||||
left unmodified.
|
||||
*/
|
||||
already_AddRefed<AsyncPanZoomController>
|
||||
APZCTreeManager::GetTargetAPZC(const ScreenPoint& aPoint,
|
||||
gfx3DMatrix& aTransformToApzcOut,
|
||||
gfx3DMatrix& aTransformToScreenOut)
|
||||
{
|
||||
MonitorAutoLock lock(mTreeLock);
|
||||
nsRefPtr<AsyncPanZoomController> target;
|
||||
// The root may have siblings, so check those too
|
||||
gfxPoint point(aPoint.x, aPoint.y);
|
||||
for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) {
|
||||
target = GetAPZCAtPoint(apzc, point, aTransformToApzcOut, aTransformToScreenOut);
|
||||
if (target) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return target.forget();
|
||||
}
|
||||
|
||||
AsyncPanZoomController*
|
||||
APZCTreeManager::FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid) {
|
||||
// This walks the tree in depth-first, reverse order, so that it encounters
|
||||
// APZCs front-to-back on the screen.
|
||||
for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
|
||||
AsyncPanZoomController* match = FindTargetAPZC(child, aGuid);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
if (aApzc->Matches(aGuid)) {
|
||||
return aApzc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AsyncPanZoomController*
|
||||
APZCTreeManager::GetAPZCAtPoint(AsyncPanZoomController* aApzc, const gfxPoint& aHitTestPoint,
|
||||
gfx3DMatrix& aTransformToApzcOut, gfx3DMatrix& aTransformToScreenOut)
|
||||
void
|
||||
APZCTreeManager::GetInputTransforms(AsyncPanZoomController *aApzc, gfx3DMatrix& aTransformToApzcOut,
|
||||
gfx3DMatrix& aTransformToScreenOut)
|
||||
{
|
||||
// The comments below assume there is a chain of layers L..R with L and P having APZC instances as
|
||||
// explained in the comment on GetTargetAPZC. This function will recurse with aApzc at L and P, and the
|
||||
// comments explain what values are stored in the variables at these two levels. All the comments
|
||||
// use standard matrix notation where the leftmost matrix in a multiplication is applied first.
|
||||
// explained in the comment above. This function is called with aApzc at L, and the loop
|
||||
// below performs one iteration, where parent is at P. The comments explain what values are stored
|
||||
// in the variables at these two levels. All the comments use standard matrix notation where the
|
||||
// leftmost matrix in a multiplication is applied first.
|
||||
|
||||
// ancestorUntransform is OC.Inverse() * NC.Inverse() * MC.Inverse() at recursion level for L,
|
||||
// and RC.Inverse() * QC.Inverse() at recursion level for P.
|
||||
// ancestorUntransform is OC.Inverse() * NC.Inverse() * MC.Inverse()
|
||||
gfx3DMatrix ancestorUntransform = aApzc->GetAncestorTransform().Inverse();
|
||||
// asyncUntransform is LA.Inverse() at recursion level for L,
|
||||
// and PA.Inverse() at recursion level for P.
|
||||
// asyncUntransform is LA.Inverse()
|
||||
gfx3DMatrix asyncUntransform = gfx3DMatrix(aApzc->GetCurrentAsyncTransform()).Inverse();
|
||||
// untransformSinceLastApzc is OC.Inverse() * NC.Inverse() * MC.Inverse() * LA.Inverse() * LC.Inverse() at L,
|
||||
// and RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse() at P.
|
||||
gfx3DMatrix untransformSinceLastApzc = ancestorUntransform * asyncUntransform * aApzc->GetCSSTransform().Inverse();
|
||||
// untransformed is the user input in L's layer space at L,
|
||||
// and in P's layer space at P.
|
||||
gfxPoint untransformed = untransformSinceLastApzc.ProjectPoint(aHitTestPoint);
|
||||
APZC_LOG("Untransformed %f %f to %f %f for APZC %p\n", aHitTestPoint.x, aHitTestPoint.y, untransformed.x, untransformed.y, aApzc);
|
||||
|
||||
// This walks the tree in depth-first, reverse order, so that it encounters
|
||||
// APZCs front-to-back on the screen.
|
||||
for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
|
||||
AsyncPanZoomController* match = GetAPZCAtPoint(child, untransformed, aTransformToApzcOut, aTransformToScreenOut);
|
||||
if (match) {
|
||||
// This code is not run in the recursion at layer L.
|
||||
// aTransformToApzcOut is RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse()
|
||||
// at recursion level for P
|
||||
aTransformToApzcOut = untransformSinceLastApzc * aTransformToApzcOut;
|
||||
// aTransformToScreenOut is LA.Inverse() * MC * NC * OC * PC * QC * RC at recursion level for P
|
||||
aTransformToScreenOut = aTransformToScreenOut * aApzc->GetCSSTransform() * aApzc->GetAncestorTransform();
|
||||
// The above values for aTransformToApzcOut and aTransformToScreenOut at recursion level for P match
|
||||
// the required output as explained in the comment above GetTargetAPZC. Note that any missing terms
|
||||
// are async transforms that are guaranteed to be identity transforms.
|
||||
return match;
|
||||
}
|
||||
// aTransformToApzcOut is initialized to OC.Inverse() * NC.Inverse() * MC.Inverse()
|
||||
aTransformToApzcOut = ancestorUntransform;
|
||||
// aTransformToScreenOut is initialized to LA.Inverse() * MC * NC * OC
|
||||
aTransformToScreenOut = asyncUntransform * aApzc->GetAncestorTransform();
|
||||
|
||||
for (AsyncPanZoomController* parent = aApzc->GetParent(); parent; parent = parent->GetParent()) {
|
||||
// ancestorUntransform is updated to RC.Inverse() * QC.Inverse() when parent == P
|
||||
ancestorUntransform = parent->GetAncestorTransform().Inverse();
|
||||
// asyncUntransform is updated to PA.Inverse() when parent == P
|
||||
asyncUntransform = gfx3DMatrix(parent->GetCurrentAsyncTransform()).Inverse();
|
||||
// untransformSinceLastApzc is RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse()
|
||||
gfx3DMatrix untransformSinceLastApzc = ancestorUntransform * asyncUntransform * parent->GetCSSTransform().Inverse();
|
||||
|
||||
// aTransformToApzcOut is RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse()
|
||||
aTransformToApzcOut = untransformSinceLastApzc * aTransformToApzcOut;
|
||||
// aTransformToScreenOut is LA.Inverse() * MC * NC * OC * PC * QC * RC
|
||||
aTransformToScreenOut = aTransformToScreenOut * parent->GetCSSTransform() * parent->GetAncestorTransform();
|
||||
|
||||
// The above values for aTransformToApzcOut and aTransformToScreenOut when parent == P match
|
||||
// the required output as explained in the comment above GetTargetAPZC. Note that any missing terms
|
||||
// are async transforms that are guaranteed to be identity transforms.
|
||||
}
|
||||
if (aApzc->VisibleRegionContains(LayerPoint(untransformed.x, untransformed.y))) {
|
||||
APZC_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p\n", untransformed.x, untransformed.y, aApzc);
|
||||
// This code is not run in the recursion at layer P.
|
||||
// aTransformToApzcOut is OC.Inverse() * NC.Inverse() * MC.Inverse() at recursion level for L.
|
||||
aTransformToApzcOut = ancestorUntransform;
|
||||
// aTransformToScreenOut is LA.Inverse() * MC * NC * OC at recursion level for L.
|
||||
aTransformToScreenOut = asyncUntransform * aApzc->GetAncestorTransform();
|
||||
return aApzc;
|
||||
}
|
||||
|
||||
AsyncPanZoomController*
|
||||
APZCTreeManager::CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2)
|
||||
{
|
||||
// If either aApzc1 or aApzc2 is null, min(depth1, depth2) will be 0 and this function
|
||||
// will return null.
|
||||
|
||||
// Calculate depth of the APZCs in the tree
|
||||
int depth1 = 0, depth2 = 0;
|
||||
for (AsyncPanZoomController* parent = aApzc1; parent; parent = parent->GetParent()) {
|
||||
depth1++;
|
||||
}
|
||||
for (AsyncPanZoomController* parent = aApzc2; parent; parent = parent->GetParent()) {
|
||||
depth2++;
|
||||
}
|
||||
|
||||
// At most one of the following two loops will be executed; the deeper APZC pointer
|
||||
// will get walked up to the depth of the shallower one.
|
||||
int minDepth = depth1 < depth2 ? depth1 : depth2;
|
||||
while (depth1 > minDepth) {
|
||||
depth1--;
|
||||
aApzc1 = aApzc1->GetParent();
|
||||
}
|
||||
while (depth2 > minDepth) {
|
||||
depth2--;
|
||||
aApzc2 = aApzc2->GetParent();
|
||||
}
|
||||
|
||||
// Walk up the ancestor chains of both APZCs, always staying at the same depth for
|
||||
// either APZC, and return the the first common ancestor encountered.
|
||||
while (true) {
|
||||
if (aApzc1 == aApzc2) {
|
||||
return aApzc1;
|
||||
}
|
||||
if (depth1 <= 0) {
|
||||
break;
|
||||
}
|
||||
aApzc1 = aApzc1->GetParent();
|
||||
aApzc2 = aApzc2->GetParent();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -142,7 +142,6 @@ public:
|
||||
* General handler for incoming input events. Manipulates the frame metrics
|
||||
* based on what type of input it is. For example, a PinchGestureEvent will
|
||||
* cause scaling. This should only be called externally to this class.
|
||||
* HandleInputEvent() should be used internally.
|
||||
*/
|
||||
nsEventStatus ReceiveInputEvent(const InputData& aEvent);
|
||||
|
||||
@ -251,13 +250,14 @@ public:
|
||||
used by other production code.
|
||||
*/
|
||||
already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScrollableLayerGuid& aGuid);
|
||||
already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint, gfx3DMatrix& aTransformToApzcOut,
|
||||
gfx3DMatrix& aTransformToScreenOut);
|
||||
already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint);
|
||||
void GetInputTransforms(AsyncPanZoomController *aApzc, gfx3DMatrix& aTransformToApzcOut,
|
||||
gfx3DMatrix& aTransformToScreenOut);
|
||||
private:
|
||||
/* Recursive helpers */
|
||||
/* Helpers */
|
||||
AsyncPanZoomController* FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid);
|
||||
AsyncPanZoomController* GetAPZCAtPoint(AsyncPanZoomController* aApzc, const gfxPoint& aHitTestPoint,
|
||||
gfx3DMatrix& aTransformToApzcOut, gfx3DMatrix& aTransformToScreenOut);
|
||||
AsyncPanZoomController* GetAPZCAtPoint(AsyncPanZoomController* aApzc, const gfxPoint& aHitTestPoint);
|
||||
AsyncPanZoomController* CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2);
|
||||
|
||||
/**
|
||||
* Recursive helper function to build the APZC tree. The tree of APZC instances has
|
||||
@ -285,6 +285,12 @@ private:
|
||||
* is considered part of the APZC tree management state. */
|
||||
mozilla::Monitor mTreeLock;
|
||||
nsRefPtr<AsyncPanZoomController> mRootApzc;
|
||||
/* This tracks the APZC that should receive all inputs for the current input event block.
|
||||
* This allows touch points to move outside the thing they started on, but still have the
|
||||
* touch events delivered to the same initial APZC. This will only ever be touched on the
|
||||
* input delivery thread, and so does not require locking.
|
||||
*/
|
||||
nsRefPtr<AsyncPanZoomController> mApzcForInputBlock;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -255,6 +255,7 @@ AsyncPanZoomController::Destroy()
|
||||
}
|
||||
mPrevSibling = nullptr;
|
||||
mLastChild = nullptr;
|
||||
mParent = nullptr;
|
||||
}
|
||||
|
||||
/* static */float
|
||||
|
@ -616,13 +616,27 @@ private:
|
||||
* instance.
|
||||
*/
|
||||
public:
|
||||
void SetLastChild(AsyncPanZoomController* child) { mLastChild = child; }
|
||||
void SetPrevSibling(AsyncPanZoomController* sibling) { mPrevSibling = sibling; }
|
||||
void SetLastChild(AsyncPanZoomController* child) {
|
||||
mLastChild = child;
|
||||
if (child) {
|
||||
child->mParent = this;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPrevSibling(AsyncPanZoomController* sibling) {
|
||||
mPrevSibling = sibling;
|
||||
if (sibling) {
|
||||
sibling->mParent = mParent;
|
||||
}
|
||||
}
|
||||
|
||||
AsyncPanZoomController* GetLastChild() const { return mLastChild; }
|
||||
AsyncPanZoomController* GetPrevSibling() const { return mPrevSibling; }
|
||||
AsyncPanZoomController* GetParent() const { return mParent; }
|
||||
private:
|
||||
nsRefPtr<AsyncPanZoomController> mLastChild;
|
||||
nsRefPtr<AsyncPanZoomController> mPrevSibling;
|
||||
nsRefPtr<AsyncPanZoomController> mParent;
|
||||
|
||||
/* The functions and members in this section are used to maintain the
|
||||
* area that this APZC instance is responsible for. This is used when
|
||||
|
@ -381,6 +381,17 @@ NudgeToIntegers(const gfxPoint& aPoint)
|
||||
return gfxPoint(x, y);
|
||||
}
|
||||
|
||||
static already_AddRefed<AsyncPanZoomController>
|
||||
GetTargetAPZC(APZCTreeManager* manager, const ScreenPoint& aPoint,
|
||||
gfx3DMatrix& aTransformToApzcOut, gfx3DMatrix& aTransformToScreenOut)
|
||||
{
|
||||
nsRefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(aPoint);
|
||||
if (hit) {
|
||||
manager->GetInputTransforms(hit.get(), aTransformToApzcOut, aTransformToScreenOut);
|
||||
}
|
||||
return hit.forget();
|
||||
}
|
||||
|
||||
TEST(APZCTreeManager, GetAPZCAtPoint) {
|
||||
nsTArray<nsRefPtr<Layer> > layers;
|
||||
nsRefPtr<LayerManager> lm;
|
||||
@ -396,7 +407,7 @@ TEST(APZCTreeManager, GetAPZCAtPoint) {
|
||||
gfx3DMatrix transformToScreen;
|
||||
|
||||
// No APZC attached so hit testing will return no APZC at (20,20)
|
||||
nsRefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(20, 20), transformToApzc, transformToScreen);
|
||||
nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(manager, ScreenPoint(20, 20), transformToApzc, transformToScreen);
|
||||
AsyncPanZoomController* nullAPZC = nullptr;
|
||||
EXPECT_EQ(nullAPZC, hit.get());
|
||||
EXPECT_EQ(gfx3DMatrix(), transformToApzc);
|
||||
@ -405,7 +416,7 @@ TEST(APZCTreeManager, GetAPZCAtPoint) {
|
||||
// Now we have a root APZC that will match the page
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::ROOT_SCROLL_ID, mcc);
|
||||
manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
|
||||
hit = manager->GetTargetAPZC(ScreenPoint(15, 15), transformToApzc, transformToScreen);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
|
||||
EXPECT_EQ(root->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
|
||||
// expect hit point at LayerIntPoint(15, 15)
|
||||
EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15)));
|
||||
@ -415,36 +426,36 @@ TEST(APZCTreeManager, GetAPZCAtPoint) {
|
||||
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID, mcc);
|
||||
manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
|
||||
EXPECT_NE(root->AsContainerLayer()->GetAsyncPanZoomController(), layers[3]->AsContainerLayer()->GetAsyncPanZoomController());
|
||||
hit = manager->GetTargetAPZC(ScreenPoint(15, 15), transformToApzc, transformToScreen);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
|
||||
EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
|
||||
// expect hit point at LayerIntPoint(15, 15)
|
||||
EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15)));
|
||||
EXPECT_EQ(gfxPoint(15, 15), transformToScreen.Transform(gfxPoint(15, 15)));
|
||||
|
||||
// Now test hit testing when we have two scrollable layers
|
||||
hit = manager->GetTargetAPZC(ScreenPoint(15, 15), transformToApzc, transformToScreen);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
|
||||
EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
|
||||
SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1, mcc);
|
||||
manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
|
||||
hit = manager->GetTargetAPZC(ScreenPoint(15, 15), transformToApzc, transformToScreen);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
|
||||
EXPECT_EQ(layers[4]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
|
||||
// expect hit point at LayerIntPoint(15, 15)
|
||||
EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15)));
|
||||
EXPECT_EQ(gfxPoint(15, 15), transformToScreen.Transform(gfxPoint(15, 15)));
|
||||
|
||||
// Hit test ouside the reach of layer[3,4] but inside root
|
||||
hit = manager->GetTargetAPZC(ScreenPoint(90, 90), transformToApzc, transformToScreen);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(90, 90), transformToApzc, transformToScreen);
|
||||
EXPECT_EQ(root->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
|
||||
// expect hit point at LayerIntPoint(90, 90)
|
||||
EXPECT_EQ(gfxPoint(90, 90), transformToApzc.Transform(gfxPoint(90, 90)));
|
||||
EXPECT_EQ(gfxPoint(90, 90), transformToScreen.Transform(gfxPoint(90, 90)));
|
||||
|
||||
// Hit test ouside the reach of any layer
|
||||
hit = manager->GetTargetAPZC(ScreenPoint(1000, 10), transformToApzc, transformToScreen);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(1000, 10), transformToApzc, transformToScreen);
|
||||
EXPECT_EQ(nullAPZC, hit.get());
|
||||
EXPECT_EQ(gfx3DMatrix(), transformToApzc);
|
||||
EXPECT_EQ(gfx3DMatrix(), transformToScreen);
|
||||
hit = manager->GetTargetAPZC(ScreenPoint(-1000, 10), transformToApzc, transformToScreen);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(-1000, 10), transformToApzc, transformToScreen);
|
||||
EXPECT_EQ(nullAPZC, hit.get());
|
||||
EXPECT_EQ(gfx3DMatrix(), transformToApzc);
|
||||
EXPECT_EQ(gfx3DMatrix(), transformToScreen);
|
||||
@ -454,14 +465,14 @@ TEST(APZCTreeManager, GetAPZCAtPoint) {
|
||||
transform.ScalePost(0.1, 0.1, 1);
|
||||
root->SetBaseTransform(transform);
|
||||
manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
|
||||
hit = manager->GetTargetAPZC(ScreenPoint(50, 50), transformToApzc, transformToScreen); // This point is now outside the root layer
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(50, 50), transformToApzc, transformToScreen); // This point is now outside the root layer
|
||||
EXPECT_EQ(nullAPZC, hit.get());
|
||||
EXPECT_EQ(gfx3DMatrix(), transformToApzc);
|
||||
EXPECT_EQ(gfx3DMatrix(), transformToScreen);
|
||||
|
||||
// This hit test will hit both layers[3] and layers[4]; layers[4] is later in the tree so
|
||||
// it is a better match
|
||||
hit = manager->GetTargetAPZC(ScreenPoint(2, 2), transformToApzc, transformToScreen);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(2, 2), transformToApzc, transformToScreen);
|
||||
EXPECT_EQ(layers[4]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
|
||||
// expect hit point at LayerPoint(20, 20)
|
||||
EXPECT_EQ(gfxPoint(20, 20), NudgeToIntegers(transformToApzc.Transform(gfxPoint(2, 2))));
|
||||
@ -472,7 +483,7 @@ TEST(APZCTreeManager, GetAPZCAtPoint) {
|
||||
// layer 4 effective visible screenrect: (0.05, 0.05, 0.2, 0.2)
|
||||
// Does not contain (2, 2)
|
||||
manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
|
||||
hit = manager->GetTargetAPZC(ScreenPoint(2, 2), transformToApzc, transformToScreen);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(2, 2), transformToApzc, transformToScreen);
|
||||
EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
|
||||
// expect hit point at LayerPoint(20, 20)
|
||||
EXPECT_EQ(gfxPoint(20, 20), NudgeToIntegers(transformToApzc.Transform(gfxPoint(2, 2))));
|
||||
@ -495,7 +506,7 @@ TEST(APZCTreeManager, GetAPZCAtPoint) {
|
||||
|
||||
manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
|
||||
// layer 7 effective visible screenrect (0,16,4,60) but clipped by parent layers
|
||||
hit = manager->GetTargetAPZC(ScreenPoint(1, 45), transformToApzc, transformToScreen);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(1, 45), transformToApzc, transformToScreen);
|
||||
EXPECT_EQ(layers[7]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
|
||||
// expect hit point at LayerPoint(20, 440), which is CSSPoint(20, 29)
|
||||
EXPECT_EQ(gfxPoint(20, 440), NudgeToIntegers(transformToApzc.Transform(gfxPoint(1, 45))));
|
||||
|
@ -38,9 +38,9 @@ public class BrowserDB {
|
||||
|
||||
public Cursor filter(ContentResolver cr, CharSequence constraint, int limit);
|
||||
|
||||
// This should onlyl return frecent sites, BrowserDB.getTopSites will do the
|
||||
// This should only return frecent bookmarks, BrowserDB.getTopBookmarks will do the
|
||||
// work to combine that list with the pinned sites list
|
||||
public Cursor getTopSites(ContentResolver cr, int limit);
|
||||
public Cursor getTopBookmarks(ContentResolver cr, int limit);
|
||||
|
||||
public void updateVisitedHistory(ContentResolver cr, String uri);
|
||||
|
||||
@ -137,12 +137,12 @@ public class BrowserDB {
|
||||
return sDb.filter(cr, constraint, limit);
|
||||
}
|
||||
|
||||
public static Cursor getTopSites(ContentResolver cr, int limit) {
|
||||
// Note this is not a single query anymore, but actually returns a mixture of two queries, one for topSites
|
||||
// and one for pinned sites
|
||||
Cursor topSites = sDb.getTopSites(cr, limit);
|
||||
public static Cursor getTopBookmarks(ContentResolver cr, int limit) {
|
||||
// Note this is not a single query anymore, but actually returns a mixture of two queries,
|
||||
// one for top bookmarks, and one for pinned sites (which are actually bookmarks as well).
|
||||
Cursor topBookmarks = sDb.getTopBookmarks(cr, limit);
|
||||
Cursor pinnedSites = sDb.getPinnedSites(cr, limit);
|
||||
return new TopSitesCursorWrapper(pinnedSites, topSites, limit);
|
||||
return new TopSitesCursorWrapper(pinnedSites, topBookmarks, limit);
|
||||
}
|
||||
|
||||
public static void updateVisitedHistory(ContentResolver cr, String uri) {
|
||||
|
@ -231,9 +231,13 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getTopSites(ContentResolver cr, int limit) {
|
||||
// Filter out sites that are pinned
|
||||
String selection = DBUtils.concatenateWhere("", Combined.URL + " NOT IN (SELECT " +
|
||||
public Cursor getTopBookmarks(ContentResolver cr, int limit) {
|
||||
// Only select bookmarks. Unfortunately, we need to query the combined view,
|
||||
// instead of just the bookmarks table, in order to do the frecency calculation.
|
||||
String selection = Combined.BOOKMARK_ID + " IS NOT NULL";
|
||||
|
||||
// Filter out sites that are pinned.
|
||||
selection = DBUtils.concatenateWhere(selection, Combined.URL + " NOT IN (SELECT " +
|
||||
Bookmarks.URL + " FROM bookmarks WHERE " +
|
||||
DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " == ? AND " +
|
||||
DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)");
|
||||
|
@ -60,13 +60,18 @@ class BookmarksListAdapter extends MultiTypeCursorAdapter {
|
||||
|
||||
/**
|
||||
* Moves to parent folder, if one exists.
|
||||
*
|
||||
* @return Whether the adapter successfully moved to a parent folder.
|
||||
*/
|
||||
public void moveToParentFolder() {
|
||||
public boolean moveToParentFolder() {
|
||||
// If we're already at the root, we can't move to a parent folder
|
||||
if (mParentStack.size() != 1) {
|
||||
mParentStack.removeFirst();
|
||||
refreshCurrentFolder();
|
||||
if (mParentStack.size() == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mParentStack.removeFirst();
|
||||
refreshCurrentFolder();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,6 +13,7 @@ import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
@ -56,6 +57,17 @@ public class BookmarksListView extends HomeListView
|
||||
super.onAttachedToWindow();
|
||||
|
||||
setOnItemClickListener(this);
|
||||
|
||||
setOnKeyListener(new View.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
// If the user hit the BACK key, try to move to the parent folder.
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
return getBookmarksListAdapter().moveToParentFolder();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -101,14 +113,7 @@ public class BookmarksListView extends HomeListView
|
||||
// Absolute position for the adapter.
|
||||
position -= headerCount;
|
||||
|
||||
BookmarksListAdapter adapter;
|
||||
ListAdapter listAdapter = getAdapter();
|
||||
if (listAdapter instanceof HeaderViewListAdapter) {
|
||||
adapter = (BookmarksListAdapter) ((HeaderViewListAdapter) listAdapter).getWrappedAdapter();
|
||||
} else {
|
||||
adapter = (BookmarksListAdapter) listAdapter;
|
||||
}
|
||||
|
||||
final BookmarksListAdapter adapter = getBookmarksListAdapter();
|
||||
if (adapter.isShowingChildFolder()) {
|
||||
if (position == 0) {
|
||||
// If we tap on an opened folder, move back to parent folder.
|
||||
@ -141,4 +146,15 @@ public class BookmarksListView extends HomeListView
|
||||
getOnUrlOpenListener().onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
|
||||
}
|
||||
}
|
||||
|
||||
private BookmarksListAdapter getBookmarksListAdapter() {
|
||||
BookmarksListAdapter adapter;
|
||||
ListAdapter listAdapter = getAdapter();
|
||||
if (listAdapter instanceof HeaderViewListAdapter) {
|
||||
adapter = (BookmarksListAdapter) ((HeaderViewListAdapter) listAdapter).getWrappedAdapter();
|
||||
} else {
|
||||
adapter = (BookmarksListAdapter) listAdapter;
|
||||
}
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +149,10 @@ public class BookmarksPage extends HomeFragment {
|
||||
});
|
||||
mList.setAdapter(mListAdapter);
|
||||
|
||||
// Invalidate the cached value that keeps track of whether or
|
||||
// not desktop bookmarks (or reading list items) exist.
|
||||
BrowserDB.invalidateCachedState();
|
||||
|
||||
// Create callbacks before the initial loader is started.
|
||||
mLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
|
||||
mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks();
|
||||
@ -362,7 +366,7 @@ public class BookmarksPage extends HomeFragment {
|
||||
@Override
|
||||
public Cursor loadCursor() {
|
||||
final int max = getContext().getResources().getInteger(R.integer.number_of_top_sites);
|
||||
return BrowserDB.getTopSites(getContext().getContentResolver(), max);
|
||||
return BrowserDB.getTopBookmarks(getContext().getContentResolver(), max);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.EditBookmarkDialog;
|
||||
import org.mozilla.gecko.Favicons;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.R;
|
||||
@ -132,14 +133,8 @@ abstract class HomeFragment extends Fragment {
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: bug 897772
|
||||
Bitmap bitmap = null;
|
||||
if (info.favicon != null) {
|
||||
bitmap = BitmapUtils.decodeByteArray(info.favicon);
|
||||
}
|
||||
|
||||
String shortcutTitle = TextUtils.isEmpty(info.title) ? info.url.replaceAll(REGEX_URL_TO_TITLE, "") : info.title;
|
||||
GeckoAppShell.createShortcut(shortcutTitle, info.url, bitmap, "");
|
||||
new AddToLauncherTask(info.url, shortcutTitle).execute();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -225,6 +220,35 @@ abstract class HomeFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private static class AddToLauncherTask extends UiAsyncTask<Void, Void, String> {
|
||||
private final String mUrl;
|
||||
private final String mTitle;
|
||||
|
||||
public AddToLauncherTask(String url, String title) {
|
||||
super(ThreadUtils.getBackgroundHandler());
|
||||
|
||||
mUrl = url;
|
||||
mTitle = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String doInBackground(Void... params) {
|
||||
return Favicons.getInstance().getFaviconUrlForPageUrl(mUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(String faviconUrl) {
|
||||
Favicons.OnFaviconLoadedListener listener = new Favicons.OnFaviconLoadedListener() {
|
||||
@Override
|
||||
public void onFaviconLoaded(String url, Bitmap favicon) {
|
||||
GeckoAppShell.createShortcut(mTitle, mUrl, favicon, "");
|
||||
}
|
||||
};
|
||||
|
||||
Favicons.getInstance().loadFavicon(mUrl, faviconUrl, 0, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoveBookmarkTask extends UiAsyncTask<Void, Void, Void> {
|
||||
private final Context mContext;
|
||||
private final int mId;
|
||||
|
@ -113,7 +113,6 @@ public class HomeListView extends ListView
|
||||
public int bookmarkId;
|
||||
public int historyId;
|
||||
public String url;
|
||||
public byte[] favicon;
|
||||
public String title;
|
||||
public int display;
|
||||
public boolean isFolder;
|
||||
@ -165,13 +164,6 @@ public class HomeListView extends ListView
|
||||
historyId = -1;
|
||||
}
|
||||
|
||||
final int faviconCol = cursor.getColumnIndex(Combined.FAVICON);
|
||||
if (faviconCol != -1) {
|
||||
favicon = cursor.getBlob(faviconCol);
|
||||
} else {
|
||||
favicon = null;
|
||||
}
|
||||
|
||||
// We only have the parent column in cursors from getBookmarksInFolder.
|
||||
final int parentCol = cursor.getColumnIndex(Bookmarks.PARENT);
|
||||
if (parentCol != -1) {
|
||||
|
@ -587,11 +587,11 @@ AboutReader.prototype = {
|
||||
|
||||
let start = 0;
|
||||
|
||||
if (host.startsWith("www"))
|
||||
if (host.startsWith("www."))
|
||||
start = 4;
|
||||
else if (host.startsWith("m"))
|
||||
else if (host.startsWith("m."))
|
||||
start = 2;
|
||||
else if (host.startsWith("mobile"))
|
||||
else if (host.startsWith("mobile."))
|
||||
start = 7;
|
||||
|
||||
return host.substring(start);
|
||||
|
@ -50,6 +50,9 @@ if (outOfProcess) {
|
||||
mm.loadFrameScript(CHILD_LOGGER_SCRIPT, true);
|
||||
mm.loadFrameScript(CHILD_SCRIPT_API, true);
|
||||
mm.loadFrameScript(CHILD_SCRIPT, true);
|
||||
//Workaround for bug 848411, once that bug is fixed, the following line can be removed
|
||||
mm.loadFrameScript('data:,addEventListener%28%22DOMWindowCreated%22%2C%20function%28e%29%20%7B%0A%20%20removeEventListener%28%22DOMWindowCreated%22%2C%20arguments.callee%2C%20false%29%3B%0A%20%20var%20window%20%3D%20e.target.defaultView%3B%0A%20%20window.wrappedJSObject.SpecialPowers.addPermission%28%22allowXULXBL%22%2C%20true%2C%20window.document%29%3B%0A%7D%0AB', true);
|
||||
|
||||
specialPowersObserver._isFrameScriptLoaded = true;
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ function injectController(doc, topic, data) {
|
||||
return;
|
||||
}
|
||||
|
||||
var containingBrowser = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
let containingBrowser = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler;
|
||||
@ -67,7 +67,7 @@ function injectController(doc, topic, data) {
|
||||
}
|
||||
|
||||
SocialService.getProvider(doc.nodePrincipal.origin, function(provider) {
|
||||
if (provider && provider.workerURL && provider.enabled) {
|
||||
if (provider && provider.enabled) {
|
||||
attachToWindow(provider, window);
|
||||
}
|
||||
});
|
||||
@ -88,7 +88,7 @@ function attachToWindow(provider, targetWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
var port = provider.getWorkerPort(targetWindow);
|
||||
let port = provider.workerURL ? provider.getWorkerPort(targetWindow) : null;
|
||||
|
||||
let mozSocialObj = {
|
||||
// Use a method for backwards compat with existing providers, but we
|
||||
@ -206,12 +206,14 @@ function attachToWindow(provider, targetWindow) {
|
||||
return targetWindow.navigator.wrappedJSObject.mozSocial = contentObj;
|
||||
});
|
||||
|
||||
targetWindow.addEventListener("unload", function () {
|
||||
// We want to close the port, but also want the target window to be
|
||||
// able to use the port during an unload event they setup - so we
|
||||
// set a timer which will fire after the unload events have all fired.
|
||||
schedule(function () { port.close(); });
|
||||
});
|
||||
if (port) {
|
||||
targetWindow.addEventListener("unload", function () {
|
||||
// We want to close the port, but also want the target window to be
|
||||
// able to use the port during an unload event they setup - so we
|
||||
// set a timer which will fire after the unload events have all fired.
|
||||
schedule(function () { port.close(); });
|
||||
});
|
||||
}
|
||||
|
||||
// We allow window.close() to close the panel, so add an event handler for
|
||||
// this, then cancel the event (so the window itself doesn't die) and
|
||||
|
@ -34,7 +34,13 @@ XPCOMUtils.defineLazyServiceGetter(this, "etld",
|
||||
|
||||
// Internal helper methods and state
|
||||
let SocialServiceInternal = {
|
||||
enabled: Services.prefs.getBoolPref("social.enabled"),
|
||||
_enabled: Services.prefs.getBoolPref("social.enabled"),
|
||||
get enabled() this._enabled,
|
||||
set enabled(val) {
|
||||
this._enabled = !!val;
|
||||
Services.prefs.setBoolPref("social.enabled", !!val);
|
||||
},
|
||||
|
||||
get providerArray() {
|
||||
return [p for ([, p] of Iterator(this.providers))];
|
||||
},
|
||||
@ -362,7 +368,6 @@ this.SocialService = {
|
||||
|
||||
SocialServiceInternal.enabled = enable;
|
||||
MozSocialAPI.enabled = enable;
|
||||
Services.obs.notifyObservers(null, "social:pref-changed", enable ? "enabled" : "disabled");
|
||||
Services.telemetry.getHistogramById("SOCIAL_TOGGLED").add(enable);
|
||||
},
|
||||
|
||||
@ -723,6 +728,12 @@ function SocialProvider(input) {
|
||||
}
|
||||
|
||||
SocialProvider.prototype = {
|
||||
reload: function() {
|
||||
this._terminate();
|
||||
this._activate();
|
||||
Services.obs.notifyObservers(null, "social:provider-reload", this.origin);
|
||||
},
|
||||
|
||||
// Provider enabled/disabled state. Disabled providers do not have active
|
||||
// connections to their FrameWorkers.
|
||||
_enabled: false,
|
||||
@ -868,6 +879,10 @@ SocialProvider.prototype = {
|
||||
closeAllChatWindows(this);
|
||||
},
|
||||
|
||||
haveLoggedInUser: function () {
|
||||
return !!(this.profile && this.profile.userName);
|
||||
},
|
||||
|
||||
// Called by the workerAPI to add/update a notification icon.
|
||||
setAmbientNotification: function(notification) {
|
||||
if (!this.profile.userName)
|
||||
|
@ -2,6 +2,8 @@
|
||||
* 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/. */
|
||||
|
||||
let provider;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
@ -13,10 +15,22 @@ function test() {
|
||||
|
||||
ensureSocialEnabled();
|
||||
|
||||
SocialService.addProvider(manifest, function (provider) {
|
||||
// enable the provider
|
||||
provider.enabled = true;
|
||||
SocialService.addProvider(manifest, function (p) {
|
||||
provider = p;
|
||||
runTests(tests, undefined, undefined, function () {
|
||||
SocialService.removeProvider(p.origin, function() {
|
||||
ok(!provider.enabled, "removing an enabled provider should have disabled the provider");
|
||||
let port = provider.getWorkerPort();
|
||||
ok(!port, "should not be able to get a port after removing the provider");
|
||||
provider = null;
|
||||
finish();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let tests = {
|
||||
testSingleProvider: function(next) {
|
||||
ok(provider.enabled, "provider is initially enabled");
|
||||
let port = provider.getWorkerPort();
|
||||
ok(port, "should be able to get a port from enabled provider");
|
||||
@ -37,12 +51,38 @@ function test() {
|
||||
ok(port, "should be able to get a port from re-enabled provider");
|
||||
port.close();
|
||||
ok(provider.workerAPI, "should be able to get a workerAPI from re-enabled provider");
|
||||
|
||||
SocialService.removeProvider(provider.origin, function() {
|
||||
ok(!provider.enabled, "removing an enabled provider should have disabled the provider");
|
||||
next();
|
||||
},
|
||||
testTwoProviders: function(next) {
|
||||
// add another provider, test both workers
|
||||
let manifest = {
|
||||
origin: 'http://test2.example.com',
|
||||
name: "Example Provider 2",
|
||||
workerURL: "http://test2.example.com/browser/toolkit/components/social/test/browser/worker_social.js"
|
||||
};
|
||||
SocialService.addProvider(manifest, function (provider2) {
|
||||
ok(provider.enabled, "provider is initially enabled");
|
||||
ok(!provider2.enabled, "provider2 is not initially enabled");
|
||||
provider2.enabled = true;
|
||||
let port = provider.getWorkerPort();
|
||||
ok(!port, "should not be able to get a port after removing the provider");
|
||||
finish();
|
||||
let port2 = provider2.getWorkerPort();
|
||||
ok(port, "have port for provider");
|
||||
ok(port2, "have port for provider2");
|
||||
port.onmessage = function(e) {
|
||||
if (e.data.topic == "test-initialization-complete") {
|
||||
ok(true, "first provider initialized");
|
||||
port2.postMessage({topic: "test-initialization"});
|
||||
}
|
||||
}
|
||||
port2.onmessage = function(e) {
|
||||
if (e.data.topic == "test-initialization-complete") {
|
||||
ok(true, "second provider initialized");
|
||||
SocialService.removeProvider(provider2.origin, function() {
|
||||
next();
|
||||
});
|
||||
}
|
||||
}
|
||||
port.postMessage({topic: "test-initialization"});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -78,38 +78,44 @@ function testGetProviderList(manifests, next) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function testEnabled(manifests, next) {
|
||||
// Check that providers are disabled by default
|
||||
let providers = yield SocialService.getProviderList(next);
|
||||
do_check_true(providers.length >= manifests.length);
|
||||
do_check_true(SocialService.enabled);
|
||||
do_check_true(SocialService.enabled, "social enabled at test start");
|
||||
providers.forEach(function (provider) {
|
||||
do_check_false(provider.enabled);
|
||||
});
|
||||
|
||||
let notificationDisabledCorrect = false;
|
||||
Services.obs.addObserver(function obs1(subj, topic, data) {
|
||||
Services.obs.removeObserver(obs1, "social:pref-changed");
|
||||
notificationDisabledCorrect = data == "disabled";
|
||||
}, "social:pref-changed", false);
|
||||
do_test_pending();
|
||||
function waitForEnableObserver(cb) {
|
||||
Services.prefs.addObserver("social.enabled", function prefObserver(subj, topic, data) {
|
||||
Services.prefs.removeObserver("social.enabled", prefObserver);
|
||||
cb();
|
||||
}, false);
|
||||
}
|
||||
|
||||
// enable one of the added providers
|
||||
providers[providers.length-1].enabled = true;
|
||||
|
||||
// now disable the service and check that it disabled that provider (and all others for good measure)
|
||||
waitForEnableObserver(function() {
|
||||
do_check_true(!SocialService.enabled);
|
||||
providers.forEach(function (provider) {
|
||||
do_check_false(provider.enabled);
|
||||
});
|
||||
waitForEnableObserver(function() {
|
||||
do_check_true(SocialService.enabled);
|
||||
// Enabling the service should not enable providers
|
||||
providers.forEach(function (provider) {
|
||||
do_check_false(provider.enabled);
|
||||
});
|
||||
do_test_finished();
|
||||
});
|
||||
SocialService.enabled = true;
|
||||
});
|
||||
SocialService.enabled = false;
|
||||
do_check_true(notificationDisabledCorrect);
|
||||
do_check_true(!SocialService.enabled);
|
||||
providers.forEach(function (provider) {
|
||||
do_check_false(provider.enabled);
|
||||
});
|
||||
|
||||
SocialService.enabled = true;
|
||||
do_check_true(SocialService.enabled);
|
||||
// Enabling the service should not enable providers
|
||||
providers.forEach(function (provider) {
|
||||
do_check_false(provider.enabled);
|
||||
});
|
||||
}
|
||||
|
||||
function testAddRemoveProvider(manifests, next) {
|
||||
|
Loading…
Reference in New Issue
Block a user