зеркало из https://github.com/mozilla/gecko-dev.git
Merge b2g-inbound to m-c.
This commit is contained in:
Коммит
9c932cff15
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"revision": "4e8959a60a4ee1373e71fe31229ba5bd7575c02c",
|
||||
"revision": "14a570c0af0ad29420a47318576fc365ebf7b10a",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -123,12 +123,6 @@ public:
|
|||
|
||||
NS_IMPL_ISUPPORTS1(nsContentView, nsIContentView)
|
||||
|
||||
bool
|
||||
nsContentView::IsRoot() const
|
||||
{
|
||||
return mScrollId == FrameMetrics::ROOT_SCROLL_ID;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsContentView::Update(const ViewConfig& aConfig)
|
||||
{
|
||||
|
@ -2376,7 +2370,7 @@ nsFrameLoader::GetRootContentView(nsIContentView** aContentView)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsContentView* view = rfp->GetContentView();
|
||||
nsContentView* view = rfp->GetRootContentView();
|
||||
NS_ABORT_IF_FALSE(view, "Should always be able to create root scrollable!");
|
||||
nsRefPtr<nsIContentView>(view).forget(aContentView);
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ public:
|
|||
float mYScale;
|
||||
};
|
||||
|
||||
nsContentView(nsFrameLoader* aFrameLoader, ViewID aScrollId,
|
||||
nsContentView(nsFrameLoader* aFrameLoader, ViewID aScrollId, bool aIsRoot,
|
||||
ViewConfig aConfig = ViewConfig())
|
||||
: mViewportSize(0, 0)
|
||||
, mContentSize(0, 0)
|
||||
|
@ -111,10 +111,14 @@ public:
|
|||
, mParentScaleY(1.0)
|
||||
, mFrameLoader(aFrameLoader)
|
||||
, mScrollId(aScrollId)
|
||||
, mIsRoot(aIsRoot)
|
||||
, mConfig(aConfig)
|
||||
{}
|
||||
|
||||
bool IsRoot() const;
|
||||
bool IsRoot() const
|
||||
{
|
||||
return mIsRoot;
|
||||
}
|
||||
|
||||
ViewID GetId() const
|
||||
{
|
||||
|
@ -137,6 +141,7 @@ private:
|
|||
nsresult Update(const ViewConfig& aConfig);
|
||||
|
||||
ViewID mScrollId;
|
||||
bool mIsRoot;
|
||||
ViewConfig mConfig;
|
||||
};
|
||||
|
||||
|
|
|
@ -1699,22 +1699,6 @@ nsDOMWindowUtils::FindElementWithViewId(nsViewID aID,
|
|||
return NS_ERROR_DOM_SECURITY_ERR;
|
||||
}
|
||||
|
||||
if (aID == FrameMetrics::ROOT_SCROLL_ID) {
|
||||
nsPresContext* presContext = GetPresContext();
|
||||
if (!presContext) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
nsIDocument* document = presContext->Document();
|
||||
Element* rootElement = document->GetRootElement();
|
||||
if (!rootElement) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
CallQueryInterface(rootElement, aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsRefPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aID);
|
||||
return content ? CallQueryInterface(content, aResult) : NS_OK;
|
||||
}
|
||||
|
|
|
@ -293,7 +293,7 @@ parent:
|
|||
* The zoom controller code lives on the parent side and so this allows it to
|
||||
* have up-to-date zoom constraints.
|
||||
*/
|
||||
UpdateZoomConstraints(uint32_t aPresShellId, ViewID aViewId,
|
||||
UpdateZoomConstraints(uint32_t aPresShellId, ViewID aViewId, bool aIsRoot,
|
||||
bool aAllowZoom, CSSToScreenScale aMinZoom, CSSToScreenScale aMaxZoom);
|
||||
|
||||
/**
|
||||
|
|
|
@ -303,7 +303,6 @@ TabChild::HandleEvent(nsIDOMEvent* aEvent)
|
|||
|
||||
ViewID viewId;
|
||||
uint32_t presShellId;
|
||||
nsIScrollableFrame* scrollFrame = nullptr;
|
||||
|
||||
nsCOMPtr<nsIContent> content;
|
||||
if (nsCOMPtr<nsIDocument> doc = do_QueryInterface(target))
|
||||
|
@ -317,27 +316,19 @@ TabChild::HandleEvent(nsIDOMEvent* aEvent)
|
|||
if (!nsLayoutUtils::FindIDFor(content, &viewId))
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
|
||||
// Note that we cannot use FindScrollableFrameFor(ROOT_SCROLL_ID) because
|
||||
// it might return the root element from a different page in the case where
|
||||
// that page is in the bfcache and this page is not run through layout
|
||||
// before being drawn to the screen. Hence the code blocks below treat
|
||||
// ROOT_SCROLL_ID separately from the non-ROOT_SCROLL_ID case.
|
||||
nsIScrollableFrame* scrollFrame = nsLayoutUtils::FindScrollableFrameFor(viewId);
|
||||
if (!scrollFrame)
|
||||
return NS_OK;
|
||||
|
||||
CSSIntPoint scrollOffset;
|
||||
if (viewId != FrameMetrics::ROOT_SCROLL_ID) {
|
||||
scrollFrame = nsLayoutUtils::FindScrollableFrameFor(viewId);
|
||||
if (!scrollFrame) {
|
||||
return NS_OK;
|
||||
}
|
||||
scrollOffset = scrollFrame->GetScrollPositionCSSPixels();
|
||||
} else {
|
||||
CSSIntPoint scrollOffset = scrollFrame->GetScrollPositionCSSPixels();
|
||||
|
||||
if (viewId == mLastMetrics.mScrollId) {
|
||||
// For the root frame, we store the last metrics, including the last
|
||||
// scroll offset, sent by APZC. (This is updated in ProcessUpdateFrame()).
|
||||
// We use this here to avoid sending APZC back a scroll event that
|
||||
// originally came from APZC (besides being unnecessary, the event might
|
||||
// be slightly out of date by the time it reaches APZC).
|
||||
// We should probably do this for subframes, too.
|
||||
utils->GetScrollXY(false, &scrollOffset.x, &scrollOffset.y);
|
||||
if (RoundedToInt(mLastMetrics.mScrollOffset) == scrollOffset) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -347,6 +338,7 @@ TabChild::HandleEvent(nsIDOMEvent* aEvent)
|
|||
// gets a chance to update it.
|
||||
mLastMetrics.mScrollOffset = scrollOffset;
|
||||
}
|
||||
|
||||
SendUpdateScrollOffset(presShellId, viewId, scrollOffset);
|
||||
}
|
||||
|
||||
|
@ -547,6 +539,7 @@ TabChild::HandlePossibleViewportChange()
|
|||
nsViewportInfo viewportInfo = nsContentUtils::GetViewportInfo(document, mInnerSize);
|
||||
SendUpdateZoomConstraints(presShellId,
|
||||
viewId,
|
||||
/* isRoot = */ true,
|
||||
viewportInfo.IsZoomAllowed(),
|
||||
viewportInfo.GetMinZoom(),
|
||||
viewportInfo.GetMaxZoom());
|
||||
|
@ -1537,14 +1530,14 @@ TabChild::RecvUpdateFrame(const FrameMetrics& aFrameMetrics)
|
|||
{
|
||||
MOZ_ASSERT(aFrameMetrics.mScrollId != FrameMetrics::NULL_SCROLL_ID);
|
||||
|
||||
if (aFrameMetrics.mScrollId == FrameMetrics::ROOT_SCROLL_ID) {
|
||||
if (aFrameMetrics.mIsRoot) {
|
||||
nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
|
||||
if (APZCCallbackHelper::HasValidPresShellId(utils, aFrameMetrics)) {
|
||||
return ProcessUpdateFrame(aFrameMetrics);
|
||||
}
|
||||
} else {
|
||||
// aFrameMetrics.mScrollId is not FrameMetrics::ROOT_SCROLL_ID,
|
||||
// so we are trying to update a subframe. This requires special handling.
|
||||
// aFrameMetrics.mIsRoot is false, so we are trying to update a subframe.
|
||||
// This requires special handling.
|
||||
nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(
|
||||
aFrameMetrics.mScrollId);
|
||||
if (content) {
|
||||
|
|
|
@ -1628,12 +1628,13 @@ TabParent::RecvZoomToRect(const uint32_t& aPresShellId,
|
|||
bool
|
||||
TabParent::RecvUpdateZoomConstraints(const uint32_t& aPresShellId,
|
||||
const ViewID& aViewId,
|
||||
const bool& aIsRoot,
|
||||
const bool& aAllowZoom,
|
||||
const CSSToScreenScale& aMinZoom,
|
||||
const CSSToScreenScale& aMaxZoom)
|
||||
{
|
||||
if (RenderFrameParent* rfp = GetRenderFrame()) {
|
||||
rfp->UpdateZoomConstraints(aPresShellId, aViewId, aAllowZoom, aMinZoom, aMaxZoom);
|
||||
rfp->UpdateZoomConstraints(aPresShellId, aViewId, aIsRoot, aAllowZoom, aMinZoom, aMaxZoom);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@ public:
|
|||
const CSSRect& aRect);
|
||||
virtual bool RecvUpdateZoomConstraints(const uint32_t& aPresShellId,
|
||||
const ViewID& aViewId,
|
||||
const bool& aIsRoot,
|
||||
const bool& aAllowZoom,
|
||||
const CSSToScreenScale& aMinZoom,
|
||||
const CSSToScreenScale& aMaxZoom);
|
||||
|
|
|
@ -25,7 +25,7 @@ interface nsIRilMobileMessageDatabaseRecordCallback : nsISupports
|
|||
void notify(in nsresult aRv, in jsval aMessageRecord, in nsISupports aDomMessage);
|
||||
};
|
||||
|
||||
[scriptable, uuid(f6cd671e-f9af-11e2-b64b-1fb87e9c217c)]
|
||||
[scriptable, uuid(d5374151-7451-4590-a70e-40c49c1369ce)]
|
||||
interface nsIRilMobileMessageDatabaseService : nsIMobileMessageDatabaseService
|
||||
{
|
||||
/**
|
||||
|
@ -38,6 +38,7 @@ interface nsIRilMobileMessageDatabaseService : nsIMobileMessageDatabaseService
|
|||
* - If |type| == "sms", we also need:
|
||||
* - |messageClass| DOMString: the message class of received message
|
||||
* - |receiver| DOMString: the phone number of receiver
|
||||
* - |pid| Number: the TP-PID field of the SMS TPDU, default 0.
|
||||
*
|
||||
* - If |type| == "mms", we also need:
|
||||
* - |delivery| DOMString: the delivery state of received message
|
||||
|
@ -49,7 +50,7 @@ interface nsIRilMobileMessageDatabaseService : nsIMobileMessageDatabaseService
|
|||
* Note: |deliveryStatus| should only contain single string to specify
|
||||
* the delivery status of MMS message for the phone owner self.
|
||||
*/
|
||||
long saveReceivedMessage(in jsval aMessage,
|
||||
void saveReceivedMessage(in jsval aMessage,
|
||||
[optional] in nsIRilMobileMessageDatabaseCallback aCallback);
|
||||
|
||||
/**
|
||||
|
@ -66,7 +67,7 @@ interface nsIRilMobileMessageDatabaseService : nsIMobileMessageDatabaseService
|
|||
* - If |type| == "mms", we also need:
|
||||
* - |receivers| DOMString Array: the phone numbers of receivers
|
||||
*/
|
||||
long saveSendingMessage(in jsval aMessage,
|
||||
void saveSendingMessage(in jsval aMessage,
|
||||
[optional] in nsIRilMobileMessageDatabaseCallback aCallback);
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,6 +10,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
|
||||
|
||||
var RIL = {};
|
||||
Cu.import("resource://gre/modules/ril_consts.js", RIL);
|
||||
|
||||
const RIL_MOBILEMESSAGEDATABASESERVICE_CONTRACTID =
|
||||
"@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1";
|
||||
const RIL_MOBILEMESSAGEDATABASESERVICE_CID =
|
||||
|
@ -24,7 +27,7 @@ const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
|
|||
|
||||
|
||||
const DB_NAME = "sms";
|
||||
const DB_VERSION = 18;
|
||||
const DB_VERSION = 19;
|
||||
const MESSAGE_STORE_NAME = "sms";
|
||||
const THREAD_STORE_NAME = "thread";
|
||||
const PARTICIPANT_STORE_NAME = "participant";
|
||||
|
@ -252,6 +255,10 @@ MobileMessageDatabaseService.prototype = {
|
|||
self.upgradeSchema17(event.target.transaction, next);
|
||||
break;
|
||||
case 18:
|
||||
if (DEBUG) debug("Upgrade to version 19. Add pid for incoming SMS.");
|
||||
self.upgradeSchema18(event.target.transaction, next);
|
||||
break;
|
||||
case 19:
|
||||
// This will need to be moved for each new version
|
||||
if (DEBUG) debug("Upgrade finished.");
|
||||
break;
|
||||
|
@ -1151,6 +1158,28 @@ MobileMessageDatabaseService.prototype = {
|
|||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Add pid for incoming SMS.
|
||||
*/
|
||||
upgradeSchema18: function upgradeSchema18(transaction, next) {
|
||||
let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
|
||||
|
||||
messageStore.openCursor().onsuccess = function(event) {
|
||||
let cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
let messageRecord = cursor.value;
|
||||
if (messageRecord.type == "sms") {
|
||||
messageRecord.pid = RIL.PDU_PID_DEFAULT;
|
||||
cursor.update(messageRecord);
|
||||
}
|
||||
cursor.continue();
|
||||
};
|
||||
},
|
||||
|
||||
matchParsedPhoneNumbers: function matchParsedPhoneNumbers(addr1, parsedAddr1,
|
||||
addr2, parsedAddr2) {
|
||||
if ((parsedAddr1.internationalNumber &&
|
||||
|
@ -1455,30 +1484,26 @@ MobileMessageDatabaseService.prototype = {
|
|||
},
|
||||
|
||||
saveRecord: function saveRecord(aMessageRecord, aAddresses, aCallback) {
|
||||
let isOverriding = (aMessageRecord.id !== undefined);
|
||||
if (!isOverriding) {
|
||||
// Assign a new id.
|
||||
this.lastMessageId += 1;
|
||||
aMessageRecord.id = this.lastMessageId;
|
||||
}
|
||||
if (DEBUG) debug("Going to store " + JSON.stringify(aMessageRecord));
|
||||
|
||||
let self = this;
|
||||
function notifyResult(rv) {
|
||||
if (!aCallback) {
|
||||
return;
|
||||
}
|
||||
let domMessage = self.createDomMessageFromRecord(aMessageRecord);
|
||||
aCallback.notify(rv, domMessage);
|
||||
}
|
||||
|
||||
this.newTxn(READ_WRITE, function(error, txn, stores) {
|
||||
let notifyResult = function(rv) {
|
||||
if (aCallback) {
|
||||
aCallback.notify(rv, self.createDomMessageFromRecord(aMessageRecord));
|
||||
}
|
||||
};
|
||||
|
||||
if (error) {
|
||||
// TODO bug 832140 check event.target.errorCode
|
||||
notifyResult(Cr.NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
txn.oncomplete = function oncomplete(event) {
|
||||
if (aMessageRecord.id > self.lastMessageId) {
|
||||
self.lastMessageId = aMessageRecord.id;
|
||||
}
|
||||
notifyResult(Cr.NS_OK);
|
||||
};
|
||||
txn.onabort = function onabort(event) {
|
||||
|
@ -1489,99 +1514,172 @@ MobileMessageDatabaseService.prototype = {
|
|||
let messageStore = stores[0];
|
||||
let participantStore = stores[1];
|
||||
let threadStore = stores[2];
|
||||
|
||||
self.findThreadRecordByParticipants(threadStore, participantStore,
|
||||
aAddresses, true,
|
||||
function (threadRecord,
|
||||
participantIds) {
|
||||
if (!participantIds) {
|
||||
notifyResult(Cr.NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
let insertMessageRecord = function (threadId) {
|
||||
// Setup threadId & threadIdIndex.
|
||||
aMessageRecord.threadId = threadId;
|
||||
aMessageRecord.threadIdIndex = [threadId, timestamp];
|
||||
// Setup participantIdsIndex.
|
||||
aMessageRecord.participantIdsIndex = [];
|
||||
for each (let id in participantIds) {
|
||||
aMessageRecord.participantIdsIndex.push([id, timestamp]);
|
||||
}
|
||||
|
||||
if (!isOverriding) {
|
||||
// Really add to message store.
|
||||
messageStore.put(aMessageRecord);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're going to override an old message, we need to update the
|
||||
// info of the original thread containing the overridden message.
|
||||
// To get the original thread ID and read status of the overridden
|
||||
// message record, we need to retrieve it before overriding it.
|
||||
messageStore.get(aMessageRecord.id).onsuccess = function(event) {
|
||||
let oldMessageRecord = event.target.result;
|
||||
messageStore.put(aMessageRecord);
|
||||
if (oldMessageRecord) {
|
||||
self.updateThreadByMessageChange(messageStore,
|
||||
threadStore,
|
||||
oldMessageRecord.threadId,
|
||||
aMessageRecord.id,
|
||||
oldMessageRecord.read);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let timestamp = aMessageRecord.timestamp;
|
||||
if (threadRecord) {
|
||||
let needsUpdate = false;
|
||||
|
||||
if (threadRecord.lastTimestamp <= timestamp) {
|
||||
let lastMessageSubject;
|
||||
if (aMessageRecord.type == "mms") {
|
||||
lastMessageSubject = aMessageRecord.headers.subject;
|
||||
}
|
||||
threadRecord.lastMessageSubject = lastMessageSubject || null;
|
||||
threadRecord.lastTimestamp = timestamp;
|
||||
threadRecord.body = aMessageRecord.body;
|
||||
threadRecord.lastMessageId = aMessageRecord.id;
|
||||
threadRecord.lastMessageType = aMessageRecord.type;
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (!aMessageRecord.read) {
|
||||
threadRecord.unreadCount++;
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
threadStore.put(threadRecord);
|
||||
}
|
||||
|
||||
insertMessageRecord(threadRecord.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let lastMessageSubject;
|
||||
if (aMessageRecord.type == "mms") {
|
||||
lastMessageSubject = aMessageRecord.headers.subject;
|
||||
}
|
||||
threadStore.add({participantIds: participantIds,
|
||||
participantAddresses: aAddresses,
|
||||
lastMessageId: aMessageRecord.id,
|
||||
lastTimestamp: timestamp,
|
||||
lastMessageSubject: lastMessageSubject || null,
|
||||
body: aMessageRecord.body,
|
||||
unreadCount: aMessageRecord.read ? 0 : 1,
|
||||
lastMessageType: aMessageRecord.type})
|
||||
.onsuccess = function (event) {
|
||||
let threadId = event.target.result;
|
||||
insertMessageRecord(threadId);
|
||||
};
|
||||
});
|
||||
self.replaceShortMessageOnSave(txn, messageStore, participantStore,
|
||||
threadStore, aMessageRecord, aAddresses);
|
||||
}, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]);
|
||||
// We return the key that we expect to store in the db
|
||||
return aMessageRecord.id;
|
||||
},
|
||||
|
||||
replaceShortMessageOnSave:
|
||||
function replaceShortMessageOnSave(aTransaction, aMessageStore,
|
||||
aParticipantStore, aThreadStore,
|
||||
aMessageRecord, aAddresses) {
|
||||
if (aMessageRecord.type != "sms" ||
|
||||
aMessageRecord.delivery != DELIVERY_RECEIVED ||
|
||||
!(aMessageRecord.pid >= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 &&
|
||||
aMessageRecord.pid <= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7)) {
|
||||
this.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
|
||||
aThreadStore, aMessageRecord, aAddresses);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3GPP TS 23.040 subclause 9.2.3.9 "TP-Protocol-Identifier (TP-PID)":
|
||||
//
|
||||
// ... the MS shall check the originating address and replace any
|
||||
// existing stored message having the same Protocol Identifier code
|
||||
// and originating address with the new short message and other
|
||||
// parameter values. If there is no message to be replaced, the MS
|
||||
// shall store the message in the normal way. ... it is recommended
|
||||
// that the SC address should not be checked by the MS."
|
||||
let self = this;
|
||||
this.findParticipantRecordByAddress(aParticipantStore,
|
||||
aMessageRecord.sender, false,
|
||||
function(participantRecord) {
|
||||
if (!participantRecord) {
|
||||
self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
|
||||
aThreadStore, aMessageRecord, aAddresses);
|
||||
return;
|
||||
}
|
||||
|
||||
let participantId = participantRecord.id;
|
||||
let range = IDBKeyRange.bound([participantId, 0], [participantId, ""]);
|
||||
let request = aMessageStore.index("participantIds").openCursor(range);
|
||||
request.onsuccess = function onsuccess(event) {
|
||||
let cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
|
||||
aThreadStore, aMessageRecord, aAddresses);
|
||||
return;
|
||||
}
|
||||
|
||||
// A message record with same participantId found.
|
||||
// Verify matching criteria.
|
||||
let foundMessageRecord = cursor.value;
|
||||
if (foundMessageRecord.type != "sms" ||
|
||||
foundMessageRecord.sender != aMessageRecord.sender ||
|
||||
foundMessageRecord.pid != aMessageRecord.pid) {
|
||||
cursor.continue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Match! Now replace that found message record with current one.
|
||||
aMessageRecord.id = foundMessageRecord.id;
|
||||
self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
|
||||
aThreadStore, aMessageRecord, aAddresses);
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
realSaveRecord: function realSaveRecord(aTransaction, aMessageStore,
|
||||
aParticipantStore, aThreadStore,
|
||||
aMessageRecord, aAddresses) {
|
||||
let self = this;
|
||||
this.findThreadRecordByParticipants(aThreadStore, aParticipantStore,
|
||||
aAddresses, true,
|
||||
function(threadRecord, participantIds) {
|
||||
if (!participantIds) {
|
||||
aTransaction.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
let isOverriding = (aMessageRecord.id !== undefined);
|
||||
if (!isOverriding) {
|
||||
// |self.lastMessageId| is only updated in |txn.oncomplete|.
|
||||
aMessageRecord.id = self.lastMessageId + 1;
|
||||
}
|
||||
|
||||
let timestamp = aMessageRecord.timestamp;
|
||||
let insertMessageRecord = function(threadId) {
|
||||
// Setup threadId & threadIdIndex.
|
||||
aMessageRecord.threadId = threadId;
|
||||
aMessageRecord.threadIdIndex = [threadId, timestamp];
|
||||
// Setup participantIdsIndex.
|
||||
aMessageRecord.participantIdsIndex = [];
|
||||
for each (let id in participantIds) {
|
||||
aMessageRecord.participantIdsIndex.push([id, timestamp]);
|
||||
}
|
||||
|
||||
if (!isOverriding) {
|
||||
// Really add to message store.
|
||||
aMessageStore.put(aMessageRecord);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're going to override an old message, we need to update the
|
||||
// info of the original thread containing the overridden message.
|
||||
// To get the original thread ID and read status of the overridden
|
||||
// message record, we need to retrieve it before overriding it.
|
||||
aMessageStore.get(aMessageRecord.id).onsuccess = function(event) {
|
||||
let oldMessageRecord = event.target.result;
|
||||
aMessageStore.put(aMessageRecord);
|
||||
if (oldMessageRecord) {
|
||||
self.updateThreadByMessageChange(aMessageStore,
|
||||
aThreadStore,
|
||||
oldMessageRecord.threadId,
|
||||
aMessageRecord.id,
|
||||
oldMessageRecord.read);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if (threadRecord) {
|
||||
let needsUpdate = false;
|
||||
|
||||
if (threadRecord.lastTimestamp <= timestamp) {
|
||||
let lastMessageSubject;
|
||||
if (aMessageRecord.type == "mms") {
|
||||
lastMessageSubject = aMessageRecord.headers.subject;
|
||||
}
|
||||
threadRecord.lastMessageSubject = lastMessageSubject || null;
|
||||
threadRecord.lastTimestamp = timestamp;
|
||||
threadRecord.body = aMessageRecord.body;
|
||||
threadRecord.lastMessageId = aMessageRecord.id;
|
||||
threadRecord.lastMessageType = aMessageRecord.type;
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (!aMessageRecord.read) {
|
||||
threadRecord.unreadCount++;
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
aThreadStore.put(threadRecord);
|
||||
}
|
||||
|
||||
insertMessageRecord(threadRecord.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let lastMessageSubject;
|
||||
if (aMessageRecord.type == "mms") {
|
||||
lastMessageSubject = aMessageRecord.headers.subject;
|
||||
}
|
||||
|
||||
threadRecord = {
|
||||
participantIds: participantIds,
|
||||
participantAddresses: aAddresses,
|
||||
lastMessageId: aMessageRecord.id,
|
||||
lastTimestamp: timestamp,
|
||||
lastMessageSubject: lastMessageSubject || null,
|
||||
body: aMessageRecord.body,
|
||||
unreadCount: aMessageRecord.read ? 0 : 1,
|
||||
lastMessageType: aMessageRecord.type,
|
||||
};
|
||||
aThreadStore.add(threadRecord).onsuccess = function(event) {
|
||||
let threadId = event.target.result;
|
||||
insertMessageRecord(threadId);
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
forEachMatchedMmsDeliveryInfo:
|
||||
|
@ -1882,6 +1980,10 @@ MobileMessageDatabaseService.prototype = {
|
|||
aMessage.delivery = DELIVERY_RECEIVED;
|
||||
aMessage.deliveryStatus = DELIVERY_STATUS_SUCCESS;
|
||||
|
||||
if (aMessage.pid == undefined) {
|
||||
aMessage.pid = RIL.PDU_PID_DEFAULT;
|
||||
}
|
||||
|
||||
// If |deliveryTimestamp| is not specified, use 0 as default.
|
||||
if (aMessage.deliveryTimestamp == undefined) {
|
||||
aMessage.deliveryTimestamp = 0;
|
||||
|
@ -1889,7 +1991,7 @@ MobileMessageDatabaseService.prototype = {
|
|||
}
|
||||
aMessage.deliveryIndex = [aMessage.delivery, timestamp];
|
||||
|
||||
return this.saveRecord(aMessage, threadParticipants, aCallback);
|
||||
this.saveRecord(aMessage, threadParticipants, aCallback);
|
||||
},
|
||||
|
||||
saveSendingMessage: function saveSendingMessage(aMessage, aCallback) {
|
||||
|
@ -1951,7 +2053,7 @@ MobileMessageDatabaseService.prototype = {
|
|||
} else if (aMessage.type == "mms") {
|
||||
addresses = aMessage.receivers;
|
||||
}
|
||||
return this.saveRecord(aMessage, addresses, aCallback);
|
||||
this.saveRecord(aMessage, addresses, aCallback);
|
||||
},
|
||||
|
||||
setMessageDeliveryByMessageId: function setMessageDeliveryByMessageId(
|
||||
|
|
|
@ -196,7 +196,7 @@ function getThreadById(aThreadId) {
|
|||
return getAllThreads()
|
||||
.then(function(aThreads) {
|
||||
for (let thread of aThreads) {
|
||||
if (thread.id == aThreadId) {
|
||||
if (thread.id === aThreadId) {
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
|
@ -267,6 +267,70 @@ function deleteAllMessages() {
|
|||
return getAllMessages().then(deleteMessages);
|
||||
}
|
||||
|
||||
let pendingEmulatorCmdCount = 0;
|
||||
|
||||
/* Send emulator command with safe guard.
|
||||
*
|
||||
* We should only call |finish()| after all emulator command transactions
|
||||
* end, so here comes with the pending counter. Resolve when the emulator
|
||||
* gives positive response, and reject otherwise.
|
||||
*
|
||||
* Forfill params:
|
||||
* result -- an array of emulator response lines.
|
||||
*
|
||||
* Reject params:
|
||||
* result -- an array of emulator response lines.
|
||||
*
|
||||
* @return A deferred promise.
|
||||
*/
|
||||
function runEmulatorCmdSafe(aCommand) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
++pendingEmulatorCmdCount;
|
||||
runEmulatorCmd(aCommand, function(aResult) {
|
||||
--pendingEmulatorCmdCount;
|
||||
|
||||
ok(true, "Emulator response: " + JSON.stringify(aResult));
|
||||
if (Array.isArray(aResult) && aResult[0] === "OK") {
|
||||
deferred.resolve(aResult);
|
||||
} else {
|
||||
deferred.reject(aResult);
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/* Send simple text SMS to emulator.
|
||||
*
|
||||
* Forfill params:
|
||||
* result -- an array of emulator response lines.
|
||||
*
|
||||
* Reject params:
|
||||
* result -- an array of emulator response lines.
|
||||
*
|
||||
* @return A deferred promise.
|
||||
*/
|
||||
function sendTextSmsToEmulator(aFrom, aText) {
|
||||
let command = "sms send " + aFrom + " " + aText;
|
||||
return runEmulatorCmdSafe(command);
|
||||
}
|
||||
|
||||
/* Send raw SMS TPDU to emulator.
|
||||
*
|
||||
* Forfill params:
|
||||
* result -- an array of emulator response lines.
|
||||
*
|
||||
* Reject params:
|
||||
* result -- an array of emulator response lines.
|
||||
*
|
||||
* @return A deferred promise.
|
||||
*/
|
||||
function sendRawSmsToEmulator(aPdu) {
|
||||
let command = "sms pdu " + aPdu;
|
||||
return runEmulatorCmdSafe(command);
|
||||
}
|
||||
|
||||
/* Create a new array of id attribute of input messages.
|
||||
*
|
||||
* @param aMessages an array of {Sms,Mms}Message instances.
|
||||
|
@ -284,11 +348,15 @@ function messagesToIds(aMessages) {
|
|||
/* Flush permission settings and call |finish()|.
|
||||
*/
|
||||
function cleanUp() {
|
||||
SpecialPowers.flushPermissions(function() {
|
||||
// Use ok here so that we have at least one test run.
|
||||
ok(true, "permissions flushed");
|
||||
waitFor(function() {
|
||||
SpecialPowers.flushPermissions(function() {
|
||||
// Use ok here so that we have at least one test run.
|
||||
ok(true, "permissions flushed");
|
||||
|
||||
finish();
|
||||
finish();
|
||||
});
|
||||
}, function() {
|
||||
return pendingEmulatorCmdCount === 0;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -297,5 +365,8 @@ function startTestCommon(aTestCaseMain) {
|
|||
.then(deleteAllMessages)
|
||||
.then(aTestCaseMain)
|
||||
.then(deleteAllMessages)
|
||||
.then(cleanUp, cleanUp);
|
||||
.then(cleanUp, function() {
|
||||
ok(false, 'promise rejects during test.');
|
||||
cleanUp();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -40,3 +40,4 @@ qemu = true
|
|||
[test_dsds_default_service_id.js]
|
||||
[test_thread_subject.js]
|
||||
[test_mmdb_setmessagedeliverybyid_sms.js]
|
||||
[test_replace_short_message_type.js]
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
MARIONETTE_TIMEOUT = 60000;
|
||||
MARIONETTE_HEAD_JS = 'head.js';
|
||||
|
||||
const PDU_SMSC_NONE = "00"; // no SMSC Address
|
||||
|
||||
const PDU_FIRST_OCTET = "00"; // RP:no, UDHI:no, SRI:no, MMS:no, MTI:SMS-DELIVER
|
||||
|
||||
const PDU_SENDER_0 = "0A912143658709"; // +1234567890
|
||||
const PDU_SENDER_1 = "0A912143658719"; // +1234567891
|
||||
const SENDER_0 = "+1234567890";
|
||||
const SENDER_1 = "+1234567891";
|
||||
|
||||
const PDU_PID_NORMAL = "00";
|
||||
const PDU_PIDS = ["00", "41", "42", "43", "44", "45", "46", "47"];
|
||||
|
||||
const PDU_DCS_NORMAL = "00";
|
||||
|
||||
const PDU_TIMESTAMP = "00101000000000"; // 2000/01/01
|
||||
|
||||
const PDU_UDL = "01";
|
||||
const PDU_UD_A = "41"; // "A"
|
||||
const PDU_UD_B = "42"; // "B"
|
||||
const BODY_A = "A";
|
||||
const BODY_B = "B";
|
||||
|
||||
function buildPdu(aSender, aPid, aBody) {
|
||||
return PDU_SMSC_NONE + PDU_FIRST_OCTET + aSender + aPid + PDU_DCS_NORMAL +
|
||||
PDU_TIMESTAMP + PDU_UDL + aBody;
|
||||
}
|
||||
|
||||
let receivedMessage;
|
||||
function consumeReceivedMessage() {
|
||||
let message = receivedMessage;
|
||||
receivedMessage = null;
|
||||
return message;
|
||||
}
|
||||
|
||||
function waitForIncomingMessage() {
|
||||
if (receivedMessage) {
|
||||
return consumeReceivedMessage();
|
||||
}
|
||||
|
||||
let deferred = Promise.defer();
|
||||
|
||||
waitFor(function() {
|
||||
deferred.resolve(consumeReceivedMessage());
|
||||
}, function() {
|
||||
return receivedMessage != null;
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function sendRawSmsAndWait(aPdu) {
|
||||
sendRawSmsToEmulator(aPdu);
|
||||
return waitForIncomingMessage();
|
||||
}
|
||||
|
||||
function verifyReplacing(aVictim, aSender, aPid, aCompare) {
|
||||
let readableSender = aSender === PDU_SENDER_0 ? SENDER_0 : SENDER_1;
|
||||
log(" Checking ('" + readableSender + "', '" + aPid + "', '" + BODY_B + "')");
|
||||
|
||||
let pdu = buildPdu(aSender, aPid, PDU_UD_B);
|
||||
ok(true, "Sending " + pdu);
|
||||
|
||||
return sendRawSmsAndWait(pdu)
|
||||
.then(function(aReceivedMessage) {
|
||||
is(aReceivedMessage.sender, readableSender, "SmsMessage sender");
|
||||
is(aReceivedMessage.body, BODY_B, "SmsMessage body");
|
||||
|
||||
aCompare(aReceivedMessage.id, aVictim.id, "SmsMessage id");
|
||||
});
|
||||
}
|
||||
|
||||
function verifyNotReplaced(aVictim, aSender, aPid) {
|
||||
return verifyReplacing(aVictim, aSender, aPid, isnot);
|
||||
}
|
||||
|
||||
function verifyReplaced(aVictim, aSender, aPid) {
|
||||
return verifyReplacing(aVictim, aSender, aPid, is);
|
||||
}
|
||||
|
||||
function testPid(aPid) {
|
||||
log("Test message PID '" + aPid + "'");
|
||||
|
||||
return sendRawSmsAndWait(buildPdu(PDU_SENDER_0, aPid, PDU_UD_A))
|
||||
.then(function(aReceivedMessage) {
|
||||
let promise = Promise.resolve();
|
||||
|
||||
for (let pid of PDU_PIDS) {
|
||||
let verify = (aPid !== PDU_PID_NORMAL && pid === aPid)
|
||||
? verifyReplaced : verifyNotReplaced;
|
||||
promise =
|
||||
promise.then(verify.bind(null, aReceivedMessage, PDU_SENDER_0, pid))
|
||||
.then(verifyNotReplaced.bind(null, aReceivedMessage,
|
||||
PDU_SENDER_1, pid));
|
||||
}
|
||||
|
||||
return promise;
|
||||
});
|
||||
}
|
||||
|
||||
startTestCommon(function testCaseMain() {
|
||||
manager.onreceived = function(event) {
|
||||
receivedMessage = event.message;
|
||||
};
|
||||
|
||||
let promise = Promise.resolve();
|
||||
for (let pid of PDU_PIDS) {
|
||||
promise = promise.then(testPid.bind(null, pid))
|
||||
.then(deleteAllMessages);
|
||||
}
|
||||
|
||||
// Clear |manager.onreceived| handler.
|
||||
return promise.then(function() {
|
||||
manager.onreceived = null;
|
||||
});
|
||||
});
|
|
@ -1935,7 +1935,7 @@ RadioInterface.prototype = {
|
|||
// At this point we could send a message to content to notify the user
|
||||
// that storing an incoming SMS failed, most likely due to a full disk.
|
||||
if (DEBUG) {
|
||||
this.debug("Could not store SMS " + message.id + ", error code " + rv);
|
||||
this.debug("Could not store SMS, error code " + rv);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1945,8 +1945,8 @@ RadioInterface.prototype = {
|
|||
}.bind(this);
|
||||
|
||||
if (message.messageClass != RIL.GECKO_SMS_MESSAGE_CLASSES[RIL.PDU_DCS_MSG_CLASS_0]) {
|
||||
message.id = gMobileMessageDatabaseService.saveReceivedMessage(message,
|
||||
notifyReceived);
|
||||
gMobileMessageDatabaseService.saveReceivedMessage(message,
|
||||
notifyReceived);
|
||||
} else {
|
||||
message.id = -1;
|
||||
message.threadId = 0;
|
||||
|
@ -3146,8 +3146,8 @@ RadioInterface.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
let id = gMobileMessageDatabaseService.saveSendingMessage(
|
||||
sendingMessage, notifyResult);
|
||||
gMobileMessageDatabaseService.saveSendingMessage(sendingMessage,
|
||||
notifyResult);
|
||||
},
|
||||
|
||||
registerDataCallCallback: function registerDataCallCallback(callback) {
|
||||
|
|
|
@ -584,6 +584,7 @@ struct ParamTraits<mozilla::layers::FrameMetrics>
|
|||
WriteParam(aMsg, aParam.mDevPixelsPerCSSPixel);
|
||||
WriteParam(aMsg, aParam.mMayHaveTouchListeners);
|
||||
WriteParam(aMsg, aParam.mPresShellId);
|
||||
WriteParam(aMsg, aParam.mIsRoot);
|
||||
}
|
||||
|
||||
static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
|
||||
|
@ -600,7 +601,8 @@ struct ParamTraits<mozilla::layers::FrameMetrics>
|
|||
ReadParam(aMsg, aIter, &aResult->mZoom) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mDevPixelsPerCSSPixel) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mMayHaveTouchListeners) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mPresShellId));
|
||||
ReadParam(aMsg, aIter, &aResult->mPresShellId) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mIsRoot));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ public:
|
|||
// We use IDs to identify frames across processes.
|
||||
typedef uint64_t ViewID;
|
||||
static const ViewID NULL_SCROLL_ID; // This container layer does not scroll.
|
||||
static const ViewID ROOT_SCROLL_ID; // This is the root scroll frame.
|
||||
static const ViewID START_SCROLL_ID; // This is the ID that scrolling subframes
|
||||
// will begin at.
|
||||
|
||||
|
@ -54,6 +53,7 @@ public:
|
|||
, mDevPixelsPerCSSPixel(1)
|
||||
, mMayHaveTouchListeners(false)
|
||||
, mPresShellId(-1)
|
||||
, mIsRoot(false)
|
||||
{}
|
||||
|
||||
// Default copy ctor and operator= are fine
|
||||
|
@ -71,7 +71,8 @@ public:
|
|||
mCumulativeResolution == aOther.mCumulativeResolution &&
|
||||
mDevPixelsPerCSSPixel == aOther.mDevPixelsPerCSSPixel &&
|
||||
mMayHaveTouchListeners == aOther.mMayHaveTouchListeners &&
|
||||
mPresShellId == aOther.mPresShellId;
|
||||
mPresShellId == aOther.mPresShellId &&
|
||||
mIsRoot == aOther.mIsRoot;
|
||||
}
|
||||
bool operator!=(const FrameMetrics& aOther) const
|
||||
{
|
||||
|
@ -88,7 +89,7 @@ public:
|
|||
|
||||
bool IsRootScrollable() const
|
||||
{
|
||||
return mScrollId == ROOT_SCROLL_ID;
|
||||
return mIsRoot;
|
||||
}
|
||||
|
||||
bool IsScrollable() const
|
||||
|
@ -206,8 +207,7 @@ public:
|
|||
// not any parents, regardless of parent transforms.
|
||||
CSSPoint mScrollOffset;
|
||||
|
||||
// A unique ID assigned to each scrollable frame (unless this is
|
||||
// ROOT_SCROLL_ID, in which case it is not unique).
|
||||
// A unique ID assigned to each scrollable frame.
|
||||
ViewID mScrollId;
|
||||
|
||||
// The scrollable bounds of a frame. This is determined by reflow.
|
||||
|
@ -253,6 +253,9 @@ public:
|
|||
bool mMayHaveTouchListeners;
|
||||
|
||||
uint32_t mPresShellId;
|
||||
|
||||
// Whether or not this is the root scroll frame for the root content document.
|
||||
bool mIsRoot;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -293,16 +296,6 @@ struct ScrollableLayerGuid {
|
|||
MOZ_COUNT_CTOR(ScrollableLayerGuid);
|
||||
}
|
||||
|
||||
ScrollableLayerGuid(uint64_t aLayersId)
|
||||
: mLayersId(aLayersId)
|
||||
, mPresShellId(0)
|
||||
, mScrollId(FrameMetrics::ROOT_SCROLL_ID)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ScrollableLayerGuid);
|
||||
// TODO: get rid of this constructor once all callers know their
|
||||
// presShellId and scrollId
|
||||
}
|
||||
|
||||
~ScrollableLayerGuid()
|
||||
{
|
||||
MOZ_COUNT_DTOR(ScrollableLayerGuid);
|
||||
|
|
|
@ -37,8 +37,7 @@ using namespace mozilla::gfx;
|
|||
|
||||
typedef FrameMetrics::ViewID ViewID;
|
||||
const ViewID FrameMetrics::NULL_SCROLL_ID = 0;
|
||||
const ViewID FrameMetrics::ROOT_SCROLL_ID = 1;
|
||||
const ViewID FrameMetrics::START_SCROLL_ID = 2;
|
||||
const ViewID FrameMetrics::START_SCROLL_ID = 1;
|
||||
|
||||
uint8_t gLayerManagerLayerBuilder;
|
||||
|
||||
|
|
|
@ -1527,19 +1527,19 @@ void AsyncPanZoomController::SendAsyncScrollEvent() {
|
|||
return;
|
||||
}
|
||||
|
||||
FrameMetrics::ViewID scrollId;
|
||||
bool isRoot;
|
||||
CSSRect contentRect;
|
||||
CSSSize scrollableSize;
|
||||
{
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
|
||||
scrollId = mFrameMetrics.mScrollId;
|
||||
isRoot = mFrameMetrics.mIsRoot;
|
||||
scrollableSize = mFrameMetrics.mScrollableRect.Size();
|
||||
contentRect = mFrameMetrics.CalculateCompositedRectInCssPixels();
|
||||
contentRect.MoveTo(mCurrentAsyncScrollOffset);
|
||||
}
|
||||
|
||||
controller->SendAsyncScrollDOMEvent(scrollId, contentRect, scrollableSize);
|
||||
controller->SendAsyncScrollDOMEvent(isRoot, contentRect, scrollableSize);
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::UpdateScrollOffset(const CSSPoint& aScrollOffset)
|
||||
|
|
|
@ -54,7 +54,7 @@ public:
|
|||
* |aContentRect| is in CSS pixels, relative to the current cssPage.
|
||||
* |aScrollableSize| is the current content width/height in CSS pixels.
|
||||
*/
|
||||
virtual void SendAsyncScrollDOMEvent(FrameMetrics::ViewID aScrollId,
|
||||
virtual void SendAsyncScrollDOMEvent(bool aIsRoot,
|
||||
const CSSRect &aContentRect,
|
||||
const CSSSize &aScrollableSize) = 0;
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ public:
|
|||
MOCK_METHOD1(HandleDoubleTap, void(const CSSIntPoint&));
|
||||
MOCK_METHOD1(HandleSingleTap, void(const CSSIntPoint&));
|
||||
MOCK_METHOD1(HandleLongTap, void(const CSSIntPoint&));
|
||||
MOCK_METHOD3(SendAsyncScrollDOMEvent, void(FrameMetrics::ViewID aScrollId, const CSSRect &aContentRect, const CSSSize &aScrollableSize));
|
||||
MOCK_METHOD3(SendAsyncScrollDOMEvent, void(bool aIsRoot, const CSSRect &aContentRect, const CSSSize &aScrollableSize));
|
||||
MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs));
|
||||
};
|
||||
|
||||
|
@ -273,10 +273,10 @@ TEST(AsyncPanZoomController, ComplexTransform) {
|
|||
metrics.mResolution = ParentLayerToLayerScale(2);
|
||||
metrics.mZoom = CSSToScreenScale(6);
|
||||
metrics.mDevPixelsPerCSSPixel = CSSToLayoutDeviceScale(3);
|
||||
metrics.mScrollId = FrameMetrics::ROOT_SCROLL_ID;
|
||||
metrics.mScrollId = FrameMetrics::START_SCROLL_ID;
|
||||
|
||||
FrameMetrics childMetrics = metrics;
|
||||
childMetrics.mScrollId = FrameMetrics::START_SCROLL_ID;
|
||||
childMetrics.mScrollId = FrameMetrics::START_SCROLL_ID + 1;
|
||||
|
||||
layers[0]->AsContainerLayer()->SetFrameMetrics(metrics);
|
||||
layers[1]->AsContainerLayer()->SetFrameMetrics(childMetrics);
|
||||
|
@ -521,7 +521,7 @@ TEST(APZCTreeManager, HitTesting1) {
|
|||
EXPECT_EQ(gfx3DMatrix(), transformToGecko);
|
||||
|
||||
// Now we have a root APZC that will match the page
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::ROOT_SCROLL_ID);
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
|
||||
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko);
|
||||
EXPECT_EQ(root->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
|
||||
|
@ -530,7 +530,7 @@ TEST(APZCTreeManager, HitTesting1) {
|
|||
EXPECT_EQ(gfxPoint(15, 15), transformToGecko.Transform(gfxPoint(15, 15)));
|
||||
|
||||
// Now we have a sub APZC with a better fit
|
||||
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID);
|
||||
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1);
|
||||
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0);
|
||||
EXPECT_NE(root->AsContainerLayer()->GetAsyncPanZoomController(), layers[3]->AsContainerLayer()->GetAsyncPanZoomController());
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko);
|
||||
|
@ -542,7 +542,7 @@ TEST(APZCTreeManager, HitTesting1) {
|
|||
// Now test hit testing when we have two scrollable layers
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko);
|
||||
EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
|
||||
SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1);
|
||||
SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2);
|
||||
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0);
|
||||
hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko);
|
||||
EXPECT_EQ(layers[4]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
|
||||
|
@ -592,9 +592,9 @@ TEST(APZCTreeManager, HitTesting2) {
|
|||
layers[2]->SetBaseTransform(transform);
|
||||
|
||||
// Make some other layers scrollable.
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::ROOT_SCROLL_ID, CSSRect(0, 0, 200, 200));
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 80, 80));
|
||||
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80));
|
||||
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
|
||||
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80));
|
||||
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 80, 80));
|
||||
|
||||
manager->UpdatePanZoomControllerTree(nullptr, root, false, 0);
|
||||
|
||||
|
|
|
@ -593,6 +593,7 @@ static void RecordFrameMetrics(nsIFrame* aForFrame,
|
|||
nsRect* aDisplayPort,
|
||||
nsRect* aCriticalDisplayPort,
|
||||
ViewID aScrollId,
|
||||
bool aIsRoot,
|
||||
const ContainerLayerParameters& aContainerParameters) {
|
||||
nsPresContext* presContext = aForFrame->PresContext();
|
||||
int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
|
||||
|
@ -629,6 +630,7 @@ static void RecordFrameMetrics(nsIFrame* aForFrame,
|
|||
}
|
||||
|
||||
metrics.mScrollId = aScrollId;
|
||||
metrics.mIsRoot = aIsRoot;
|
||||
|
||||
// Only the root scrollable frame for a given presShell should pick up
|
||||
// the presShell's resolution. All the other frames are 1.0.
|
||||
|
@ -1150,8 +1152,8 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder,
|
|||
root->SetPostScale(1.0f/containerParameters.mXScale,
|
||||
1.0f/containerParameters.mYScale);
|
||||
|
||||
ViewID id = presContext->IsRootContentDocument() ? FrameMetrics::ROOT_SCROLL_ID
|
||||
: FrameMetrics::NULL_SCROLL_ID;
|
||||
ViewID id = FrameMetrics::NULL_SCROLL_ID;
|
||||
bool isRoot = presContext->IsRootContentDocument();
|
||||
|
||||
nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
|
||||
nsRect displayport, criticalDisplayport;
|
||||
|
@ -1164,11 +1166,9 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder,
|
|||
usingCriticalDisplayport =
|
||||
nsLayoutUtils::GetCriticalDisplayPort(content, &criticalDisplayport);
|
||||
|
||||
if (id == FrameMetrics::ROOT_SCROLL_ID) {
|
||||
// Record the mapping between the root scroll frame's content and
|
||||
// ROOT_SCROLL_ID so that users of nsLayoutUtils::FindIDFor() and
|
||||
// nsLayoutUtils::FindContentFor() don't have to special-case the root.
|
||||
nsLayoutUtils::FindOrCreateIDFor(content, true);
|
||||
// If this is the root content document, we want it to have a scroll id.
|
||||
if (isRoot) {
|
||||
id = nsLayoutUtils::FindOrCreateIDFor(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1180,7 +1180,7 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder,
|
|||
root, mVisibleRect, viewport,
|
||||
(usingDisplayport ? &displayport : nullptr),
|
||||
(usingCriticalDisplayport ? &criticalDisplayport : nullptr),
|
||||
id, containerParameters);
|
||||
id, isRoot, containerParameters);
|
||||
if (usingDisplayport &&
|
||||
!(root->GetContentFlags() & Layer::CONTENT_OPAQUE)) {
|
||||
// See bug 693938, attachment 567017
|
||||
|
@ -3450,7 +3450,7 @@ nsDisplayScrollLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
|
|||
mVisibleRect, viewport,
|
||||
(usingDisplayport ? &displayport : nullptr),
|
||||
(usingCriticalDisplayport ? &criticalDisplayport : nullptr),
|
||||
scrollId, aContainerParameters);
|
||||
scrollId, false, aContainerParameters);
|
||||
|
||||
return layer.forget();
|
||||
}
|
||||
|
|
|
@ -528,22 +528,12 @@ nsLayoutUtils::FindIDFor(const nsIContent* aContent, ViewID* aOutViewId)
|
|||
}
|
||||
|
||||
ViewID
|
||||
nsLayoutUtils::FindOrCreateIDFor(nsIContent* aContent, bool aRoot)
|
||||
nsLayoutUtils::FindOrCreateIDFor(nsIContent* aContent)
|
||||
{
|
||||
ViewID scrollId;
|
||||
|
||||
if (!FindIDFor(aContent, &scrollId)) {
|
||||
scrollId = aRoot ? FrameMetrics::ROOT_SCROLL_ID : sScrollIdCounter++;
|
||||
if (aRoot) {
|
||||
// We are possibly replacing the old ROOT_SCROLL_ID content with a new one, so
|
||||
// we should clear the property on the old content if there is one. Otherwise when
|
||||
// that content is destroyed it will clear its property list and clobber ROOT_SCROLL_ID.
|
||||
nsIContent* oldRoot;
|
||||
bool oldExists = GetContentMap().Get(scrollId, &oldRoot);
|
||||
if (oldExists) {
|
||||
oldRoot->DeleteProperty(nsGkAtoms::RemoteId);
|
||||
}
|
||||
}
|
||||
scrollId = sScrollIdCounter++;
|
||||
aContent->SetProperty(nsGkAtoms::RemoteId, new ViewID(scrollId),
|
||||
DestroyViewID);
|
||||
GetContentMap().Put(scrollId, aContent);
|
||||
|
|
|
@ -95,10 +95,9 @@ public:
|
|||
|
||||
/**
|
||||
* Finds previously assigned or generates a unique ViewID for the given
|
||||
* content element. If aRoot is true, the special ID
|
||||
* FrameMetrics::ROOT_SCROLL_ID is used.
|
||||
* content element.
|
||||
*/
|
||||
static ViewID FindOrCreateIDFor(nsIContent* aContent, bool aRoot = false);
|
||||
static ViewID FindOrCreateIDFor(nsIContent* aContent);
|
||||
|
||||
/**
|
||||
* Find content for given ID.
|
||||
|
|
|
@ -109,7 +109,7 @@ AssertInTopLevelChromeDoc(ContainerLayer* aContainer,
|
|||
"Expected frame to be in top-level chrome document");
|
||||
}
|
||||
|
||||
// Return view for given ID in aArray, nullptr if not found.
|
||||
// Return view for given ID in aMap, nullptr if not found.
|
||||
static nsContentView*
|
||||
FindViewForId(const ViewMap& aMap, ViewID aId)
|
||||
{
|
||||
|
@ -117,6 +117,19 @@ FindViewForId(const ViewMap& aMap, ViewID aId)
|
|||
return iter != aMap.end() ? iter->second : nullptr;
|
||||
}
|
||||
|
||||
// Return the root content view in aMap, nullptr if not found.
|
||||
static nsContentView*
|
||||
FindRootView(const ViewMap& aMap)
|
||||
{
|
||||
for (ViewMap::const_iterator iter = aMap.begin(), end = aMap.end();
|
||||
iter != end;
|
||||
++iter) {
|
||||
if (iter->second->IsRoot())
|
||||
return iter->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static const FrameMetrics*
|
||||
GetFrameMetrics(Layer* aLayer)
|
||||
{
|
||||
|
@ -403,7 +416,7 @@ BuildViewMap(ViewMap& oldContentViews, ViewMap& newContentViews,
|
|||
config.mScrollOffset = nsPoint(
|
||||
NSIntPixelsToAppUnits(metrics.mScrollOffset.x, auPerCSSPixel) * aXScale,
|
||||
NSIntPixelsToAppUnits(metrics.mScrollOffset.y, auPerCSSPixel) * aYScale);
|
||||
view = new nsContentView(aFrameLoader, scrollId, config);
|
||||
view = new nsContentView(aFrameLoader, scrollId, metrics.mIsRoot, config);
|
||||
view->mParentScaleX = aAccConfigXScale;
|
||||
view->mParentScaleY = aAccConfigYScale;
|
||||
}
|
||||
|
@ -554,7 +567,7 @@ public:
|
|||
|
||||
void ClearRenderFrame() { mRenderFrame = nullptr; }
|
||||
|
||||
virtual void SendAsyncScrollDOMEvent(FrameMetrics::ViewID aScrollId,
|
||||
virtual void SendAsyncScrollDOMEvent(bool aIsRoot,
|
||||
const CSSRect& aContentRect,
|
||||
const CSSSize& aContentSize) MOZ_OVERRIDE
|
||||
{
|
||||
|
@ -563,10 +576,10 @@ public:
|
|||
FROM_HERE,
|
||||
NewRunnableMethod(this,
|
||||
&RemoteContentController::SendAsyncScrollDOMEvent,
|
||||
aScrollId, aContentRect, aContentSize));
|
||||
aIsRoot, aContentRect, aContentSize));
|
||||
return;
|
||||
}
|
||||
if (mRenderFrame && aScrollId == FrameMetrics::ROOT_SCROLL_ID) {
|
||||
if (mRenderFrame && aIsRoot) {
|
||||
TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
|
||||
BrowserElementParent::DispatchAsyncScrollEvent(browser, aContentRect,
|
||||
aContentSize);
|
||||
|
@ -627,9 +640,6 @@ RenderFrameParent::RenderFrameParent(nsFrameLoader* aFrameLoader,
|
|||
, mFrameLoaderDestroyed(false)
|
||||
, mBackgroundColor(gfxRGBA(1, 1, 1))
|
||||
{
|
||||
mContentViews[FrameMetrics::ROOT_SCROLL_ID] =
|
||||
new nsContentView(aFrameLoader, FrameMetrics::ROOT_SCROLL_ID);
|
||||
|
||||
*aId = 0;
|
||||
|
||||
nsRefPtr<LayerManager> lm = GetFrom(mFrameLoader);
|
||||
|
@ -640,6 +650,13 @@ RenderFrameParent::RenderFrameParent(nsFrameLoader* aFrameLoader,
|
|||
*aTextureFactoryIdentifier = TextureFactoryIdentifier();
|
||||
}
|
||||
|
||||
if (lm && lm->GetRoot() && lm->GetRoot()->AsContainerLayer()) {
|
||||
ViewID rootScrollId = lm->GetRoot()->AsContainerLayer()->GetFrameMetrics().mScrollId;
|
||||
if (rootScrollId != FrameMetrics::NULL_SCROLL_ID) {
|
||||
mContentViews[rootScrollId] = new nsContentView(aFrameLoader, rootScrollId, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (CompositorParent::CompositorLoop()) {
|
||||
// Our remote frame will push layers updates to the compositor,
|
||||
// and we'll keep an indirect reference to that tree.
|
||||
|
@ -695,6 +712,12 @@ RenderFrameParent::GetContentView(ViewID aId)
|
|||
return FindViewForId(mContentViews, aId);
|
||||
}
|
||||
|
||||
nsContentView*
|
||||
RenderFrameParent::GetRootContentView()
|
||||
{
|
||||
return FindRootView(mContentViews);
|
||||
}
|
||||
|
||||
void
|
||||
RenderFrameParent::ContentViewScaleChanged(nsContentView* aView)
|
||||
{
|
||||
|
@ -805,7 +828,7 @@ RenderFrameParent::BuildLayer(nsDisplayListBuilder* aBuilder,
|
|||
mContainer->SetClipRect(nullptr);
|
||||
|
||||
if (mFrameLoader->AsyncScrollEnabled()) {
|
||||
const nsContentView* view = GetContentView(FrameMetrics::ROOT_SCROLL_ID);
|
||||
const nsContentView* view = GetRootContentView();
|
||||
BuildBackgroundPatternFor(mContainer,
|
||||
shadowRoot,
|
||||
view->GetViewConfig(),
|
||||
|
@ -927,8 +950,9 @@ RenderFrameParent::BuildViewMap()
|
|||
// the content view map should only contain the root view and content
|
||||
// views that are present in the layer tree.
|
||||
if (newContentViews.empty()) {
|
||||
newContentViews[FrameMetrics::ROOT_SCROLL_ID] =
|
||||
FindViewForId(mContentViews, FrameMetrics::ROOT_SCROLL_ID);
|
||||
nsContentView* rootView = FindRootView(mContentViews);
|
||||
if (rootView)
|
||||
newContentViews[rootView->GetId()] = rootView;
|
||||
}
|
||||
|
||||
mContentViews = newContentViews;
|
||||
|
@ -1023,11 +1047,12 @@ RenderFrameParent::ContentReceivedTouch(const ScrollableLayerGuid& aGuid,
|
|||
void
|
||||
RenderFrameParent::UpdateZoomConstraints(uint32_t aPresShellId,
|
||||
ViewID aViewId,
|
||||
bool aIsRoot,
|
||||
bool aAllowZoom,
|
||||
const CSSToScreenScale& aMinZoom,
|
||||
const CSSToScreenScale& aMaxZoom)
|
||||
{
|
||||
if (mContentController && aViewId == FrameMetrics::ROOT_SCROLL_ID) {
|
||||
if (mContentController && aIsRoot) {
|
||||
mContentController->SaveZoomConstraints(aAllowZoom, aMinZoom, aMaxZoom);
|
||||
}
|
||||
if (GetApzcTreeManager()) {
|
||||
|
|
|
@ -68,10 +68,11 @@ public:
|
|||
void Destroy();
|
||||
|
||||
/**
|
||||
* Helper function for getting a non-owning reference to a scrollable.
|
||||
* Helper functions for getting a non-owning reference to a scrollable.
|
||||
* @param aId The ID of the frame.
|
||||
*/
|
||||
nsContentView* GetContentView(ViewID aId = FrameMetrics::ROOT_SCROLL_ID);
|
||||
nsContentView* GetContentView(ViewID aId);
|
||||
nsContentView* GetRootContentView();
|
||||
|
||||
void ContentViewScaleChanged(nsContentView* aView);
|
||||
|
||||
|
@ -117,6 +118,7 @@ public:
|
|||
|
||||
void UpdateZoomConstraints(uint32_t aPresShellId,
|
||||
ViewID aViewId,
|
||||
bool aIsRoot,
|
||||
bool aAllowZoom,
|
||||
const CSSToScreenScale& aMinZoom,
|
||||
const CSSToScreenScale& aMaxZoom);
|
||||
|
|
|
@ -1939,7 +1939,7 @@ AndroidBridge::HandleLongTap(const CSSIntPoint& aPoint)
|
|||
}
|
||||
|
||||
void
|
||||
AndroidBridge::SendAsyncScrollDOMEvent(mozilla::layers::FrameMetrics::ViewID aScrollId,
|
||||
AndroidBridge::SendAsyncScrollDOMEvent(bool aIsRoot,
|
||||
const CSSRect& aContentRect,
|
||||
const CSSSize& aScrollableSize)
|
||||
{
|
||||
|
|
|
@ -419,7 +419,7 @@ public:
|
|||
void HandleDoubleTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE;
|
||||
void HandleSingleTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE;
|
||||
void HandleLongTap(const CSSIntPoint& aPoint) MOZ_OVERRIDE;
|
||||
void SendAsyncScrollDOMEvent(mozilla::layers::FrameMetrics::ViewID aScrollId,
|
||||
void SendAsyncScrollDOMEvent(bool aIsRoot,
|
||||
const CSSRect& aContentRect,
|
||||
const CSSSize& aScrollableSize) MOZ_OVERRIDE;
|
||||
void PostDelayedTask(Task* aTask, int aDelayMs) MOZ_OVERRIDE;
|
||||
|
|
|
@ -850,7 +850,8 @@ Java_org_mozilla_gecko_gfx_NativePanZoomController_abortAnimation(JNIEnv* env, j
|
|||
{
|
||||
APZCTreeManager *controller = nsWindow::GetAPZCTreeManager();
|
||||
if (controller) {
|
||||
controller->CancelAnimation(ScrollableLayerGuid(nsWindow::RootLayerTreeId()));
|
||||
// TODO: Pass in correct values for presShellId and viewId.
|
||||
controller->CancelAnimation(ScrollableLayerGuid(nsWindow::RootLayerTreeId(), 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -918,7 +919,8 @@ Java_org_mozilla_gecko_gfx_NativePanZoomController_notifyDefaultActionPrevented(
|
|||
{
|
||||
APZCTreeManager *controller = nsWindow::GetAPZCTreeManager();
|
||||
if (controller) {
|
||||
controller->ContentReceivedTouch(ScrollableLayerGuid(nsWindow::RootLayerTreeId()), prevented);
|
||||
// TODO: Pass in correct values for presShellId and viewId.
|
||||
controller->ContentReceivedTouch(ScrollableLayerGuid(nsWindow::RootLayerTreeId(), 0, 0), prevented);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -947,7 +949,8 @@ Java_org_mozilla_gecko_gfx_NativePanZoomController_updateScrollOffset(JNIEnv* en
|
|||
{
|
||||
APZCTreeManager *controller = nsWindow::GetAPZCTreeManager();
|
||||
if (controller) {
|
||||
controller->UpdateScrollOffset(ScrollableLayerGuid(nsWindow::RootLayerTreeId()), CSSPoint(cssX, cssY));
|
||||
// TODO: Pass in correct values for presShellId and viewId.
|
||||
controller->UpdateScrollOffset(ScrollableLayerGuid(nsWindow::RootLayerTreeId(), 0, 0), CSSPoint(cssX, cssY));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -225,8 +225,12 @@ sendKeyEventWithMsg(uint32_t keyCode,
|
|||
bool isRepeat)
|
||||
{
|
||||
WidgetKeyboardEvent event(true, msg, nullptr);
|
||||
event.keyCode = keyCode;
|
||||
event.charCode = charCode;
|
||||
if (msg == NS_KEY_PRESS && charCode >= ' ') {
|
||||
event.charCode = charCode;
|
||||
} else {
|
||||
event.keyCode = keyCode;
|
||||
}
|
||||
event.isChar = !!event.charCode;
|
||||
event.mIsRepeat = isRepeat;
|
||||
event.mKeyNameIndex = keyNameIndex;
|
||||
event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_MOBILE;
|
||||
|
|
|
@ -299,7 +299,7 @@ APZController::HandleLongTap(const CSSIntPoint& aPoint)
|
|||
|
||||
// requests that we send a mozbrowserasyncscroll domevent. not in use.
|
||||
void
|
||||
APZController::SendAsyncScrollDOMEvent(FrameMetrics::ViewID aScrollId,
|
||||
APZController::SendAsyncScrollDOMEvent(bool aIsRoot,
|
||||
const CSSRect &aContentRect,
|
||||
const CSSSize &aScrollableSize)
|
||||
{
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
virtual void HandleDoubleTap(const mozilla::CSSIntPoint& aPoint);
|
||||
virtual void HandleSingleTap(const mozilla::CSSIntPoint& aPoint);
|
||||
virtual void HandleLongTap(const mozilla::CSSIntPoint& aPoint);
|
||||
virtual void SendAsyncScrollDOMEvent(FrameMetrics::ViewID aScrollId, const mozilla::CSSRect &aContentRect, const mozilla::CSSSize &aScrollableSize);
|
||||
virtual void SendAsyncScrollDOMEvent(bool aIsRoot, const mozilla::CSSRect &aContentRect, const mozilla::CSSSize &aScrollableSize);
|
||||
virtual void PostDelayedTask(Task* aTask, int aDelayMs);
|
||||
virtual void HandlePanBegin();
|
||||
virtual void HandlePanEnd();
|
||||
|
|
Загрузка…
Ссылка в новой задаче