mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1057915 - 2/2: Gonk MobileMessage DB read ahead. r=bevistzeng
This commit is contained in:
parent
f1f889e5f0
commit
eb0f4edee3
@ -513,6 +513,10 @@ let settingsToObserve = {
|
||||
prefName: 'dom.sms.strict7BitEncoding',
|
||||
defaultValue: false
|
||||
},
|
||||
'ril.sms.maxReadAheadEntries': {
|
||||
prefName: 'dom.sms.maxReadAheadEntries',
|
||||
defaultValue: 7
|
||||
},
|
||||
'ui.touch.radius.leftmm': {
|
||||
resetToPref: true
|
||||
},
|
||||
|
@ -65,6 +65,9 @@ const COLLECT_ID_END = 0;
|
||||
const COLLECT_ID_ERROR = -1;
|
||||
const COLLECT_TIMESTAMP_UNUSED = 0;
|
||||
|
||||
// Default value for integer preference "dom.sms.maxReadAheadEntries".
|
||||
const DEFAULT_READ_AHEAD_ENTRIES = 7;
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService",
|
||||
"@mozilla.org/mobilemessage/mobilemessageservice;1",
|
||||
"nsIMobileMessageService");
|
||||
@ -3143,7 +3146,7 @@ MobileMessageDB.prototype = {
|
||||
|
||||
let self = this;
|
||||
self.newTxn(READ_ONLY, function(error, txn, stores) {
|
||||
let collector = cursor.collector;
|
||||
let collector = cursor.collector.idCollector;
|
||||
let collect = collector.collect.bind(collector);
|
||||
FilterSearcherHelper.transact(self, txn, error, filter, aReverse, collect);
|
||||
}, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME]);
|
||||
@ -3252,7 +3255,7 @@ MobileMessageDB.prototype = {
|
||||
|
||||
let cursor = new GetThreadsCursor(this, callback);
|
||||
this.newTxn(READ_ONLY, function(error, txn, threadStore) {
|
||||
let collector = cursor.collector;
|
||||
let collector = cursor.collector.idCollector;
|
||||
if (error) {
|
||||
collector.collect(null, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED);
|
||||
return;
|
||||
@ -3489,11 +3492,306 @@ let FilterSearcherHelper = {
|
||||
}
|
||||
};
|
||||
|
||||
function ResultsCollector() {
|
||||
/**
|
||||
* Collector class for read-ahead result objects. Mmdb may now try to fetch
|
||||
* message/thread records before it's requested explicitly.
|
||||
*
|
||||
* The read ahead behavior can be controlled by an integer mozSettings entry
|
||||
* "ril.sms.maxReadAheadEntries" as well as an integer holding preference
|
||||
* "dom.sms.maxReadAheadEntries". The meanings are:
|
||||
*
|
||||
* positive: finite read-ahead entries,
|
||||
* 0: don't read ahead unless explicitly requested, (default)
|
||||
* negative: read ahead all IDs if possible.
|
||||
*
|
||||
* The order of ID filtering objects are now:
|
||||
*
|
||||
* [UnionResultsCollector]
|
||||
* +-> [IntersectionResultsCollector]
|
||||
* +-> IDsCollector
|
||||
* +-> ResultsCollector
|
||||
*
|
||||
* ResultsCollector has basically similar behaviour with IDsCollector. When
|
||||
* RC::squeeze() is called, either RC::drip() is called instantly if we have
|
||||
* already fetched results available, or the request is kept and IC::squeeze()
|
||||
* is called.
|
||||
*
|
||||
* When RC::collect is called by IC::drip, it proceeds to fetch the
|
||||
* corresponding record given that collected ID is neither an error nor an end
|
||||
* mark. After the message/thread record being fetched, ResultsCollector::drip
|
||||
* is called if we have pending request. Anyway, RC::maybeSqueezeIdCollector is
|
||||
* called to determine whether we need to call IC::squeeze again.
|
||||
*
|
||||
* RC::squeeze is called when nsICursorContinueCallback::handleContinue() is
|
||||
* called. ResultsCollector::drip will call to
|
||||
* nsIMobileMessageCursorCallback::notifyFoo.
|
||||
*
|
||||
* In summary, the major call paths are:
|
||||
*
|
||||
* RC::squeeze
|
||||
* o-> RC::drip
|
||||
* +-> RC::notifyCallback
|
||||
* +-> nsIMobileMessageCursorCallback::notifyFoo
|
||||
* +-> RC::maybeSqueezeIdCollector
|
||||
* o-> IC::squeeze
|
||||
* o-> IC::drip
|
||||
* +-> RC::collect
|
||||
* o-> RC::readAhead
|
||||
* +-> RC::notifyResult
|
||||
* o-> RC::drip ...
|
||||
* +-> RC::maybeSqueezeIdCollector ...
|
||||
* o-> RC::notifyResult ...
|
||||
*/
|
||||
function ResultsCollector(readAheadFunc) {
|
||||
this.idCollector = new IDsCollector();
|
||||
this.results = [];
|
||||
this.readAhead = readAheadFunc;
|
||||
|
||||
this.maxReadAhead = DEFAULT_READ_AHEAD_ENTRIES;
|
||||
try {
|
||||
// positive: finite read-ahead entries,
|
||||
// 0: don't read ahead unless explicitly requested,
|
||||
// negative: read ahead all IDs if possible.
|
||||
this.maxReadAhead =
|
||||
Services.prefs.getIntPref("dom.sms.maxReadAheadEntries");
|
||||
} catch (e) {}
|
||||
}
|
||||
ResultsCollector.prototype = {
|
||||
/**
|
||||
* Underlying ID collector object.
|
||||
*/
|
||||
idCollector: null,
|
||||
|
||||
/**
|
||||
* An array keeping fetched result objects. Replaced by a new empty array
|
||||
* every time when |this.drip| is called.
|
||||
*/
|
||||
results: null,
|
||||
|
||||
/**
|
||||
* A function that takes (<txn>, <id>, <collector>). It fetches the object
|
||||
* specified by <id> and notify <collector> with that by calling
|
||||
* |<collector>.notifyResult()|. If <txn> is null, this function should
|
||||
* create a new read-only transaction itself. The returned result object may
|
||||
* be null to indicate an error during the fetch process.
|
||||
*/
|
||||
readAhead: null,
|
||||
|
||||
/**
|
||||
* A boolean value inidicating a readAhead call is ongoing. Set before calling
|
||||
* |this.readAhead| and reset in |this.notifyResult|.
|
||||
*/
|
||||
readingAhead: false,
|
||||
|
||||
/**
|
||||
* A numeric value read from preference "dom.sms.maxReadAheadEntries".
|
||||
*/
|
||||
maxReadAhead: 0,
|
||||
|
||||
/**
|
||||
* An active IDBTransaction object to be reused.
|
||||
*/
|
||||
activeTxn: null,
|
||||
|
||||
/**
|
||||
* A nsIMobileMessageCursorCallback.
|
||||
*/
|
||||
requestWaiting: null,
|
||||
|
||||
/**
|
||||
* A boolean value indicating either a COLLECT_ID_END or COLLECT_ID_ERROR has
|
||||
* been received.
|
||||
*/
|
||||
done: false,
|
||||
|
||||
/**
|
||||
* When |this.done|, it's either COLLECT_ID_END or COLLECT_ID_ERROR.
|
||||
*/
|
||||
lastId: null,
|
||||
|
||||
/**
|
||||
* Receive collected id from IDsCollector and fetch the correspond result
|
||||
* object if necessary.
|
||||
*
|
||||
* @param txn
|
||||
* An IDBTransaction object. Null if there is no active transaction in
|
||||
* IDsCollector. That is, the ID collecting transaction is completed.
|
||||
* @param id
|
||||
* A positive numeric id, COLLECT_ID_END(0), or COLLECT_ID_ERROR(-1).
|
||||
*/
|
||||
collect: function(txn, id) {
|
||||
if (this.done) {
|
||||
// If this callector has been terminated because of previous errors in
|
||||
// |this.readAhead|, ignore any further IDs from IDsCollector.
|
||||
return;
|
||||
}
|
||||
|
||||
if (DEBUG) debug("ResultsCollector::collect ID = " + id);
|
||||
|
||||
// Reuse the active transaction cached if IDsCollector has no active
|
||||
// transaction.
|
||||
txn = txn || this.activeTxn;
|
||||
|
||||
if (id > 0) {
|
||||
this.readingAhead = true;
|
||||
this.readAhead(txn, id, this);
|
||||
} else {
|
||||
this.notifyResult(txn, id, null);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback function for |this.readAhead|.
|
||||
*
|
||||
* This function pushes result object to |this.results| or updates
|
||||
* |this.done|, |this.lastId| if an end mark or an error is found. Since we
|
||||
* have already a valid result entry, check |this.requestWaiting| and deal
|
||||
* with it. At last, call to |this.maybeSqueezeIdCollector| to ask more id
|
||||
* again if necessary.
|
||||
*
|
||||
* @param txn
|
||||
* An IDBTransaction object. Null if caller has no active transaction.
|
||||
* @param id
|
||||
* A positive numeric id, COLLECT_ID_END(0), or COLLECT_ID_ERROR(-1).
|
||||
* @param result
|
||||
* An object associated with id. Null if |this.readAhead| failed.
|
||||
*/
|
||||
notifyResult: function(txn, id, result) {
|
||||
if (DEBUG) debug("notifyResult(txn, " + id + ", <result>)");
|
||||
|
||||
this.readingAhead = false;
|
||||
|
||||
if (id > 0) {
|
||||
if (result != null) {
|
||||
this.results.push(result);
|
||||
} else {
|
||||
id = COLLECT_ID_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (id <= 0) {
|
||||
this.lastId = id;
|
||||
this.done = true;
|
||||
}
|
||||
|
||||
if (!this.requestWaiting) {
|
||||
if (DEBUG) debug("notifyResult: cursor.continue() not called yet");
|
||||
} else {
|
||||
let callback = this.requestWaiting;
|
||||
this.requestWaiting = null;
|
||||
|
||||
this.drip(callback);
|
||||
}
|
||||
|
||||
this.maybeSqueezeIdCollector(txn);
|
||||
},
|
||||
|
||||
/**
|
||||
* Request for one more ID if necessary.
|
||||
*
|
||||
* @param txn
|
||||
* An IDBTransaction object. Null if caller has no active transaction.
|
||||
*/
|
||||
maybeSqueezeIdCollector: function(txn) {
|
||||
if (this.done || // Nothing to be read.
|
||||
this.readingAhead || // Already in progress.
|
||||
this.idCollector.requestWaiting) { // Already requested.
|
||||
return;
|
||||
}
|
||||
|
||||
let max = this.maxReadAhead;
|
||||
if (!max && this.requestWaiting) {
|
||||
// If |this.requestWaiting| is set, try to read ahead at least once.
|
||||
max = 1;
|
||||
}
|
||||
if (max >= 0 && this.results.length >= max) {
|
||||
// More-equal than <max> entries has been read. Stop.
|
||||
if (DEBUG) debug("maybeSqueezeIdCollector: max " + max + " entries read. Stop.");
|
||||
return;
|
||||
}
|
||||
|
||||
// A hack to pass current txn to |this.collect| when it's called directly by
|
||||
// |IDsCollector.squeeze|.
|
||||
this.activeTxn = txn;
|
||||
this.idCollector.squeeze(this.collect.bind(this));
|
||||
this.activeTxn = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Request to pass available results or wait.
|
||||
*
|
||||
* @param callback
|
||||
* A nsIMobileMessageCursorCallback.
|
||||
*/
|
||||
squeeze: function(callback) {
|
||||
if (this.requestWaiting) {
|
||||
throw new Error("Already waiting for another request!");
|
||||
}
|
||||
|
||||
if (this.results.length || this.done) {
|
||||
// If |this.results.length| is non-zero, we have already some results to
|
||||
// pass. Otherwise, if |this.done| evaluates to true, we have also a
|
||||
// confirmed result to pass.
|
||||
this.drip(callback);
|
||||
} else {
|
||||
this.requestWaiting = callback;
|
||||
}
|
||||
|
||||
// If we called |this.drip| in the last step, the fetched results have been
|
||||
// consumed and we should ask some more for read-ahead now.
|
||||
//
|
||||
// Otherwise, kick start read-ahead again because it may be stopped
|
||||
// previously because of |this.maxReadAhead| had been reached.
|
||||
this.maybeSqueezeIdCollector(null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Consume fetched resutls.
|
||||
*
|
||||
* @param callback
|
||||
* A nsIMobileMessageCursorCallback.
|
||||
*/
|
||||
drip: function(callback) {
|
||||
let results = this.results;
|
||||
this.results = [];
|
||||
|
||||
let func = this.notifyCallback.bind(this, callback, results, this.lastId);
|
||||
Services.tm.currentThread.dispatch(func, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify a nsIMobileMessageCursorCallback.
|
||||
*
|
||||
* @param callback
|
||||
* A nsIMobileMessageCursorCallback.
|
||||
* @param results
|
||||
* An array of result objects.
|
||||
* @param lastId
|
||||
* Since we only call |this.drip| when either there are results
|
||||
* available or the read-ahead has done, so lastId here will be
|
||||
* COLLECT_ID_END or COLLECT_ID_ERROR when results is empty and null
|
||||
* otherwise.
|
||||
*/
|
||||
notifyCallback: function(callback, results, lastId) {
|
||||
if (DEBUG) {
|
||||
debug("notifyCallback(results[" + results.length + "], " + lastId + ")");
|
||||
}
|
||||
|
||||
if (results.length) {
|
||||
callback.notifyCursorResult(results, results.length);
|
||||
} else if (lastId == COLLECT_ID_END) {
|
||||
callback.notifyCursorDone();
|
||||
} else {
|
||||
callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function IDsCollector() {
|
||||
this.results = [];
|
||||
this.done = false;
|
||||
}
|
||||
ResultsCollector.prototype = {
|
||||
IDsCollector.prototype = {
|
||||
results: null,
|
||||
requestWaiting: null,
|
||||
done: null,
|
||||
@ -3517,20 +3815,16 @@ ResultsCollector.prototype = {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
debug("collect: message ID = " + id);
|
||||
}
|
||||
if (id) {
|
||||
// Queue up any id but '0' and replies later accordingly.
|
||||
this.results.push(id);
|
||||
}
|
||||
if (DEBUG) debug("IDsCollector::collect ID = " + id);
|
||||
// Queue up any id.
|
||||
this.results.push(id);
|
||||
if (id <= 0) {
|
||||
// No more processing on '0' or negative values passed.
|
||||
this.done = true;
|
||||
}
|
||||
|
||||
if (!this.requestWaiting) {
|
||||
if (DEBUG) debug("Cursor.continue() not called yet");
|
||||
if (DEBUG) debug("IDsCollector::squeeze() not called yet");
|
||||
return !this.done;
|
||||
}
|
||||
|
||||
@ -3575,22 +3869,11 @@ ResultsCollector.prototype = {
|
||||
* A callback function that accepts a numeric id.
|
||||
*/
|
||||
drip: function(txn, callback) {
|
||||
if (!this.results.length) {
|
||||
if (DEBUG) debug("No messages matching the filter criteria");
|
||||
callback(txn, COLLECT_ID_END);
|
||||
return;
|
||||
let firstId = this.results[0];
|
||||
if (firstId > 0) {
|
||||
this.results.shift();
|
||||
}
|
||||
|
||||
if (this.results[0] < 0) {
|
||||
// An previous error found. Keep the answer in results so that we can
|
||||
// reply INTERNAL_ERROR for further requests.
|
||||
if (DEBUG) debug("An previous error found");
|
||||
callback(txn, COLLECT_ID_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
let firstMessageId = this.results.shift();
|
||||
callback(txn, firstMessageId);
|
||||
callback(txn, firstId);
|
||||
}
|
||||
};
|
||||
|
||||
@ -3784,7 +4067,7 @@ UnionResultsCollector.prototype = {
|
||||
function GetMessagesCursor(mmdb, callback) {
|
||||
this.mmdb = mmdb;
|
||||
this.callback = callback;
|
||||
this.collector = new ResultsCollector();
|
||||
this.collector = new ResultsCollector(this.getMessage.bind(this));
|
||||
|
||||
this.handleContinue(); // Trigger first run.
|
||||
}
|
||||
@ -3796,7 +4079,7 @@ GetMessagesCursor.prototype = {
|
||||
callback: null,
|
||||
collector: null,
|
||||
|
||||
getMessageTxn: function(messageStore, messageId) {
|
||||
getMessageTxn: function(txn, messageStore, messageId, collector) {
|
||||
if (DEBUG) debug ("Fetching message " + messageId);
|
||||
|
||||
let getRequest = messageStore.get(messageId);
|
||||
@ -3807,32 +4090,26 @@ GetMessagesCursor.prototype = {
|
||||
}
|
||||
let domMessage =
|
||||
self.mmdb.createDomMessageFromRecord(event.target.result);
|
||||
self.callback.notifyCursorResult([domMessage], 1);
|
||||
collector.notifyResult(txn, messageId, domMessage);
|
||||
};
|
||||
getRequest.onerror = function(event) {
|
||||
// Error reporting is done in ResultsCollector.notifyCallback.
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if (DEBUG) {
|
||||
debug("notifyCursorError - messageId: " + messageId);
|
||||
}
|
||||
self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
|
||||
collector.notifyResult(txn, messageId, null);
|
||||
};
|
||||
},
|
||||
|
||||
notify: function(txn, messageId) {
|
||||
if (!messageId) {
|
||||
this.callback.notifyCursorDone();
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageId < 0) {
|
||||
this.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
getMessage: function(txn, messageId, collector) {
|
||||
// When filter transaction is not yet completed, we're called with current
|
||||
// ongoing transaction object.
|
||||
if (txn) {
|
||||
let messageStore = txn.objectStore(MESSAGE_STORE_NAME);
|
||||
this.getMessageTxn(messageStore, messageId);
|
||||
this.getMessageTxn(txn, messageStore, messageId, collector);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3840,10 +4117,11 @@ GetMessagesCursor.prototype = {
|
||||
let self = this;
|
||||
this.mmdb.newTxn(READ_ONLY, function(error, txn, messageStore) {
|
||||
if (error) {
|
||||
self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
|
||||
return;
|
||||
debug("getMessage: failed to create new transaction");
|
||||
collector.notifyResult(null, messageId, null);
|
||||
} else {
|
||||
self.getMessageTxn(txn, messageStore, messageId, collector);
|
||||
}
|
||||
self.getMessageTxn(messageStore, messageId);
|
||||
}, [MESSAGE_STORE_NAME]);
|
||||
},
|
||||
|
||||
@ -3851,14 +4129,14 @@ GetMessagesCursor.prototype = {
|
||||
|
||||
handleContinue: function() {
|
||||
if (DEBUG) debug("Getting next message in list");
|
||||
this.collector.squeeze(this.notify.bind(this));
|
||||
this.collector.squeeze(this.callback);
|
||||
}
|
||||
};
|
||||
|
||||
function GetThreadsCursor(mmdb, callback) {
|
||||
this.mmdb = mmdb;
|
||||
this.callback = callback;
|
||||
this.collector = new ResultsCollector();
|
||||
this.collector = new ResultsCollector(this.getThread.bind(this));
|
||||
|
||||
this.handleContinue(); // Trigger first run.
|
||||
}
|
||||
@ -3870,11 +4148,10 @@ GetThreadsCursor.prototype = {
|
||||
callback: null,
|
||||
collector: null,
|
||||
|
||||
getThreadTxn: function(threadStore, threadId) {
|
||||
getThreadTxn: function(txn, threadStore, threadId, collector) {
|
||||
if (DEBUG) debug ("Fetching thread " + threadId);
|
||||
|
||||
let getRequest = threadStore.get(threadId);
|
||||
let self = this;
|
||||
getRequest.onsuccess = function(event) {
|
||||
let threadRecord = event.target.result;
|
||||
if (DEBUG) {
|
||||
@ -3888,32 +4165,26 @@ GetThreadsCursor.prototype = {
|
||||
threadRecord.body,
|
||||
threadRecord.unreadCount,
|
||||
threadRecord.lastMessageType);
|
||||
self.callback.notifyCursorResult([thread], 1);
|
||||
collector.notifyResult(txn, threadId, thread);
|
||||
};
|
||||
getRequest.onerror = function(event) {
|
||||
// Error reporting is done in ResultsCollector.notifyCallback.
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if (DEBUG) {
|
||||
debug("notifyCursorError - threadId: " + threadId);
|
||||
}
|
||||
self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
|
||||
collector.notifyResult(txn, threadId, null);
|
||||
};
|
||||
},
|
||||
|
||||
notify: function(txn, threadId) {
|
||||
if (!threadId) {
|
||||
this.callback.notifyCursorDone();
|
||||
return;
|
||||
}
|
||||
|
||||
if (threadId < 0) {
|
||||
this.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
getThread: function(txn, threadId, collector) {
|
||||
// When filter transaction is not yet completed, we're called with current
|
||||
// ongoing transaction object.
|
||||
if (txn) {
|
||||
let threadStore = txn.objectStore(THREAD_STORE_NAME);
|
||||
this.getThreadTxn(threadStore, threadId);
|
||||
this.getThreadTxn(txn, threadStore, threadId, collector);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3921,10 +4192,10 @@ GetThreadsCursor.prototype = {
|
||||
let self = this;
|
||||
this.mmdb.newTxn(READ_ONLY, function(error, txn, threadStore) {
|
||||
if (error) {
|
||||
self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
|
||||
return;
|
||||
collector.notifyResult(null, threadId, null);
|
||||
} else {
|
||||
self.getThreadTxn(txn, threadStore, threadId, collector);
|
||||
}
|
||||
self.getThreadTxn(threadStore, threadId);
|
||||
}, [THREAD_STORE_NAME]);
|
||||
},
|
||||
|
||||
@ -3932,7 +4203,7 @@ GetThreadsCursor.prototype = {
|
||||
|
||||
handleContinue: function() {
|
||||
if (DEBUG) debug("Getting next thread in list");
|
||||
this.collector.squeeze(this.notify.bind(this));
|
||||
this.collector.squeeze(this.callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3956,6 +3956,12 @@ pref("dom.sms.requestStatusReport", true);
|
||||
// Numeric default service id for SMS API calls with |serviceId| parameter
|
||||
// omitted.
|
||||
pref("dom.sms.defaultServiceId", 0);
|
||||
// MobileMessage GetMessages/GetThreads read ahead aggressiveness.
|
||||
//
|
||||
// positive: finite read-ahead entries,
|
||||
// 0: don't read ahead unless explicitly requested, (default)
|
||||
// negative: read ahead all IDs if possible.
|
||||
pref("dom.sms.maxReadAheadEntries", 0);
|
||||
|
||||
// WebContacts
|
||||
pref("dom.mozContacts.enabled", false);
|
||||
|
Loading…
Reference in New Issue
Block a user