Merge m-c to b2g-i

This commit is contained in:
Carsten "Tomcat" Book 2015-10-16 14:59:51 +02:00
commit e1c0a578b8
409 changed files with 9238 additions and 4394 deletions

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please # changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more. # don't change CLOBBER for WebIDL changes any more.
Bug 1182727 - Update the clang toolchain No bug - unknown something produced intermittent OS X build failures in libstagefright and jemalloc and malloc

View File

@ -586,9 +586,8 @@ setCaretOffsetCB(AtkText *aText, gint aOffset)
} }
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) { if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
if (proxy->SetCaretOffset(aOffset)) { proxy->SetCaretOffset(aOffset);
return TRUE; return TRUE;
}
} }
return FALSE; return FALSE;

View File

@ -390,13 +390,10 @@ DocAccessibleChild::RecvCaretOffset(const uint64_t& aID, int32_t* aOffset)
bool bool
DocAccessibleChild::RecvSetCaretOffset(const uint64_t& aID, DocAccessibleChild::RecvSetCaretOffset(const uint64_t& aID,
const int32_t& aOffset, const int32_t& aOffset)
bool* aRetVal)
{ {
HyperTextAccessible* acc = IdToHyperTextAccessible(aID); HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
*aRetVal = false;
if (acc && acc->IsTextRole() && acc->IsValidOffset(aOffset)) { if (acc && acc->IsTextRole() && acc->IsValidOffset(aOffset)) {
*aRetVal = true;
acc->SetCaretOffset(aOffset); acc->SetCaretOffset(aOffset);
} }
return true; return true;

View File

@ -103,8 +103,8 @@ public:
override; override;
virtual bool RecvCaretOffset(const uint64_t& aID, int32_t* aOffset) virtual bool RecvCaretOffset(const uint64_t& aID, int32_t* aOffset)
override; override;
virtual bool RecvSetCaretOffset(const uint64_t& aID, const int32_t& aOffset, virtual bool RecvSetCaretOffset(const uint64_t& aID, const int32_t& aOffset)
bool* aValid) override; override;
virtual bool RecvCharacterCount(const uint64_t& aID, int32_t* aCount) virtual bool RecvCharacterCount(const uint64_t& aID, int32_t* aCount)
override; override;

View File

@ -92,7 +92,7 @@ child:
// TextSubstring is getText in IDL. // TextSubstring is getText in IDL.
prio(high) sync CaretLineNumber(uint64_t aID) returns(int32_t aLineNumber); prio(high) sync CaretLineNumber(uint64_t aID) returns(int32_t aLineNumber);
prio(high) sync CaretOffset(uint64_t aID) returns(int32_t aOffset); prio(high) sync CaretOffset(uint64_t aID) returns(int32_t aOffset);
prio(high) sync SetCaretOffset(uint64_t aID, int32_t aOffset) returns (bool aValid); async SetCaretOffset(uint64_t aID, int32_t aOffset);
prio(high) sync CharacterCount(uint64_t aID) returns(int32_t aCount); prio(high) sync CharacterCount(uint64_t aID) returns(int32_t aCount);
prio(high) sync SelectionCount(uint64_t aID) returns(int32_t aCount); prio(high) sync SelectionCount(uint64_t aID) returns(int32_t aCount);
prio(high) sync TextSubstring(uint64_t aID, int32_t aStartOffset, int32_t prio(high) sync TextSubstring(uint64_t aID, int32_t aStartOffset, int32_t

View File

@ -216,12 +216,10 @@ ProxyAccessible::CaretOffset()
return offset; return offset;
} }
bool void
ProxyAccessible::SetCaretOffset(int32_t aOffset) ProxyAccessible::SetCaretOffset(int32_t aOffset)
{ {
bool valid = false; unused << mDoc->SendSetCaretOffset(mID, aOffset);
unused << mDoc->SendSetCaretOffset(mID, aOffset, &valid);
return valid;
} }
int32_t int32_t

View File

@ -132,7 +132,7 @@ public:
int32_t CaretLineNumber(); int32_t CaretLineNumber();
int32_t CaretOffset(); int32_t CaretOffset();
bool SetCaretOffset(int32_t aOffset); void SetCaretOffset(int32_t aOffset);
int32_t CharacterCount(); int32_t CharacterCount();
int32_t SelectionCount(); int32_t SelectionCount();

View File

@ -73,7 +73,20 @@ var OutputGenerator = {
[addOutput(node) for // jshint ignore:line [addOutput(node) for // jshint ignore:line
(node of aContext.subtreeGenerator(false, ignoreSubtree))]; // jshint ignore:line (node of aContext.subtreeGenerator(false, ignoreSubtree))]; // jshint ignore:line
addOutput(aContext.accessible); addOutput(aContext.accessible);
// If there are any documents in new ancestry, find a first one and place
// it in the beginning of the utterance.
let doc, docIndex = contextStart.findIndex(
ancestor => ancestor.role === Roles.DOCUMENT);
if (docIndex > -1) {
doc = contextStart.splice(docIndex, 1)[0];
}
contextStart.reverse().forEach(addOutput); contextStart.reverse().forEach(addOutput);
if (doc) {
output.unshift.apply(output, self.genForObject(doc, aContext));
}
} }
return output; return output;

View File

@ -32,13 +32,13 @@
// Simple traversal forward // Simple traversal forward
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange( new ExpectedCursorChange(
['Phone status bar', 'Traversal Rule test document'], ['Traversal Rule test document', 'Phone status bar'],
{ focused: 'body' })], { focused: 'body' })],
[ContentMessages.simpleMovePrevious, new ExpectedNoMove()], [ContentMessages.simpleMovePrevious, new ExpectedNoMove()],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
[ContentMessages.simpleMoveNext, new ExpectedCursorChange( [ContentMessages.simpleMoveNext, new ExpectedCursorChange(
['wow', {'string': 'headingLevel', 'args': [1]} ,'such app'], ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}],
{ focused: 'iframe' })], { focused: 'iframe' })],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['many option', {'string': 'stateNotChecked'}, new ExpectedCursorChange(['many option', {'string': 'stateNotChecked'},
@ -79,7 +79,7 @@
[ContentMessages.simpleMovePrevious, [ContentMessages.simpleMovePrevious,
new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])], new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
[ContentMessages.simpleMovePrevious, [ContentMessages.simpleMovePrevious,
new ExpectedCursorChange(['much range', '6', {'string': 'slider'}, 'such app'])], new ExpectedCursorChange(['such app', 'much range', '6', {'string': 'slider'}])],
[ContentMessages.moveOrAdjustDown(), new ExpectedValueChange('5')], [ContentMessages.moveOrAdjustDown(), new ExpectedValueChange('5')],
[ContentMessages.androidScrollForward(), new ExpectedValueChange('6')], [ContentMessages.androidScrollForward(), new ExpectedValueChange('6')],
[ContentMessages.androidScrollBackward(), new ExpectedValueChange('5')], [ContentMessages.androidScrollBackward(), new ExpectedValueChange('5')],
@ -106,7 +106,7 @@
// fails. Bug 972035. // fails. Bug 972035.
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange( new ExpectedCursorChange(
['wow', {'string': 'headingLevel', 'args': [1]}, 'such app'])], ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
// Move from an inner frame to the last element in the parent doc // Move from an inner frame to the last element in the parent doc
[ContentMessages.simpleMoveLast, [ContentMessages.simpleMoveLast,
new ExpectedCursorChange( new ExpectedCursorChange(
@ -115,13 +115,13 @@
[ContentMessages.clearCursor, 'AccessFu:CursorCleared'], [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])], new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
[ContentMessages.moveOrAdjustDown('FormElement'), [ContentMessages.moveOrAdjustDown('FormElement'),
new ExpectedCursorChange(['Back', {"string": "pushbutton"}])], new ExpectedCursorChange(['Back', {"string": "pushbutton"}])],
[ContentMessages.moveOrAdjustDown('FormElement'), [ContentMessages.moveOrAdjustDown('FormElement'),
new ExpectedCursorChange(['many option', {'string': 'stateNotChecked'}, new ExpectedCursorChange(['such app', 'many option', {'string': 'stateNotChecked'},
{'string': 'checkbutton'}, {'string': 'listStart'}, {'string': 'checkbutton'}, {'string': 'listStart'},
{'string': 'list'}, {'string': 'listItemsCount', 'count': 1}, 'such app'])], {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])],
[ContentMessages.moveOrAdjustDown('FormElement'), [ContentMessages.moveOrAdjustDown('FormElement'),
new ExpectedCursorChange(['much range', '5', {'string': 'slider'}])], new ExpectedCursorChange(['much range', '5', {'string': 'slider'}])],
// Calling AdjustOrMove should adjust the range. // Calling AdjustOrMove should adjust the range.
@ -143,11 +143,11 @@
// Moving to the absolute first item from an embedded document // Moving to the absolute first item from an embedded document
// fails. Bug 972035. // fails. Bug 972035.
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])], new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['wow', {'string': 'headingLevel', 'args': [1]}, 'such app'])], new ExpectedCursorChange(['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
[ContentMessages.simpleMoveNext, new ExpectedCursorChange( [ContentMessages.simpleMoveNext, new ExpectedCursorChange(
['many option', {'string': 'stateNotChecked'}, ['many option', {'string': 'stateNotChecked'},
{'string': 'checkbutton'}, {'string': 'listStart'}, {'string': 'checkbutton'}, {'string': 'listStart'},
@ -160,7 +160,7 @@
// Current virtual cursor's position's name changes // Current virtual cursor's position's name changes
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])], new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
[ContentMessages.focusSelector('button#fruit', false), [ContentMessages.focusSelector('button#fruit', false),
new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])], new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
[doc.defaultView.renameFruit, new ExpectedNameChange('banana')], [doc.defaultView.renameFruit, new ExpectedNameChange('banana')],
@ -177,7 +177,7 @@
// Move cursor with focus in outside document // Move cursor with focus in outside document
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])], new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
[ContentMessages.focusSelector('button#home', false), [ContentMessages.focusSelector('button#home', false),
new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])], new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
@ -188,11 +188,11 @@
// Set focus on element outside of embedded frame while // Set focus on element outside of embedded frame while
// cursor is in frame // cursor is in frame
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])], new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['wow', {'string': 'headingLevel', 'args': [1]}, 'such app'])], new ExpectedCursorChange(['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
[ContentMessages.focusSelector('button#home', false), [ContentMessages.focusSelector('button#home', false),
new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])], new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
@ -206,12 +206,12 @@
// aria-hidden element that the virtual cursor is positioned on // aria-hidden element that the virtual cursor is positioned on
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])], new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
[doc.defaultView.ariaHideBack, [doc.defaultView.ariaHideBack,
new ExpectedCursorChange( new ExpectedCursorChange(
["wow", {"string": "headingLevel","args": [1]}, "such app"])], ["such app", "wow", {"string": "headingLevel","args": [1]}])],
// Changing aria-hidden attribute twice and making sure that the event // Changing aria-hidden attribute twice and making sure that the event
// is fired only once when the actual change happens. // is fired only once when the actual change happens.
[doc.defaultView.ariaHideBack], [doc.defaultView.ariaHideBack],
@ -222,11 +222,11 @@
// aria-hidden on the iframe that has the vc. // aria-hidden on the iframe that has the vc.
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])], new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['wow', {'string': 'headingLevel', 'args': [1]}, 'such app'])], new ExpectedCursorChange(['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
[doc.defaultView.ariaHideIframe, [doc.defaultView.ariaHideIframe,
new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])], new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
[doc.defaultView.ariaShowIframe], [doc.defaultView.ariaShowIframe],
@ -234,37 +234,39 @@
// aria-hidden element and auto Move // aria-hidden element and auto Move
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])], new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
[doc.defaultView.ariaHideBack], [doc.defaultView.ariaHideBack],
[ContentMessages.focusSelector('button#back', false), [ContentMessages.focusSelector('button#back', false),
// Must not speak Back button as it is aria-hidden // Must not speak Back button as it is aria-hidden
new ExpectedCursorChange( new ExpectedCursorChange(
["wow", {"string": "headingLevel","args": [1]}, "such app"])], ["such app", "wow", {"string": "headingLevel","args": [1]}])],
[doc.defaultView.ariaShowBack], [doc.defaultView.ariaShowBack],
[ContentMessages.focusSelector('button#back', true), null], [ContentMessages.focusSelector('button#back', true), null],
[ContentMessages.clearCursor, 'AccessFu:CursorCleared'], [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
// Open dialog in outer doc, while cursor is also in outer doc // Open dialog in outer doc, while cursor is also in outer doc
[ContentMessages.simpleMoveLast, [ContentMessages.simpleMoveLast,
new ExpectedCursorChange(['mover'])], new ExpectedCursorChange(['Traversal Rule test document', 'mover',
'medium', {'string': 'slider'}])],
[doc.defaultView.showAlert, [doc.defaultView.showAlert,
new ExpectedCursorChange(['This is an alert!', new ExpectedCursorChange(['This is an alert!',
{'string': 'headingLevel', 'args': [1]}, {'string': 'headingLevel', 'args': [1]},
{'string': 'dialog'}])], {'string': 'dialog'}])],
[doc.defaultView.hideAlert, [doc.defaultView.hideAlert,
new ExpectedCursorChange(['mover'])], new ExpectedCursorChange(['Traversal Rule test document', 'mover',
'medium', {'string': 'slider'}])],
[ContentMessages.clearCursor, 'AccessFu:CursorCleared'], [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
// Open dialog in outer doc, while cursor is in inner frame // Open dialog in outer doc, while cursor is in inner frame
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])], new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange( new ExpectedCursorChange(
['wow', {'string': 'headingLevel', 'args': [1]}, 'such app'])], ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
[doc.defaultView.showAlert, new ExpectedCursorChange(['This is an alert!', [doc.defaultView.showAlert, new ExpectedCursorChange(['This is an alert!',
{'string': 'headingLevel', 'args': [1]}, {'string': 'headingLevel', 'args': [1]},
{'string': 'dialog'}])], {'string': 'dialog'}])],
@ -276,13 +278,13 @@
[ContentMessages.activateCurrent(), [ContentMessages.activateCurrent(),
new ExpectedClickAction(), new ExpectedClickAction(),
new ExpectedCursorChange( new ExpectedCursorChange(
['wow', {'string': 'headingLevel', 'args': [1]}, 'such app'])], ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
[ContentMessages.clearCursor, 'AccessFu:CursorCleared'], [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
// Open dialog, then focus on something when closing // Open dialog, then focus on something when closing
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])], new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
[doc.defaultView.showAlert, [doc.defaultView.showAlert,
new ExpectedCursorChange(['This is an alert!', new ExpectedCursorChange(['This is an alert!',
{'string': 'headingLevel', 'args': [1]}, {'string': 'dialog'}])], {'string': 'headingLevel', 'args': [1]}, {'string': 'dialog'}])],
@ -290,8 +292,8 @@
[function hideAlertAndFocusHomeButton() { [function hideAlertAndFocusHomeButton() {
doc.defaultView.hideAlert(); doc.defaultView.hideAlert();
doc.querySelector('button#home').focus(); doc.querySelector('button#home').focus();
}, new ExpectedCursorChange(['Home', {'string': 'pushbutton'}, }, new ExpectedCursorChange(['Traversal Rule test document',
'Traversal Rule test document'])], 'Home', {'string': 'pushbutton'}])],
[ContentMessages.simpleMoveNext, [ContentMessages.simpleMoveNext,
new ExpectedCursorChange(['banana', {'string': 'pushbutton'}])] new ExpectedCursorChange(['banana', {'string': 'pushbutton'}])]
[ContentMessages.simpleMoveNext, new ExpectedNoMove()] [ContentMessages.simpleMoveNext, new ExpectedNoMove()]

View File

@ -31,9 +31,9 @@
// Read-only text tests // Read-only text tests
[ContentMessages.simpleMoveFirst, [ContentMessages.simpleMoveFirst,
new ExpectedCursorChange( new ExpectedCursorChange(
['These are my awards, Mother. From Army. The seal is for ' + ['Text content test document', 'These are my awards, Mother. ' +
'marksmanship, and the gorilla is for sand racing.', 'From Army. The seal is for marksmanship, and the gorilla is ' +
'Text content test document'])], 'for sand racing.'])],
[ContentMessages.moveNextBy('word'), [ContentMessages.moveNextBy('word'),
new ExpectedCursorTextChange('These', 0, 5)], new ExpectedCursorTextChange('These', 0, 5)],
[ContentMessages.moveNextBy('word'), [ContentMessages.moveNextBy('word'),

View File

@ -434,7 +434,8 @@ ia2AccessibleText::setCaretOffset(long aOffset)
A11Y_TRYBLOCK_BEGIN A11Y_TRYBLOCK_BEGIN
if (ProxyAccessible* proxy = HyperTextProxyFor(this)) { if (ProxyAccessible* proxy = HyperTextProxyFor(this)) {
return proxy->SetCaretOffset(aOffset) ? S_OK : E_INVALIDARG; proxy->SetCaretOffset(aOffset);
return S_OK;
} }
HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this); HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);

View File

@ -224,22 +224,27 @@ XULDropmarkerAccessible::DropmarkerOpen(bool aToggleOpen) const
{ {
bool isOpen = false; bool isOpen = false;
nsCOMPtr<nsIDOMXULButtonElement> parentButtonElement = nsIContent* parent = mContent->GetFlattenedTreeParent();
do_QueryInterface(mContent->GetFlattenedTreeParent());
while (parent) {
nsCOMPtr<nsIDOMXULButtonElement> parentButtonElement =
do_QueryInterface(parent);
if (parentButtonElement) {
parentButtonElement->GetOpen(&isOpen);
if (aToggleOpen)
parentButtonElement->SetOpen(!isOpen);
return isOpen;
}
if (parentButtonElement) {
parentButtonElement->GetOpen(&isOpen);
if (aToggleOpen)
parentButtonElement->SetOpen(!isOpen);
}
else {
nsCOMPtr<nsIDOMXULMenuListElement> parentMenuListElement = nsCOMPtr<nsIDOMXULMenuListElement> parentMenuListElement =
do_QueryInterface(parentButtonElement); do_QueryInterface(parent);
if (parentMenuListElement) { if (parentMenuListElement) {
parentMenuListElement->GetOpen(&isOpen); parentMenuListElement->GetOpen(&isOpen);
if (aToggleOpen) if (aToggleOpen)
parentMenuListElement->SetOpen(!isOpen); parentMenuListElement->SetOpen(!isOpen);
return isOpen;
} }
parent = parent->GetFlattenedTreeParent();
} }
return isOpen; return isOpen;

View File

@ -698,6 +698,10 @@ var settingsToObserve = {
prefName: 'dom.sms.maxReadAheadEntries', prefName: 'dom.sms.maxReadAheadEntries',
defaultValue: 7 defaultValue: 7
}, },
'services.sync.enabled': {
defaultValue: false,
notifyChange: true
},
'ui.touch.radius.leftmm': { 'ui.touch.radius.leftmm': {
resetToPref: true resetToPref: true
}, },
@ -717,6 +721,18 @@ var settingsToObserve = {
'wap.UAProf.url': '' 'wap.UAProf.url': ''
}; };
function settingObserver(setPref, prefName, setting) {
return value => {
setPref(prefName, value);
if (setting.notifyChange) {
SystemAppProxy._sendCustomEvent('mozPrefChromeEvent', {
prefName: prefName,
value: value
});
}
};
}
for (let key in settingsToObserve) { for (let key in settingsToObserve) {
let setting = settingsToObserve[key]; let setting = settingsToObserve[key];
@ -766,7 +782,6 @@ for (let key in settingsToObserve) {
break; break;
} }
SettingsListener.observe(key, defaultValue, function(value) { SettingsListener.observe(key, defaultValue,
setPref(prefName, value); settingObserver(setPref, prefName, setting));
});
}; };

View File

@ -156,7 +156,8 @@ this.FxAccountsMgmtService = {
case "signIn": case "signIn":
case "signUp": case "signUp":
case "refreshAuthentication": case "refreshAuthentication":
FxAccountsManager[data.method](data.email, data.password).then( FxAccountsManager[data.method](data.email, data.password,
data.fetchKeys).then(
user => { user => {
self._onFulfill(msg.id, user); self._onFulfill(msg.id, user);
}, },

View File

@ -602,10 +602,7 @@ menupopup[emptyplacesresult="true"] > .hide-if-empty-places-result {
menuitem.spell-suggestion { menuitem.spell-suggestion {
font-weight: bold; font-weight: bold;
} }
/* apply Fitts' law to the notification bar's close button */
window:not([sizemode="normal"]) .notification-inner {
-moz-border-end-width: 0 !important;
}
/* Hide extension toolbars that neglected to set the proper class */ /* Hide extension toolbars that neglected to set the proper class */
window[chromehidden~="location"][chromehidden~="toolbar"] toolbar:not(.chromeclass-menubar), window[chromehidden~="location"][chromehidden~="toolbar"] toolbar:not(.chromeclass-menubar),
window[chromehidden~="toolbar"] toolbar:not(#nav-bar):not(#TabsToolbar):not(#print-preview-toolbar):not(.chromeclass-menubar) { window[chromehidden~="toolbar"] toolbar:not(#nav-bar):not(#TabsToolbar):not(#print-preview-toolbar):not(.chromeclass-menubar) {

View File

@ -2345,6 +2345,15 @@ function BrowserViewSourceOfDocument(aArgsOrDocument) {
let browserWindow = RecentWindow.getMostRecentBrowserWindow(); let browserWindow = RecentWindow.getMostRecentBrowserWindow();
tabBrowser = browserWindow.gBrowser; tabBrowser = browserWindow.gBrowser;
} }
// Some internal URLs (such as specific chrome: and about: URLs that are
// not yet remote ready) cannot be loaded in a remote browser. View
// source in tab expects the new view source browser's remoteness to match
// that of the original URL, so disable remoteness if necessary for this
// URL.
let contentProcess = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
let forceNotRemote =
gMultiProcessBrowser &&
!E10SUtils.canLoadURIInProcess(args.URL, contentProcess);
// `viewSourceInBrowser` will load the source content from the page // `viewSourceInBrowser` will load the source content from the page
// descriptor for the tab (when possible) or fallback to the network if // descriptor for the tab (when possible) or fallback to the network if
// that fails. Either way, the view source module will manage the tab's // that fails. Either way, the view source module will manage the tab's
@ -2352,7 +2361,8 @@ function BrowserViewSourceOfDocument(aArgsOrDocument) {
// requests. // requests.
let tab = tabBrowser.loadOneTab("about:blank", { let tab = tabBrowser.loadOneTab("about:blank", {
relatedToCurrent: true, relatedToCurrent: true,
inBackground: false inBackground: false,
forceNotRemote,
}); });
args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab); args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
top.gViewSourceUtils.viewSourceInBrowser(args); top.gViewSourceUtils.viewSourceInBrowser(args);

View File

@ -711,27 +711,50 @@
onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'" onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'"
onblur="setTimeout(() => { document.getElementById('identity-box').style.MozUserFocus = ''; }, 0);"> onblur="setTimeout(() => { document.getElementById('identity-box').style.MozUserFocus = ''; }, 0);">
<box id="notification-popup-box" hidden="true" align="center"> <box id="notification-popup-box" hidden="true" align="center">
<image id="default-notification-icon" class="notification-anchor-icon" role="button"/> <image id="default-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.defaultNotificationAnchor.label;"/>
<!-- NB: the identity-notification-icon is used for persona-based auth and preffed
off by default. It hasn't been updated for some time and liable to being
removed.-->
<image id="identity-notification-icon" class="notification-anchor-icon" role="button"/> <image id="identity-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="geo-notification-icon" class="notification-anchor-icon" role="button"/> <image id="geo-notification-icon" class="notification-anchor-icon" role="button"
<image id="addons-notification-icon" class="notification-anchor-icon" role="button"/> aria-label="&urlbar.geolocationNotificationAnchor.label;"/>
<image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/> <image id="addons-notification-icon" class="notification-anchor-icon" role="button"
<image id="login-fill-notification-icon" class="notification-anchor-icon" role="button"/> aria-label="&urlbar.addonsNotificationAnchor.label;"/>
<image id="password-notification-icon" class="notification-anchor-icon" role="button"/> <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"
<image id="webapps-notification-icon" class="notification-anchor-icon" role="button"/> aria-label="&urlbar.indexedDBNotificationAnchor.label;"/>
<image id="plugins-notification-icon" class="notification-anchor-icon" role="button"/> <image id="login-fill-notification-icon" class="notification-anchor-icon" role="button"
<image id="web-notifications-notification-icon" class="notification-anchor-icon" role="button"/> aria-label="&urlbar.loginFillNotificationAnchor.label;"/>
<image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"/> <image id="password-notification-icon" class="notification-anchor-icon" role="button"
<image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"/> aria-label="&urlbar.passwordNotificationAnchor.label;"/>
<image id="webRTC-shareMicrophone-notification-icon" class="notification-anchor-icon" role="button"/> <image id="webapps-notification-icon" class="notification-anchor-icon" role="button"
<image id="webRTC-sharingMicrophone-notification-icon" class="notification-anchor-icon" role="button"/> aria-label="&urlbar.webappsNotificationAnchor.label;"/>
<image id="webRTC-shareScreen-notification-icon" class="notification-anchor-icon" role="button"/> <image id="plugins-notification-icon" class="notification-anchor-icon" role="button"
<image id="webRTC-sharingScreen-notification-icon" class="notification-anchor-icon" role="button"/> aria-label="&urlbar.pluginsNotificationAnchor.label;"/>
<image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"/> <image id="web-notifications-notification-icon" class="notification-anchor-icon" role="button"
<image id="servicesInstall-notification-icon" class="notification-anchor-icon" role="button"/> aria-label="&urlbar.webNotsNotificationAnchor.label;"/>
<image id="translate-notification-icon" class="notification-anchor-icon" role="button"/> <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"
<image id="translated-notification-icon" class="notification-anchor-icon" role="button"/> aria-label="&urlbar.webRTCShareDevicesNotificationAnchor.label;"/>
<image id="eme-notification-icon" class="notification-anchor-icon" role="button"/> <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.webRTCSharingDevicesNotificationAnchor.label;"/>
<image id="webRTC-shareMicrophone-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.webRTCShareMicrophoneNotificationAnchor.label;"/>
<image id="webRTC-sharingMicrophone-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.webRTCSharingMicrophoneNotificationAnchor.label;"/>
<image id="webRTC-shareScreen-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.webRTCShareScreenNotificationAnchor.label;"/>
<image id="webRTC-sharingScreen-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.webRTCSharingScreenNotificationAnchor.label;"/>
<image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.pointerLockNotificationAnchor.label;"/>
<image id="servicesInstall-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.servicesNotificationAnchor.label;"/>
<image id="translate-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.translateNotificationAnchor.label;"/>
<image id="translated-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.translatedNotificationAnchor.label;"/>
<image id="eme-notification-icon" class="notification-anchor-icon" role="button"
aria-label="&urlbar.emeNotificationAnchor.label;"/>
</box> </box>
<!-- Use onclick instead of normal popup= syntax since the popup <!-- Use onclick instead of normal popup= syntax since the popup
code fires onmousedown, and hence eats our favicon drag events. code fires onmousedown, and hence eats our favicon drag events.
@ -739,6 +762,7 @@
has focus, otherwise pressing F6 focuses it instead of the location bar --> has focus, otherwise pressing F6 focuses it instead of the location bar -->
<box id="identity-box" role="button" <box id="identity-box" role="button"
align="center" align="center"
aria-label="&urlbar.viewSiteInfo.label;"
onclick="gIdentityHandler.handleIdentityButtonEvent(event);" onclick="gIdentityHandler.handleIdentityButtonEvent(event);"
onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);" onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);"
ondragstart="gIdentityHandler.onDragStart(event);"> ondragstart="gIdentityHandler.onDragStart(event);">
@ -775,13 +799,16 @@
<toolbarbutton id="urlbar-go-button" <toolbarbutton id="urlbar-go-button"
class="chromeclass-toolbar-additional" class="chromeclass-toolbar-additional"
onclick="gURLBar.handleCommand(event);" onclick="gURLBar.handleCommand(event);"
aria-label="&goEndCap.tooltip;"
tooltiptext="&goEndCap.tooltip;"/> tooltiptext="&goEndCap.tooltip;"/>
<toolbarbutton id="urlbar-reload-button" <toolbarbutton id="urlbar-reload-button"
class="chromeclass-toolbar-additional" class="chromeclass-toolbar-additional"
command="Browser:ReloadOrDuplicate" command="Browser:ReloadOrDuplicate"
onclick="checkForMiddleClick(this, event);" onclick="checkForMiddleClick(this, event);"
aria-label="&reloadButton.tooltip;"
tooltiptext="&reloadButton.tooltip;"/> tooltiptext="&reloadButton.tooltip;"/>
<toolbarbutton id="urlbar-stop-button" <toolbarbutton id="urlbar-stop-button"
aria-label="&stopButton.tooltip;"
class="chromeclass-toolbar-additional" class="chromeclass-toolbar-additional"
command="Browser:Stop" command="Browser:Stop"
tooltiptext="&stopButton.tooltip;"/> tooltiptext="&stopButton.tooltip;"/>
@ -1092,7 +1119,7 @@
<splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/> <splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
<vbox id="appcontent" flex="1"> <vbox id="appcontent" flex="1">
<notificationbox id="high-priority-global-notificationbox"/> <notificationbox id="high-priority-global-notificationbox" notificationside="top"/>
<tabbrowser id="content" <tabbrowser id="content"
flex="1" contenttooltip="aHTMLTooltip" flex="1" contenttooltip="aHTMLTooltip"
tabcontainer="tabbrowser-tabs" tabcontainer="tabbrowser-tabs"
@ -1175,7 +1202,7 @@
</html:div> </html:div>
<vbox id="browser-bottombox" layer="true"> <vbox id="browser-bottombox" layer="true">
<notificationbox id="global-notificationbox"/> <notificationbox id="global-notificationbox" notificationside="bottom"/>
<toolbar id="developer-toolbar" <toolbar id="developer-toolbar"
hidden="true"> hidden="true">
#ifdef XP_MACOSX #ifdef XP_MACOSX

View File

@ -20,7 +20,7 @@
flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown" flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();"> onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
<xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer"> <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
<xul:notificationbox flex="1"> <xul:notificationbox flex="1" notificationside="top">
<xul:hbox flex="1" class="browserSidebarContainer"> <xul:hbox flex="1" class="browserSidebarContainer">
<xul:vbox flex="1" class="browserContainer"> <xul:vbox flex="1" class="browserContainer">
<xul:stack flex="1" class="browserStack" anonid="browserStack"> <xul:stack flex="1" class="browserStack" anonid="browserStack">
@ -1743,6 +1743,7 @@
var notificationbox = document.createElementNS(NS_XUL, var notificationbox = document.createElementNS(NS_XUL,
"notificationbox"); "notificationbox");
notificationbox.setAttribute("flex", "1"); notificationbox.setAttribute("flex", "1");
notificationbox.setAttribute("notificationside", "top");
notificationbox.appendChild(browserSidebarContainer); notificationbox.appendChild(browserSidebarContainer);
// Prevent the superfluous initial load of a blank document // Prevent the superfluous initial load of a blank document

View File

@ -41,6 +41,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
</xul:hbox> </xul:hbox>
<xul:dropmarker anonid="historydropmarker" <xul:dropmarker anonid="historydropmarker"
class="autocomplete-history-dropmarker urlbar-history-dropmarker" class="autocomplete-history-dropmarker urlbar-history-dropmarker"
aria-label="&urlbar.toggleAutocomplete.label;"
allowevents="true" allowevents="true"
xbl:inherits="open,enablehistory,parentfocused=focused"/> xbl:inherits="open,enablehistory,parentfocused=focused"/>
<children includes="hbox"/> <children includes="hbox"/>

View File

@ -4,8 +4,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pref("startup.homepage_override_url",""); pref("startup.homepage_override_url", "");
pref("startup.homepage_welcome_url","https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/"); pref("startup.homepage_welcome_url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/");
pref("startup.homepage_welcome_url.additional", "");
// The time interval between checks for a new version (in seconds) // The time interval between checks for a new version (in seconds)
pref("app.update.interval", 28800); // 8 hours pref("app.update.interval", 28800); // 8 hours
// The time interval between the downloading of mar file chunks in the // The time interval between the downloading of mar file chunks in the

View File

@ -4,6 +4,7 @@
pref("startup.homepage_override_url", "https://www.mozilla.org/projects/firefox/%VERSION%/whatsnew/?oldversion=%OLD_VERSION%"); pref("startup.homepage_override_url", "https://www.mozilla.org/projects/firefox/%VERSION%/whatsnew/?oldversion=%OLD_VERSION%");
pref("startup.homepage_welcome_url", "https://www.mozilla.org/projects/firefox/%VERSION%/firstrun/"); pref("startup.homepage_welcome_url", "https://www.mozilla.org/projects/firefox/%VERSION%/firstrun/");
pref("startup.homepage_welcome_url.additional", "");
// The time interval between checks for a new version (in seconds) // The time interval between checks for a new version (in seconds)
pref("app.update.interval", 7200); // 2 hours pref("app.update.interval", 7200); // 2 hours
// The time interval between the downloading of mar file chunks in the // The time interval between the downloading of mar file chunks in the

View File

@ -2,8 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pref("startup.homepage_override_url",""); pref("startup.homepage_override_url", "");
pref("startup.homepage_welcome_url","https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/"); pref("startup.homepage_welcome_url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/");
pref("startup.homepage_welcome_url.additional", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/learnmore/");
// Interval: Time between checks for a new version (in seconds) // Interval: Time between checks for a new version (in seconds)
pref("app.update.interval", 43200); // 12 hours pref("app.update.interval", 43200); // 12 hours
// The time interval between the downloading of mar file chunks in the // The time interval between the downloading of mar file chunks in the

View File

@ -2,8 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
pref("startup.homepage_override_url",""); pref("startup.homepage_override_url", "");
pref("startup.homepage_welcome_url",""); pref("startup.homepage_welcome_url", "");
pref("startup.homepage_welcome_url.additional", "");
// The time interval between checks for a new version (in seconds) // The time interval between checks for a new version (in seconds)
pref("app.update.interval", 86400); // 24 hours pref("app.update.interval", 86400); // 24 hours
// The time interval between the downloading of mar file chunks in the // The time interval between the downloading of mar file chunks in the

View File

@ -55,8 +55,7 @@ loop.shared.actions = (function() {
// Optional Items. There are other optional items typically sent // Optional Items. There are other optional items typically sent
// around with this action. They are for the setup of calls and rooms and // around with this action. They are for the setup of calls and rooms and
// depend on the type. See LoopCalls and LoopRooms for the details of this // depend on the type. See LoopRooms for the details of this data.
// data.
}), }),
/** /**
@ -75,56 +74,6 @@ loop.shared.actions = (function() {
WindowUnload: Action.define("windowUnload", { WindowUnload: Action.define("windowUnload", {
}), }),
/**
* Fetch a new room url from the server, intended to be sent over email when
* a contact can't be reached.
*/
FetchRoomEmailLink: Action.define("fetchRoomEmailLink", {
roomName: String
}),
/**
* Used to cancel call setup.
*/
CancelCall: Action.define("cancelCall", {
}),
/**
* Used to retry a failed call.
*/
RetryCall: Action.define("retryCall", {
}),
/**
* Signals when the user wishes to accept a call.
*/
AcceptCall: Action.define("acceptCall", {
callType: String
}),
/**
* Signals when the user declines a call.
*/
DeclineCall: Action.define("declineCall", {
blockCaller: Boolean
}),
/**
* Used to initiate connecting of a call with the relevant
* sessionData.
*/
ConnectCall: Action.define("connectCall", {
// This object contains the necessary details for the
// connection of the websocket, and the SDK
sessionData: Object
}),
/**
* Used for hanging up the call at the end of a successful call.
*/
HangupCall: Action.define("hangupCall", {
}),
/** /**
* Used to indicate the remote peer was disconnected for some reason. * Used to indicate the remote peer was disconnected for some reason.
* *
@ -134,16 +83,6 @@ loop.shared.actions = (function() {
peerHungup: Boolean peerHungup: Boolean
}), }),
/**
* Used for notifying of connection progress state changes.
* The connection refers to the overall connection flow as indicated
* on the websocket.
*/
ConnectionProgress: Action.define("connectionProgress", {
// The connection state from the websocket.
wsState: String
}),
/** /**
* Used for notifying of connection failures. * Used for notifying of connection failures.
*/ */

View File

@ -1,463 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.EXPORTED_SYMBOLS = ["LoopCalls"];
const EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
XPCOMUtils.defineLazyModuleGetter(this, "MozLoopService",
"resource:///modules/loop/MozLoopService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LOOP_SESSION_TYPE",
"resource:///modules/loop/MozLoopService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoopContacts",
"resource:///modules/loop/LoopContacts.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
/**
* Attempts to open a websocket.
*
* A new websocket interface is used each time. If an onStop callback
* was received, calling asyncOpen() on the same interface will
* trigger a "alreay open socket" exception even though the channel
* is logically closed.
*/
function CallProgressSocket(progressUrl, callId, token) {
if (!progressUrl || !callId || !token) {
throw new Error("missing required arguments");
}
this._progressUrl = progressUrl;
this._callId = callId;
this._token = token;
}
CallProgressSocket.prototype = {
/**
* Open websocket and run hello exchange.
* Sends a hello message to the server.
*
* @param {function} Callback used after a successful handshake
* over the progressUrl.
* @param {function} Callback used if an error is encountered
*/
connect: function(onSuccess, onError) {
this._onSuccess = onSuccess;
this._onError = onError || (reason => {
MozLoopService.log.warn("LoopCalls::callProgessSocket - ", reason);
});
if (!onSuccess) {
this._onError("missing onSuccess argument");
return;
}
if (Services.io.offline) {
this._onError("IO offline");
return;
}
let uri = Services.io.newURI(this._progressUrl, null, null);
// Allow _websocket to be set for testing.
if (!this._websocket) {
this._websocket = Cc["@mozilla.org/network/protocol;1?name=" + uri.scheme]
.createInstance(Ci.nsIWebSocketChannel);
this._websocket.initLoadInfo(null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_WEBSOCKET);
}
this._websocket.asyncOpen(uri, this._progressUrl, this, null);
},
/**
* Listener method, handles the start of the websocket stream.
* Sends a hello message to the server.
*
* @param {nsISupports} aContext Not used
*/
onStart: function() {
let helloMsg = {
messageType: "hello",
callId: this._callId,
auth: this._token
};
try { // in case websocket has closed before this handler is run
this._websocket.sendMsg(JSON.stringify(helloMsg));
}
catch (error) {
this._onError(error);
}
},
/**
* Listener method, called when the websocket is closed.
*
* @param {nsISupports} aContext Not used
* @param {nsresult} aStatusCode Reason for stopping (NS_OK = successful)
*/
onStop: function(aContext, aStatusCode) {
if (!this._handshakeComplete) {
this._onError("[" + aStatusCode + "]");
}
},
/**
* Listener method, called when the websocket is closed by the server.
* If there are errors, onStop may be called without ever calling this
* method.
*
* @param {nsISupports} aContext Not used
* @param {integer} aCode the websocket closing handshake close code
* @param {String} aReason the websocket closing handshake close reason
*/
onServerClose: function(aContext, aCode, aReason) {
if (!this._handshakeComplete) {
this._onError("[" + aCode + "]" + aReason);
}
},
/**
* Listener method, called when the websocket receives a message.
*
* @param {nsISupports} aContext Not used
* @param {String} aMsg The message data
*/
onMessageAvailable: function(aContext, aMsg) {
let msg = {};
try {
msg = JSON.parse(aMsg);
} catch (error) {
MozLoopService.log.error("LoopCalls: error parsing progress message - ", error);
return;
}
if (msg.messageType && msg.messageType === "hello") {
this._handshakeComplete = true;
this._onSuccess();
}
},
/**
* Create a JSON message payload and send on websocket.
*
* @param {Object} aMsg Message to send.
*/
_send: function(aMsg) {
if (!this._handshakeComplete) {
MozLoopService.log.warn("LoopCalls::_send error - handshake not complete");
return;
}
try {
this._websocket.sendMsg(JSON.stringify(aMsg));
}
catch (error) {
this._onError(error);
}
},
/**
* Notifies the server that the user has declined the call
* with a reason of busy.
*/
sendBusy: function() {
this._send({
messageType: "action",
event: "terminate",
reason: "busy"
});
}
};
/**
* Internal helper methods and state
*
* The registration is a two-part process. First we need to connect to
* and register with the push server. Then we need to take the result of that
* and register with the Loop server.
*/
var LoopCallsInternal = {
mocks: {
webSocket: undefined
},
conversationInProgress: {},
/**
* Callback from MozLoopPushHandler - A push notification has been received from
* the server.
*
* @param {String} version The version information from the server.
*/
onNotification: function(version, channelID) {
if (MozLoopService.doNotDisturb) {
return;
}
// Request the information on the new call(s) associated with this version.
// The registered FxA session is checked first, then the anonymous session.
// Make the call to get the GUEST session regardless of whether the FXA
// request fails.
if (channelID == MozLoopService.channelIDs.callsFxA && MozLoopService.userProfile) {
this._getCalls(LOOP_SESSION_TYPE.FXA, version);
}
},
/**
* Make a hawkRequest to GET/calls?=version for this session type.
*
* @param {LOOP_SESSION_TYPE} sessionType - type of hawk token used
* for the GET operation.
* @param {Object} version - LoopPushService notification version
*
* @returns {Promise}
*
*/
_getCalls: function(sessionType, version) {
return MozLoopService.hawkRequest(sessionType, "/calls?version=" + version, "GET").then(
response => { this._processCalls(response, sessionType); }
);
},
/**
* Process the calls array returned from a GET/calls?version request.
* Only one active call is permitted at this time.
*
* @param {Object} response - response payload from GET
*
* @param {LOOP_SESSION_TYPE} sessionType - type of hawk token used
* for the GET operation.
*
*/
_processCalls: function(response, sessionType) {
try {
let respData = JSON.parse(response.body);
if (respData.calls && Array.isArray(respData.calls)) {
respData.calls.forEach((callData) => {
if ("id" in this.conversationInProgress) {
this._returnBusy(callData);
} else {
callData.sessionType = sessionType;
callData.type = "incoming";
this._startCall(callData);
}
});
} else {
MozLoopService.log.warn("Error: missing calls[] in response");
}
} catch (err) {
MozLoopService.log.warn("Error parsing calls info", err);
}
},
/**
* Starts a call, saves the call data, and opens a chat window.
*
* @param {Object} callData The data associated with the call including an id.
* The data should include the type - "incoming" or
* "outgoing".
*/
_startCall: function(callData) {
const openChat = () => {
let windowId = MozLoopService.openChatWindow(callData);
if (windowId) {
this.conversationInProgress.id = windowId;
}
};
if (callData.type == "incoming" && ("callerId" in callData) &&
EMAIL_OR_PHONE_RE.test(callData.callerId)) {
LoopContacts.search({
q: callData.callerId,
field: callData.callerId.includes("@") ? "email" : "tel"
}, (err, contacts) => {
if (err) {
// Database error, helas!
openChat();
return;
}
for (let contact of contacts) {
if (contact.blocked) {
// Blocked! Send a busy signal back to the caller.
this._returnBusy(callData);
return;
}
}
openChat();
});
} else {
openChat();
}
},
/**
* Starts a direct call to the contact addresses.
*
* @param {Object} contact The contact to call
* @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
* @return true if the call is opened, false if it is not opened (i.e. busy)
*/
startDirectCall: function(contact, callType) {
if ("id" in this.conversationInProgress) {
return false;
}
var callData = {
contact: contact,
callType: callType,
type: "outgoing"
};
this._startCall(callData);
return true;
},
/**
* Block a caller so it will show up in the contacts list as a blocked contact.
* If the contact is not yet part of the users' contacts list, it will be added
* as a blocked contact directly.
*
* @param {String} callerId Email address or phone number that may identify
* the caller as an existing contact
* @param {Function} callback Function that will be invoked once the operation
* has completed. When an error occurs, it will be
* passed as its first argument
*/
blockDirectCaller: function(callerId, callback) {
let field = callerId.contains("@") ? "email" : "tel";
Task.spawn(function* () {
// See if we can find the caller in our database.
let contacts = yield LoopContacts.promise("search", {
q: callerId,
field: field
});
let contact;
if (contacts.length) {
for (contact of contacts) {
yield LoopContacts.promise("block", contact._guid);
}
} else {
// If the contact doesn't exist yet, add it as a blocked contact.
contact = {
id: MozLoopService.generateUUID(),
name: [callerId],
category: ["local"],
blocked: true
};
// Add the phone OR email field to the contact.
contact[field] = [{
pref: true,
value: callerId
}];
yield LoopContacts.promise("add", contact);
}
}).then(callback, callback);
},
/**
* Open call progress websocket and terminate with a reason of busy
* the server.
*
* @param {callData} Must contain the progressURL, callId and websocketToken
* returned by the LoopService.
*/
_returnBusy: function(callData) {
let callProgress = new CallProgressSocket(
callData.progressURL,
callData.callId,
callData.websocketToken);
if (this.mocks.webSocket) {
callProgress._websocket = this.mocks.webSocket;
}
// This instance of CallProgressSocket should stay alive until the underlying
// websocket is closed since it is passed to the websocket as the nsIWebSocketListener.
callProgress.connect(() => { callProgress.sendBusy(); });
}
};
Object.freeze(LoopCallsInternal);
/**
* Public API
*/
this.LoopCalls = {
/**
* Callback from MozLoopPushHandler - A push notification has been received from
* the server.
*
* @param {String} version The version information from the server.
*/
onNotification: function(version, channelID) {
LoopCallsInternal.onNotification(version, channelID);
},
/**
* Used to signify that a call is in progress.
*
* @param {String} The window id for the call in progress.
*/
setCallInProgress: function(conversationWindowId) {
if ("id" in LoopCallsInternal.conversationInProgress &&
LoopCallsInternal.conversationInProgress.id != conversationWindowId) {
MozLoopService.log.error("Starting a new conversation when one is already in progress?");
return;
}
LoopCallsInternal.conversationInProgress.id = conversationWindowId;
},
/**
* Releases the callData for a specific conversation window id.
*
* The result of this call will be a free call session slot.
*
* @param {Number} conversationWindowId
*/
clearCallInProgress: function(conversationWindowId) {
if ("id" in LoopCallsInternal.conversationInProgress &&
LoopCallsInternal.conversationInProgress.id == conversationWindowId) {
delete LoopCallsInternal.conversationInProgress.id;
}
},
/**
* Starts a direct call to the contact addresses.
*
* @param {Object} contact The contact to call
* @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
* @return true if the call is opened, false if it is not opened (i.e. busy)
*/
startDirectCall: function(contact, callType) {
LoopCallsInternal.startDirectCall(contact, callType);
},
/**
* @see LoopCallsInternal#blockDirectCaller
*/
blockDirectCaller: function(callerId, callback) {
return LoopCallsInternal.blockDirectCaller(callerId, callback);
}
};
Object.freeze(LoopCalls);

View File

@ -9,7 +9,6 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://services-common/utils.js"); Cu.import("resource://services-common/utils.js");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/loop/LoopCalls.jsm");
Cu.import("resource:///modules/loop/MozLoopService.jsm"); Cu.import("resource:///modules/loop/MozLoopService.jsm");
Cu.import("resource:///modules/loop/LoopRooms.jsm"); Cu.import("resource:///modules/loop/LoopRooms.jsm");
Cu.import("resource:///modules/loop/LoopContacts.jsm"); Cu.import("resource:///modules/loop/LoopContacts.jsm");
@ -372,8 +371,8 @@ function injectLoopAPI(targetWindow) {
/** /**
* Returns the window data for a specific conversation window id. * Returns the window data for a specific conversation window id.
* *
* This data will be relevant to the type of window, e.g. rooms or calls. * This data will be relevant to the type of window, e.g. rooms.
* See LoopRooms or LoopCalls for more information. * See LoopRooms for more information.
* *
* @param {String} conversationWindowId * @param {String} conversationWindowId
* @returns {Object} The window data or null if error. * @returns {Object} The window data or null if error.
@ -423,22 +422,6 @@ function injectLoopAPI(targetWindow) {
} }
}, },
/**
* Returns the calls API.
*
* @returns {Object} The rooms API object
*/
calls: {
enumerable: true,
get: function() {
if (callsAPI) {
return callsAPI;
}
return callsAPI = injectObjectAPI(LoopCalls, targetWindow);
}
},
/** /**
* Import a list of (new) contacts from an external data source. * Import a list of (new) contacts from an external data source.
* *
@ -609,50 +592,6 @@ function injectLoopAPI(targetWindow) {
} }
}, },
/**
* Performs a hawk based request to the loop server.
*
* Callback parameters:
* - {Object|null} null if success. Otherwise an object:
* {
* code: 401,
* errno: 401,
* error: "Request failed",
* message: "invalid token"
* }
* - {String} The body of the response.
*
* @param {LOOP_SESSION_TYPE} sessionType The type of session to use for
* the request. This is one of the
* LOOP_SESSION_TYPE members
* @param {String} path The path to make the request to.
* @param {String} method The request method, e.g. 'POST', 'GET'.
* @param {Object} payloadObj An object which is converted to JSON and
* transmitted with the request.
* @param {Function} callback Called when the request completes.
*/
hawkRequest: {
enumerable: true,
writable: true,
value: function(sessionType, path, method, payloadObj, callback) {
// XXX Should really return a DOM promise here.
MozLoopService.hawkRequest(sessionType, path, method, payloadObj).then((response) => {
invokeCallback(callback, null, response.body);
}, hawkError => {
// The hawkError.error property, while usually a string representing
// an HTTP response status message, may also incorrectly be a native
// error object that will cause the cloning function to fail.
invokeCallback(callback, Cu.cloneInto({
error: (hawkError.error && typeof hawkError.error == "string")
? hawkError.error : "Unexpected exception",
message: hawkError.message,
code: hawkError.code,
errno: hawkError.errno,
}, targetWindow));
}).catch(Cu.reportError);
}
},
LOOP_SESSION_TYPE: { LOOP_SESSION_TYPE: {
enumerable: true, enumerable: true,
get: function() { get: function() {

View File

@ -153,9 +153,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "LoopContacts",
XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage", XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage",
"resource:///modules/loop/LoopStorage.jsm"); "resource:///modules/loop/LoopStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoopCalls",
"resource:///modules/loop/LoopCalls.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoopRooms", XPCOMUtils.defineLazyModuleGetter(this, "LoopRooms",
"resource:///modules/loop/LoopRooms.jsm"); "resource:///modules/loop/LoopRooms.jsm");
@ -410,7 +407,7 @@ var MozLoopServiceInternal = {
* @param {String} channelID Unique identifier for the notification channel * @param {String} channelID Unique identifier for the notification channel
* registered with the PushServer. * registered with the PushServer.
* @param {LOOP_SESSION_TYPE} sessionType * @param {LOOP_SESSION_TYPE} sessionType
* @param {String} serviceType Either 'calls' or 'rooms'. * @param {String} serviceType Only 'rooms' is supported.
* @param {Function} onNotification Callback function that will be associated * @param {Function} onNotification Callback function that will be associated
* with this channel from the PushServer. * with this channel from the PushServer.
* @returns {Promise} A promise that is resolved with no params on completion, or * @returns {Promise} A promise that is resolved with no params on completion, or
@ -464,12 +461,8 @@ var MozLoopServiceInternal = {
roomsPushNotification); roomsPushNotification);
} else { } else {
regPromise = this.createNotificationChannel( regPromise = this.createNotificationChannel(
MozLoopService.channelIDs.callsFxA, sessionType, "calls", MozLoopService.channelIDs.roomsFxA, sessionType, "rooms",
LoopCalls.onNotification).then(() => { roomsPushNotification);
return this.createNotificationChannel(
MozLoopService.channelIDs.roomsFxA, sessionType, "rooms",
roomsPushNotification);
});
} }
log.debug("assigning to deferredRegistrations for sessionType:", sessionType); log.debug("assigning to deferredRegistrations for sessionType:", sessionType);
@ -491,7 +484,7 @@ var MozLoopServiceInternal = {
* *
* @private * @private
* @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA * @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
* @param {String} serviceType: "rooms" or "calls" * @param {String} serviceType: only "rooms" is currently supported.
* @param {Boolean} [retry=true] Whether to retry if authentication fails. * @param {Boolean} [retry=true] Whether to retry if authentication fails.
* @return {Promise} resolves to pushURL or rejects with an Error * @return {Promise} resolves to pushURL or rejects with an Error
*/ */
@ -505,7 +498,7 @@ var MozLoopServiceInternal = {
// Create a blank URL record set if none exists for this sessionType. // Create a blank URL record set if none exists for this sessionType.
if (!pushURLs) { if (!pushURLs) {
pushURLs = { calls: undefined, rooms: undefined }; pushURLs = { rooms: undefined };
this.pushURLs.set(sessionType, pushURLs); this.pushURLs.set(sessionType, pushURLs);
} }
@ -513,8 +506,7 @@ var MozLoopServiceInternal = {
return Promise.resolve(pushURL); return Promise.resolve(pushURL);
} }
let newURLs = {calls: pushURLs.calls, let newURLs = {rooms: pushURLs.rooms};
rooms: pushURLs.rooms};
newURLs[serviceType] = pushURL; newURLs[serviceType] = pushURL;
return this.hawkRequestInternal(sessionType, "/registration", "POST", return this.hawkRequestInternal(sessionType, "/registration", "POST",
@ -555,7 +547,7 @@ var MozLoopServiceInternal = {
* guest session with the device. * guest session with the device.
* *
* NOTE: It is the responsibiliy of the caller the clear the session token * NOTE: It is the responsibiliy of the caller the clear the session token
* after all of the notification classes: calls and rooms, for either * after all of the notification classes: rooms, for either
* Guest or FxA have been unregistered with the LoopServer. * Guest or FxA have been unregistered with the LoopServer.
* *
* @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA * @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
@ -570,34 +562,29 @@ var MozLoopServiceInternal = {
let error, let error,
pushURLs = this.pushURLs.get(sessionType), pushURLs = this.pushURLs.get(sessionType),
callsPushURL = pushURLs ? pushURLs.calls : null,
roomsPushURL = pushURLs ? pushURLs.rooms : null; roomsPushURL = pushURLs ? pushURLs.rooms : null;
this.pushURLs.delete(sessionType); this.pushURLs.delete(sessionType);
let unregister = (sessType, pushURL) => { if (!roomsPushURL) {
if (!pushURL) { return Promise.resolve("no pushURL of this type to unregister");
return Promise.resolve("no pushURL of this type to unregister"); }
}
let unregisterURL = "/registration?simplePushURL=" + encodeURIComponent(pushURL); let unregisterURL = "/registration?simplePushURL=" + encodeURIComponent(roomsPushURL);
return this.hawkRequestInternal(sessType, unregisterURL, "DELETE").then( return this.hawkRequestInternal(sessionType, unregisterURL, "DELETE").then(
() => { () => {
log.debug("Successfully unregistered from server for sessionType = ", sessType); log.debug("Successfully unregistered from server for sessionType = ", sessionType);
return "unregistered sessionType " + sessType; return "unregistered sessionType " + sessionType;
}, },
err => { err => {
if (err.code === 401) { if (err.code === 401) {
// Authorization failed, invalid token. This is fine since it may mean we already logged out. // Authorization failed, invalid token. This is fine since it may mean we already logged out.
log.debug("already unregistered - invalid token", sessType); log.debug("already unregistered - invalid token", sessionType);
return "already unregistered, sessionType = " + sessType; return "already unregistered, sessionType = " + sessionType;
} }
log.error("Failed to unregister with the loop server. Error: ", error); log.error("Failed to unregister with the loop server. Error: ", error);
throw err; throw err;
}); });
};
return Promise.all([unregister(sessionType, callsPushURL), unregister(sessionType, roomsPushURL)]);
}, },
/** /**
@ -874,16 +861,13 @@ var MozLoopServiceInternal = {
}, pc.id); }, pc.id);
}, },
/**
* Gets an id for the chat window, for now we just use the roomToken.
*
* @param {Object} conversationWindowData The conversation window data.
*/
getChatWindowID: function(conversationWindowData) { getChatWindowID: function(conversationWindowData) {
// Try getting a window ID that can (re-)identify this conversation, or resort return conversationWindowData.roomToken;
// to a globally unique one as a last resort.
// XXX We can clean this up once rooms and direct contact calling are the only
// two modes left.
let windowId = ("contact" in conversationWindowData) ?
conversationWindowData.contact._guid || gLastWindowId++ :
conversationWindowData.roomToken || conversationWindowData.callId ||
gLastWindowId++;
return windowId.toString();
}, },
getChatURL: function(chatWindowId) { getChatURL: function(chatWindowId) {
@ -1203,7 +1187,6 @@ this.MozLoopService = {
get channelIDs() { get channelIDs() {
// Channel ids that will be registered with the PushServer for notifications // Channel ids that will be registered with the PushServer for notifications
return { return {
callsFxA: "25389583-921f-4169-a426-a4673658944b",
roomsFxA: "6add272a-d316-477c-8335-f00f73dfde71", roomsFxA: "6add272a-d316-477c-8335-f00f73dfde71",
roomsGuest: "19d3f799-a8f3-4328-9822-b7cd02765832" roomsGuest: "19d3f799-a8f3-4328-9822-b7cd02765832"
}; };
@ -1697,7 +1680,6 @@ this.MozLoopService = {
MozLoopServiceInternal.deferredRegistrations.delete(LOOP_SESSION_TYPE.FXA); MozLoopServiceInternal.deferredRegistrations.delete(LOOP_SESSION_TYPE.FXA);
// Unregister with PushHandler so these push channels will not get re-registered // Unregister with PushHandler so these push channels will not get re-registered
// if the connection is re-established by the PushHandler. // if the connection is re-established by the PushHandler.
MozLoopServiceInternal.pushHandler.unregister(MozLoopService.channelIDs.callsFxA);
MozLoopServiceInternal.pushHandler.unregister(MozLoopService.channelIDs.roomsFxA); MozLoopServiceInternal.pushHandler.unregister(MozLoopService.channelIDs.roomsFxA);
// Reset the client since the initial promiseFxAOAuthParameters() call is // Reset the client since the initial promiseFxAOAuthParameters() call is
@ -1868,8 +1850,8 @@ this.MozLoopService = {
/** /**
* Returns the window data for a specific conversation window id. * Returns the window data for a specific conversation window id.
* *
* This data will be relevant to the type of window, e.g. rooms or calls. * This data will be relevant to the type of window, e.g. rooms.
* See LoopRooms or LoopCalls for more information. * See LoopRooms for more information.
* *
* @param {String} conversationWindowId * @param {String} conversationWindowId
* @returns {Object} The window data or null if error. * @returns {Object} The window data or null if error.

View File

@ -17,7 +17,6 @@ EXTRA_JS_MODULES.loop += [
'content/shared/js/utils.js', 'content/shared/js/utils.js',
'modules/CardDavImporter.jsm', 'modules/CardDavImporter.jsm',
'modules/GoogleImporter.jsm', 'modules/GoogleImporter.jsm',
'modules/LoopCalls.jsm',
'modules/LoopContacts.jsm', 'modules/LoopContacts.jsm',
'modules/LoopRooms.jsm', 'modules/LoopRooms.jsm',
'modules/LoopRoomsCache.jsm', 'modules/LoopRoomsCache.jsm',

View File

@ -313,8 +313,6 @@ add_task(function* basicAuthorizationAndRegistration() {
is(loopButton.getAttribute("state"), "active", "state of loop button should be active when logged in"); is(loopButton.getAttribute("state"), "active", "state of loop button should be active when logged in");
let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL); let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
is(registrationResponse.response.simplePushURLs.calls, "https://localhost/pushUrl/fxa-calls",
"Check registered push URL");
is(registrationResponse.response.simplePushURLs.rooms, "https://localhost/pushUrl/fxa-rooms", is(registrationResponse.response.simplePushURLs.rooms, "https://localhost/pushUrl/fxa-rooms",
"Check registered push URL"); "Check registered push URL");
@ -367,10 +365,10 @@ add_task(function* logoutWithIncorrectPushURL() {
// Create a fake FxA hawk session token // Create a fake FxA hawk session token
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA); const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH)); Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, "calls", pushURL); yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, "rooms", pushURL);
let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL); let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
is(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL"); is(registrationResponse.response.simplePushURLs.rooms, pushURL, "Check registered push URL");
MozLoopServiceInternal.pushURLs.get(LOOP_SESSION_TYPE.FXA).calls = "http://www.example.com/invalid"; MozLoopServiceInternal.pushURLs.get(LOOP_SESSION_TYPE.FXA).rooms = "http://www.example.com/invalid";
let caught = false; let caught = false;
yield MozLoopService.logOutFromFxA().catch((error) => { yield MozLoopService.logOutFromFxA().catch((error) => {
caught = true; caught = true;
@ -378,7 +376,7 @@ add_task(function* logoutWithIncorrectPushURL() {
ok(caught, "Should have caught an error logging out with a mismatched push URL"); ok(caught, "Should have caught an error logging out with a mismatched push URL");
checkLoggedOutState(); checkLoggedOutState();
registrationResponse = yield promiseOAuthGetRegistration(BASE_URL); registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
is(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL wasn't deleted"); is(registrationResponse.response.simplePushURLs.rooms, pushURL, "Check registered push URL wasn't deleted");
}); });
add_task(function* logoutWithNoPushURL() { add_task(function* logoutWithNoPushURL() {
@ -388,14 +386,14 @@ add_task(function* logoutWithNoPushURL() {
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA); const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH)); Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, "calls", pushURL); yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, "rooms", pushURL);
let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL); let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
is(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL"); is(registrationResponse.response.simplePushURLs.rooms, pushURL, "Check registered push URL");
MozLoopServiceInternal.pushURLs.delete(LOOP_SESSION_TYPE.FXA); MozLoopServiceInternal.pushURLs.delete(LOOP_SESSION_TYPE.FXA);
yield MozLoopService.logOutFromFxA(); yield MozLoopService.logOutFromFxA();
checkLoggedOutState(); checkLoggedOutState();
registrationResponse = yield promiseOAuthGetRegistration(BASE_URL); registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
is(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL wasn't deleted"); is(registrationResponse.response.simplePushURLs.rooms, pushURL, "Check registered push URL wasn't deleted");
}); });
add_task(function* loginWithRegistration401() { add_task(function* loginWithRegistration401() {

View File

@ -9,7 +9,6 @@ const {
MozLoopServiceInternal, MozLoopServiceInternal,
MozLoopService MozLoopService
} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}); } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
const {LoopCalls} = Cu.import("resource:///modules/loop/LoopCalls.jsm", {});
const {LoopRooms} = Cu.import("resource:///modules/loop/LoopRooms.jsm", {}); const {LoopRooms} = Cu.import("resource:///modules/loop/LoopRooms.jsm", {});
// Cache this value only once, at the beginning of a // Cache this value only once, at the beginning of a
@ -231,12 +230,8 @@ var mockPushHandler = {
if ("mockWebSocket" in options) { if ("mockWebSocket" in options) {
this._mockWebSocket = options.mockWebSocket; this._mockWebSocket = options.mockWebSocket;
} }
this.registrationPushURLs[MozLoopService.channelIDs.callsGuest] =
"https://localhost/pushUrl/guest-calls";
this.registrationPushURLs[MozLoopService.channelIDs.roomsGuest] = this.registrationPushURLs[MozLoopService.channelIDs.roomsGuest] =
"https://localhost/pushUrl/guest-rooms"; "https://localhost/pushUrl/guest-rooms";
this.registrationPushURLs[MozLoopService.channelIDs.callsFxA] =
"https://localhost/pushUrl/fxa-calls";
this.registrationPushURLs[MozLoopService.channelIDs.roomsFxA] = this.registrationPushURLs[MozLoopService.channelIDs.roomsFxA] =
"https://localhost/pushUrl/fxa-rooms"; "https://localhost/pushUrl/fxa-rooms";
}, },

View File

@ -213,7 +213,6 @@ function push_server(request, response) {
function registration(request, response) { function registration(request, response) {
let isFxARequest = function(payload) { let isFxARequest = function(payload) {
return (payload.simplePushURL == "https://localhost/pushUrl/fxa" || return (payload.simplePushURL == "https://localhost/pushUrl/fxa" ||
payload.simplePushURLs.calls == "https://localhost/pushUrl/fxa-calls" ||
payload.simplePushURLs.rooms == "https://localhost/pushUrl/fxa-rooms"); payload.simplePushURLs.rooms == "https://localhost/pushUrl/fxa-rooms");
}; };
@ -254,9 +253,9 @@ function delete_registration(request, response) {
// registering endpoints at the root of the hostname e.g. /registration. // registering endpoints at the root of the hostname e.g. /registration.
let url = new URL(request.queryString.replace(/%3F.*/,""), "http://www.example.com"); let url = new URL(request.queryString.replace(/%3F.*/,""), "http://www.example.com");
let state = getSharedState("/registration"); let state = getSharedState("/registration");
if (state != "") { //Already set to empty value on a successful channel unregsitration. if (state != "") { //Already set to empty value on a successful channel unregistration.
let registration = JSON.parse(state); let registration = JSON.parse(state);
if (registration.simplePushURLs.calls == url.searchParams.get("simplePushURL")) { if (registration.simplePushURLs.rooms == url.searchParams.get("simplePushURL")) {
setSharedState("/registration", ""); setSharedState("/registration", "");
} else { } else {
response.setStatusLine(request.httpVersion, 400, "Bad Request"); response.setStatusLine(request.httpVersion, 400, "Bad Request");

View File

@ -66,16 +66,16 @@ describe("loop.Dispatcher", function () {
}); });
describe("#dispatch", function() { describe("#dispatch", function() {
var getDataStore1, getDataStore2, cancelStore1, connectStore1; var getDataStore1, getDataStore2, gotMediaPermissionStore1, mediaConnectedStore1;
var getDataAction, cancelAction, connectAction, resolveCancelStore1; var getDataAction, gotMediaPermissionAction, mediaConnectedAction;
beforeEach(function() { beforeEach(function() {
getDataAction = new sharedActions.GetWindowData({ getDataAction = new sharedActions.GetWindowData({
windowId: "42" windowId: "42"
}); });
cancelAction = new sharedActions.CancelCall(); gotMediaPermissionAction = new sharedActions.GotMediaPermission();
connectAction = new sharedActions.ConnectCall({ mediaConnectedAction = new sharedActions.MediaConnected({
sessionData: {} sessionData: {}
}); });
@ -85,26 +85,27 @@ describe("loop.Dispatcher", function () {
getDataStore2 = { getDataStore2 = {
getWindowData: sinon.stub() getWindowData: sinon.stub()
}; };
cancelStore1 = { gotMediaPermissionStore1 = {
cancelCall: sinon.stub() gotMediaPermission: sinon.stub()
}; };
connectStore1 = { mediaConnectedStore1 = {
connectCall: function() {} mediaConnected: function() {}
}; };
dispatcher.register(getDataStore1, ["getWindowData"]); dispatcher.register(getDataStore1, ["getWindowData"]);
dispatcher.register(getDataStore2, ["getWindowData"]); dispatcher.register(getDataStore2, ["getWindowData"]);
dispatcher.register(cancelStore1, ["cancelCall"]); dispatcher.register(gotMediaPermissionStore1, ["gotMediaPermission"]);
dispatcher.register(connectStore1, ["connectCall"]); dispatcher.register(mediaConnectedStore1, ["mediaConnected"]);
}); });
it("should dispatch an action to the required object", function() { it("should dispatch an action to the required object", function() {
dispatcher.dispatch(cancelAction); dispatcher.dispatch(gotMediaPermissionAction);
sinon.assert.notCalled(getDataStore1.getWindowData); sinon.assert.notCalled(getDataStore1.getWindowData);
sinon.assert.calledOnce(cancelStore1.cancelCall); sinon.assert.calledOnce(gotMediaPermissionStore1.gotMediaPermission);
sinon.assert.calledWithExactly(cancelStore1.cancelCall, cancelAction); sinon.assert.calledWithExactly(gotMediaPermissionStore1.gotMediaPermission,
gotMediaPermissionAction);
sinon.assert.notCalled(getDataStore2.getWindowData); sinon.assert.notCalled(getDataStore2.getWindowData);
}); });
@ -115,17 +116,17 @@ describe("loop.Dispatcher", function () {
sinon.assert.calledOnce(getDataStore1.getWindowData); sinon.assert.calledOnce(getDataStore1.getWindowData);
sinon.assert.calledWithExactly(getDataStore1.getWindowData, getDataAction); sinon.assert.calledWithExactly(getDataStore1.getWindowData, getDataAction);
sinon.assert.notCalled(cancelStore1.cancelCall); sinon.assert.notCalled(gotMediaPermissionStore1.gotMediaPermission);
sinon.assert.calledOnce(getDataStore2.getWindowData); sinon.assert.calledOnce(getDataStore2.getWindowData);
sinon.assert.calledWithExactly(getDataStore2.getWindowData, getDataAction); sinon.assert.calledWithExactly(getDataStore2.getWindowData, getDataAction);
}); });
it("should dispatch multiple actions", function() { it("should dispatch multiple actions", function() {
dispatcher.dispatch(cancelAction); dispatcher.dispatch(gotMediaPermissionAction);
dispatcher.dispatch(getDataAction); dispatcher.dispatch(getDataAction);
sinon.assert.calledOnce(cancelStore1.cancelCall); sinon.assert.calledOnce(gotMediaPermissionStore1.gotMediaPermission);
sinon.assert.calledOnce(getDataStore1.getWindowData); sinon.assert.calledOnce(getDataStore1.getWindowData);
sinon.assert.calledOnce(getDataStore2.getWindowData); sinon.assert.calledOnce(getDataStore2.getWindowData);
}); });
@ -139,11 +140,11 @@ describe("loop.Dispatcher", function () {
getDataStore1.getWindowData.throws("Uncaught Error"); getDataStore1.getWindowData.throws("Uncaught Error");
dispatcher.dispatch(getDataAction); dispatcher.dispatch(getDataAction);
dispatcher.dispatch(cancelAction); dispatcher.dispatch(gotMediaPermissionAction);
sinon.assert.calledOnce(getDataStore1.getWindowData); sinon.assert.calledOnce(getDataStore1.getWindowData);
sinon.assert.calledOnce(getDataStore2.getWindowData); sinon.assert.calledOnce(getDataStore2.getWindowData);
sinon.assert.calledOnce(cancelStore1.cancelCall); sinon.assert.calledOnce(gotMediaPermissionStore1.gotMediaPermission);
}); });
it("should log uncaught exceptions", function() { it("should log uncaught exceptions", function() {
@ -159,7 +160,7 @@ describe("loop.Dispatcher", function () {
beforeEach(function() { beforeEach(function() {
// Restore the stub, so that we can easily add a function to be // Restore the stub, so that we can easily add a function to be
// returned. Unfortunately, sinon doesn't make this easy. // returned. Unfortunately, sinon doesn't make this easy.
sandbox.stub(connectStore1, "connectCall", function() { sandbox.stub(mediaConnectedStore1, "mediaConnected", function() {
dispatcher.dispatch(getDataAction); dispatcher.dispatch(getDataAction);
sinon.assert.notCalled(getDataStore1.getWindowData); sinon.assert.notCalled(getDataStore1.getWindowData);
@ -170,17 +171,17 @@ describe("loop.Dispatcher", function () {
it("should not dispatch an action if the previous action hasn't finished", function() { it("should not dispatch an action if the previous action hasn't finished", function() {
// Dispatch the first action. The action handler dispatches the second // Dispatch the first action. The action handler dispatches the second
// action - see the beforeEach above. // action - see the beforeEach above.
dispatcher.dispatch(connectAction); dispatcher.dispatch(mediaConnectedAction);
sinon.assert.calledOnce(connectStore1.connectCall); sinon.assert.calledOnce(mediaConnectedStore1.mediaConnected);
}); });
it("should dispatch an action when the previous action finishes", function() { it("should dispatch an action when the previous action finishes", function() {
// Dispatch the first action. The action handler dispatches the second // Dispatch the first action. The action handler dispatches the second
// action - see the beforeEach above. // action - see the beforeEach above.
dispatcher.dispatch(connectAction); dispatcher.dispatch(mediaConnectedAction);
sinon.assert.calledOnce(connectStore1.connectCall); sinon.assert.calledOnce(mediaConnectedStore1.mediaConnected);
// These should be called, because the dispatcher synchronously queues actions. // These should be called, because the dispatcher synchronously queues actions.
sinon.assert.calledOnce(getDataStore1.getWindowData); sinon.assert.calledOnce(getDataStore1.getWindowData);
sinon.assert.calledOnce(getDataStore2.getWindowData); sinon.assert.calledOnce(getDataStore2.getWindowData);

View File

@ -14,7 +14,6 @@ Cu.import("resource://gre/modules/Http.jsm");
Cu.import("resource://testing-common/httpd.js"); Cu.import("resource://testing-common/httpd.js");
Cu.import("resource:///modules/loop/MozLoopService.jsm"); Cu.import("resource:///modules/loop/MozLoopService.jsm");
Cu.import("resource://gre/modules/Promise.jsm"); Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource:///modules/loop/LoopCalls.jsm");
Cu.import("resource:///modules/loop/LoopRooms.jsm"); Cu.import("resource:///modules/loop/LoopRooms.jsm");
Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/osfile.jsm");
const { MozLoopServiceInternal } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}); const { MozLoopServiceInternal } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});

View File

@ -1,71 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Unit tests for the hawkRequest API
*/
"use strict";
Cu.import("resource:///modules/loop/MozLoopAPI.jsm");
var sandbox;
function assertInSandbox(expr, msg_opt) {
Assert.ok(Cu.evalInSandbox(expr, sandbox), msg_opt);
}
sandbox = Cu.Sandbox("about:looppanel", { wantXrays: false } );
injectLoopAPI(sandbox, true);
add_task(function* hawk_session_scope_constants() {
assertInSandbox("typeof mozLoop.LOOP_SESSION_TYPE !== 'undefined'");
assertInSandbox("mozLoop.LOOP_SESSION_TYPE.GUEST === 1");
assertInSandbox("mozLoop.LOOP_SESSION_TYPE.FXA === 2");
});
function generateSessionTypeVerificationStub(desiredSessionType) {
function hawkRequestStub(sessionType, path, method, payloadObj, callback) {
return new Promise(function (resolve, reject) {
Assert.equal(desiredSessionType, sessionType);
resolve();
});
}
return hawkRequestStub;
}
const origHawkRequest = MozLoopService.hawkRequest;
do_register_cleanup(function() {
MozLoopService.hawkRequest = origHawkRequest;
});
add_task(function* hawk_request_scope_passthrough() {
// add a stub that verifies the parameter we want
MozLoopService.hawkRequest =
generateSessionTypeVerificationStub(sandbox.mozLoop.LOOP_SESSION_TYPE.FXA);
// call mozLoop.hawkRequest, which calls MozLoopAPI.hawkRequest, which calls
// MozLoopService.hawkRequest
Cu.evalInSandbox(
"mozLoop.hawkRequest(mozLoop.LOOP_SESSION_TYPE.FXA," +
" 'call-url/fakeToken', 'POST', {}, function() {})",
sandbox);
MozLoopService.hawkRequest =
generateSessionTypeVerificationStub(sandbox.mozLoop.LOOP_SESSION_TYPE.GUEST);
Cu.evalInSandbox(
"mozLoop.hawkRequest(mozLoop.LOOP_SESSION_TYPE.GUEST," +
" 'call-url/fakeToken', 'POST', {}, function() {})",
sandbox);
});
function run_test() {
run_next_test();
}

View File

@ -1,122 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { LoopCallsInternal } = Cu.import("resource:///modules/loop/LoopCalls.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
"resource:///modules/Chat.jsm");
var actionReceived = false;
var openChatOrig = Chat.open;
const firstCallId = 4444333221;
const secondCallId = 1001100101;
var msgHandler = function(msg) {
if (msg.messageType &&
msg.messageType === "action" &&
msg.event === "terminate" &&
msg.reason === "busy") {
actionReceived = true;
}
};
add_task(function* test_busy_2fxa_calls() {
actionReceived = false;
yield MozLoopService.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA);
let opened = 0;
let windowId;
Chat.open = function(contentWindow, origin, title, url) {
opened++;
windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
return windowId;
};
mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
yield waitForCondition(() => { return actionReceived && opened > 0; }).then(() => {
do_check_true(opened === 1, "should open only one chat window");
do_check_true(actionReceived, "should respond with busy/reject to second call");
LoopCalls.clearCallInProgress(windowId);
}, () => {
do_throw("should have opened a chat window for first call and rejected second call");
});
});
function run_test() {
setupFakeLoopServer();
// Setup fake login state so we get FxA requests.
const MozLoopServiceInternal = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}).MozLoopServiceInternal;
MozLoopServiceInternal.fxAOAuthTokenData = {
token_type: "bearer",
access_token: "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",
scope: "profile"
};
MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
let mockWebSocket = new MockWebSocketChannel();
mockWebSocket.defaultMsgHandler = msgHandler;
LoopCallsInternal.mocks.webSocket = mockWebSocket;
Services.io.offline = false;
mockPushHandler.registrationPushURL = kEndPointUrl;
// For each notification received from the PushServer, MozLoopService will first query
// for any pending calls on the FxA hawk session and then again using the guest session.
// A pair of response objects in the callsResponses array will be consumed for each
// notification. The even calls object is for the FxA session, the odd the Guest session.
let callsRespCount = 0;
let callsResponses = [
{calls: [{callId: firstCallId,
websocketToken: "0deadbeef0",
progressURL: "wss://localhost:5000/websocket"},
{callId: secondCallId,
websocketToken: "1deadbeef1",
progressURL: "wss://localhost:5000/websocket"}]},
{calls: [{callId: firstCallId,
websocketToken: "0deadbeef0",
progressURL: "wss://localhost:5000/websocket"}]},
{calls: [{callId: secondCallId,
websocketToken: "1deadbeef1",
progressURL: "wss://localhost:5000/websocket"}]}
];
loopServer.registerPathHandler("/registration", (request, response) => {
response.setStatusLine(null, 200, "OK");
response.processAsync();
response.finish();
});
loopServer.registerPathHandler("/calls", (request, response) => {
response.setStatusLine(null, 200, "OK");
if (callsRespCount >= callsResponses.length) {
callsRespCount = 0;
}
response.write(JSON.stringify(callsResponses[callsRespCount++]));
response.processAsync();
response.finish();
});
do_register_cleanup(function() {
// Revert original Chat.open implementation
Chat.open = openChatOrig;
// Revert fake login state
MozLoopServiceInternal.fxAOAuthTokenData = null;
LoopCallsInternal.mocks.webSocket = undefined;
});
run_next_test();
}

View File

@ -1,93 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
"resource:///modules/Chat.jsm");
var openChatOrig = Chat.open;
const contact = {
name: [ "Mr Smith" ],
email: [{
type: "home",
value: "fakeEmail",
pref: true
}]
};
add_task(function test_startDirectCall_opens_window() {
let openedUrl;
Chat.open = function(contentWindow, origin, title, url) {
openedUrl = url;
return 1;
};
LoopCalls.startDirectCall(contact, "audio-video");
do_check_true(!!openedUrl, "should open a chat window");
// Stop the busy kicking in for following tests.
let windowId = openedUrl.match(/about:loopconversation\#(\d+)$/)[1];
LoopCalls.clearCallInProgress(windowId);
});
add_task(function test_startDirectCall_getConversationWindowData() {
let openedUrl;
Chat.open = function(contentWindow, origin, title, url) {
openedUrl = url;
return 2;
};
LoopCalls.startDirectCall(contact, "audio-video");
let windowId = openedUrl.match(/about:loopconversation\#(\d+)$/)[1];
let callData = MozLoopService.getConversationWindowData(windowId);
do_check_eq(callData.callType, "audio-video", "should have the correct call type");
do_check_eq(callData.contact, contact, "should have the contact details");
// Stop the busy kicking in for following tests.
LoopCalls.clearCallInProgress(windowId);
});
add_task(function test_startDirectCall_not_busy_if_window_fails_to_open() {
let openedUrl;
// Simulate no window available to open.
Chat.open = function(contentWindow, origin, title, url) {
openedUrl = url;
return null;
};
LoopCalls.startDirectCall(contact, "audio-video");
do_check_true(!!openedUrl, "should have attempted to open chat window");
openedUrl = null;
// Window opens successfully this time.
Chat.open = function(contentWindow, origin, title, url) {
openedUrl = url;
return 3;
};
LoopCalls.startDirectCall(contact, "audio-video");
do_check_true(!!openedUrl, "should open a chat window");
// Stop the busy kicking in for following tests.
let windowId = openedUrl.match(/about:loopconversation\#(\d+)$/)[1];
LoopCalls.clearCallInProgress(windowId);
});
function run_test() {
do_register_cleanup(function() {
// Revert original Chat.open implementation
Chat.open = openChatOrig;
});
run_next_test();
}

View File

@ -30,44 +30,6 @@ add_test(function test_set_do_not_disturb() {
run_next_test(); run_next_test();
}); });
add_test(function test_do_not_disturb_disabled_should_open_chat_window() {
MozLoopService.doNotDisturb = false;
mockPushHandler.registrationPushURL = kEndPointUrl;
MozLoopService.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
let opened = false;
Chat.open = function() {
opened = true;
};
mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
waitForCondition(() => opened).then(() => {
run_next_test();
}, () => {
do_throw("should have opened a chat window");
});
});
});
add_test(function test_do_not_disturb_enabled_shouldnt_open_chat_window() {
MozLoopService.doNotDisturb = true;
// We registered in the previous test, so no need to do that on this one.
let opened = false;
Chat.open = function() {
opened = true;
};
mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
do_timeout(500, function() {
do_check_false(opened, "should not open a chat window");
run_next_test();
});
});
function run_test() { function run_test() {
setupFakeLoopServer(); setupFakeLoopServer();

View File

@ -1,57 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
"resource:///modules/Chat.jsm");
var openChatOrig = Chat.open;
add_test(function test_openChatWindow_on_notification() {
mockPushHandler.registrationPushURL = kEndPointUrl;
MozLoopService.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
let opened = false;
Chat.open = function() {
opened = true;
};
mockPushHandler.notify(1, MozLoopService.channelIDs.callsFxA);
waitForCondition(() => opened).then(() => {
do_check_true(opened, "should open a chat window");
run_next_test();
}, () => {
do_throw("should have opened a chat window");
});
});
});
function run_test() {
setupFakeLoopServer();
setupFakeFxAUserProfile();
loopServer.registerPathHandler("/registration", (request, response) => {
response.setStatusLine(null, 200, "OK");
response.processAsync();
response.finish();
});
loopServer.registerPathHandler("/calls", (request, response) => {
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({calls: [{callId: 4444333221, websocketToken: "0deadbeef0"}]}));
response.processAsync();
response.finish();
});
do_register_cleanup(function() {
// Revert original Chat.open implementation
Chat.open = openChatOrig;
});
run_next_test();
}

View File

@ -4,13 +4,11 @@ tail =
firefox-appdir = browser firefox-appdir = browser
skip-if = toolkit == 'gonk' skip-if = toolkit == 'gonk'
[test_loopapi_hawk_request.js]
[test_looppush_initialize.js] [test_looppush_initialize.js]
[test_looprooms.js] [test_looprooms.js]
[test_looprooms_encryption_in_fxa.js] [test_looprooms_encryption_in_fxa.js]
[test_looprooms_first_notification.js] [test_looprooms_first_notification.js]
[test_looprooms_upgrade_to_encryption.js] [test_looprooms_upgrade_to_encryption.js]
[test_loopservice_directcall.js]
[test_loopservice_dnd.js] [test_loopservice_dnd.js]
[test_loopservice_encryptionkey.js] [test_loopservice_encryptionkey.js]
[test_loopservice_hawk_errors.js] [test_loopservice_hawk_errors.js]
@ -18,7 +16,6 @@ skip-if = toolkit == 'gonk'
[test_loopservice_loop_prefs.js] [test_loopservice_loop_prefs.js]
[test_loopservice_initialize.js] [test_loopservice_initialize.js]
[test_loopservice_locales.js] [test_loopservice_locales.js]
[test_loopservice_notification.js]
[test_loopservice_registration.js] [test_loopservice_registration.js]
[test_loopservice_registration_retry.js] [test_loopservice_registration_retry.js]
[test_loopservice_restart.js] [test_loopservice_restart.js]
@ -26,4 +23,3 @@ skip-if = toolkit == 'gonk'
[test_loopservice_token_save.js] [test_loopservice_token_save.js]
[test_loopservice_token_send.js] [test_loopservice_token_send.js]
[test_loopservice_token_validation.js] [test_loopservice_token_validation.js]
[test_loopservice_busy.js]

View File

@ -501,7 +501,9 @@ nsBrowserContentHandler.prototype = {
} }
} }
var override;
var overridePage = ""; var overridePage = "";
var additionalPage = "";
var willRestoreSession = false; var willRestoreSession = false;
try { try {
// Read the old value of homepage_override.mstone before // Read the old value of homepage_override.mstone before
@ -513,12 +515,13 @@ nsBrowserContentHandler.prototype = {
try { try {
old_mstone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone"); old_mstone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
} catch (ex) {} } catch (ex) {}
let override = needHomepageOverride(prefb); override = needHomepageOverride(prefb);
if (override != OVERRIDE_NONE) { if (override != OVERRIDE_NONE) {
switch (override) { switch (override) {
case OVERRIDE_NEW_PROFILE: case OVERRIDE_NEW_PROFILE:
// New profile. // New profile.
overridePage = Services.urlFormatter.formatURLPref("startup.homepage_welcome_url"); overridePage = Services.urlFormatter.formatURLPref("startup.homepage_welcome_url");
additionalPage = Services.urlFormatter.formatURLPref("startup.homepage_welcome_url.additional");
break; break;
case OVERRIDE_NEW_MSTONE: case OVERRIDE_NEW_MSTONE:
// Check whether we will restore a session. If we will, we assume // Check whether we will restore a session. If we will, we assume
@ -553,14 +556,21 @@ nsBrowserContentHandler.prototype = {
let firstUseOnWindows10URL = Services.urlFormatter.formatURLPref("browser.usedOnWindows10.introURL"); let firstUseOnWindows10URL = Services.urlFormatter.formatURLPref("browser.usedOnWindows10.introURL");
if (firstUseOnWindows10URL && firstUseOnWindows10URL.length) { if (firstUseOnWindows10URL && firstUseOnWindows10URL.length) {
if (overridePage) { additionalPage = firstUseOnWindows10URL;
overridePage += "|" + firstUseOnWindows10URL; if (override == OVERRIDE_NEW_PROFILE) {
} else { additionalPage += "&utm_content=firstrun";
overridePage = firstUseOnWindows10URL;
} }
} }
} }
if (additionalPage && additionalPage != "about:blank") {
if (overridePage) {
overridePage += "|" + additionalPage;
} else {
overridePage = additionalPage;
}
}
var startPage = ""; var startPage = "";
try { try {
var choice = prefb.getIntPref("browser.startup.page"); var choice = prefb.getIntPref("browser.startup.page");

View File

@ -3,8 +3,8 @@
"clang_version": "r247539" "clang_version": "r247539"
}, },
{ {
"size": 105219872, "size": 106877168,
"digest": "aa8de2fa535d0667e079019c475c631ea008f1bb5228505510867255b4d9c30663e2c97e579220a575a5887aa3bcf250021b50f76b90c2fa8c65a7aa19270066", "digest": "1c50c6348eaf429ed59bb603cff63bcc1f870f59216dd3c234db5b1156cfd351d5ee7b820ec31be4d2661eb4213b2e0030e2ba2782b42905d1ec19c7f8bd322a",
"algorithm": "sha512", "algorithm": "sha512",
"filename": "clang.tar.xz", "filename": "clang.tar.xz",
"unpack": true, "unpack": true,

View File

@ -3,8 +3,8 @@
"clang_version": "r247539" "clang_version": "r247539"
}, },
{ {
"size": 105219872, "size": 106877168,
"digest": "aa8de2fa535d0667e079019c475c631ea008f1bb5228505510867255b4d9c30663e2c97e579220a575a5887aa3bcf250021b50f76b90c2fa8c65a7aa19270066", "digest": "1c50c6348eaf429ed59bb603cff63bcc1f870f59216dd3c234db5b1156cfd351d5ee7b820ec31be4d2661eb4213b2e0030e2ba2782b42905d1ec19c7f8bd322a",
"algorithm": "sha512", "algorithm": "sha512",
"filename": "clang.tar.xz", "filename": "clang.tar.xz",
"unpack": true "unpack": true

View File

@ -3,8 +3,8 @@
"clang_version": "r247539" "clang_version": "r247539"
}, },
{ {
"size": 105219872, "size": 106877168,
"digest": "aa8de2fa535d0667e079019c475c631ea008f1bb5228505510867255b4d9c30663e2c97e579220a575a5887aa3bcf250021b50f76b90c2fa8c65a7aa19270066", "digest": "1c50c6348eaf429ed59bb603cff63bcc1f870f59216dd3c234db5b1156cfd351d5ee7b820ec31be4d2661eb4213b2e0030e2ba2782b42905d1ec19c7f8bd322a",
"algorithm": "sha512", "algorithm": "sha512",
"filename": "clang.tar.xz", "filename": "clang.tar.xz",
"unpack": true "unpack": true

View File

@ -3,8 +3,8 @@
"clang_version": "r247539" "clang_version": "r247539"
}, },
{ {
"size": 121393888, "size": 121389802,
"digest": "6ae4e651e545538e6de326a7fb8b44b6e6d0b3acdb6a969ecb2b6f63b9995bbad2111cabf044ba575464f17f1f948d78ec92ad3a6922a7bfdf3ad6b6b2cad050", "digest": "2be6b42cfa1e92de4b49a57123f54043fec2d3cf8385276516dc6aaed99c88768ac4aebd7ce2e007ab074163523da29223436a4d1aef82f0f750f08f1b14cd71",
"algorithm": "sha512", "algorithm": "sha512",
"filename": "clang.tar.bz2", "filename": "clang.tar.bz2",
"unpack": true "unpack": true

View File

@ -3,8 +3,8 @@
"clang_version": "r247539" "clang_version": "r247539"
}, },
{ {
"size": 105219872, "size": 106877168,
"digest": "aa8de2fa535d0667e079019c475c631ea008f1bb5228505510867255b4d9c30663e2c97e579220a575a5887aa3bcf250021b50f76b90c2fa8c65a7aa19270066", "digest": "1c50c6348eaf429ed59bb603cff63bcc1f870f59216dd3c234db5b1156cfd351d5ee7b820ec31be4d2661eb4213b2e0030e2ba2782b42905d1ec19c7f8bd322a",
"algorithm": "sha512", "algorithm": "sha512",
"filename": "clang.tar.xz", "filename": "clang.tar.xz",
"unpack": true "unpack": true

View File

@ -1,12 +1,12 @@
[ [
{ {
"clang_version": "r183744" "clang_version": "r247539"
}, },
{ {
"size": 59602619, "size": 97314461,
"digest": "86662ebc0ef650490559005948c4f0cb015dad72c7cac43732c2bf2995247081e30c139cf8008d19670a0009fc302c4eee2676981ee3f9ff4a15c01af22b783b", "digest": "9a74670fa917f760a4767923485d5166bbd258a8023c8aeb899b8c4d22f2847be76508ac5f26d7d2193318a2bb368a71bc62888d1bfe9d81eb45329a60451aa4",
"algorithm": "sha512", "algorithm": "sha512",
"filename": "clang.tar.bz2", "filename": "clang.tar.xz",
"unpack": true "unpack": true
}, },
{ {

View File

@ -189,6 +189,36 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY printButton.label "Print"> <!ENTITY printButton.label "Print">
<!ENTITY printButton.tooltip "Print this page"> <!ENTITY printButton.tooltip "Print this page">
<!ENTITY urlbar.viewSiteInfo.label "View site information">
<!-- LOCALIZATION NOTE: all of the following urlbar NotificationAnchor.label strings are
used to provide accessible labels to users of assistive technology like screenreaders.
It is not possible to see them visually in the UI. -->
<!ENTITY urlbar.defaultNotificationAnchor.label "View a notification">
<!ENTITY urlbar.geolocationNotificationAnchor.label "View the location request">
<!ENTITY urlbar.addonsNotificationAnchor.label "View the add-on install message">
<!ENTITY urlbar.indexedDBNotificationAnchor.label "View the app-offline storage message">
<!ENTITY urlbar.loginFillNotificationAnchor.label "Manage your login information">
<!ENTITY urlbar.passwordNotificationAnchor.label "Check if you want to save your password">
<!ENTITY urlbar.webappsNotificationAnchor.label "View the app install message">
<!ENTITY urlbar.pluginsNotificationAnchor.label "Manage plugin usage on this page">
<!ENTITY urlbar.webNotsNotificationAnchor.label "Change whether the site can show you notifications">
<!ENTITY urlbar.webRTCShareDevicesNotificationAnchor.label "Manage sharing your camera and/or microphone with the site">
<!ENTITY urlbar.webRTCSharingDevicesNotificationAnchor.label "You are sharing your camera and/or microphone with the site">
<!ENTITY urlbar.webRTCShareMicrophoneNotificationAnchor.label "Manage sharing your microphone with the site">
<!ENTITY urlbar.webRTCSharingMicrophoneNotificationAnchor.label "You are sharing your microphone with the site">
<!ENTITY urlbar.webRTCShareScreenNotificationAnchor.label "Manage sharing your windows or screen with the site">
<!ENTITY urlbar.webRTCSharingScreenNotificationAnchor.label "You are sharing a window or your screen with the site">
<!ENTITY urlbar.pointerLockNotificationAnchor.label "Change whether the site can hide the pointer">
<!ENTITY urlbar.servicesNotificationAnchor.label "View the service install message">
<!ENTITY urlbar.translateNotificationAnchor.label "Translate this page">
<!ENTITY urlbar.translatedNotificationAnchor.label "Manage page translation">
<!ENTITY urlbar.emeNotificationAnchor.label "Manage use of DRM software">
<!ENTITY urlbar.toggleAutocomplete.label "Toggle the autocomplete popup">
<!ENTITY locationItem.title "Location"> <!ENTITY locationItem.title "Location">
<!ENTITY searchItem.title "Search"> <!ENTITY searchItem.title "Search">

View File

@ -13,8 +13,20 @@
"cc": "/home/worker/workspace/build/src/gcc/bin/gcc", "cc": "/home/worker/workspace/build/src/gcc/bin/gcc",
"cxx": "/home/worker/workspace/build/src/gcc/bin/g++", "cxx": "/home/worker/workspace/build/src/gcc/bin/g++",
"patches": { "patches": {
"macosx64": ["llvm-debug-frame.patch"], "macosx64": [
"linux64": ["llvm-debug-frame.patch"], "llvm-debug-frame.patch",
"linux32": ["llvm-debug-frame.patch"] "query-selector-visibility.patch",
"return-empty-string-non-mangled.patch"
],
"linux64": [
"llvm-debug-frame.patch",
"query-selector-visibility.patch",
"return-empty-string-non-mangled.patch"
],
"linux32": [
"llvm-debug-frame.patch",
"query-selector-visibility.patch",
"return-empty-string-non-mangled.patch"
]
} }
} }

View File

@ -13,8 +13,20 @@
"cc": "/home/worker/workspace/build/src/gcc/bin/gcc", "cc": "/home/worker/workspace/build/src/gcc/bin/gcc",
"cxx": "/home/worker/workspace/build/src/gcc/bin/g++", "cxx": "/home/worker/workspace/build/src/gcc/bin/g++",
"patches": { "patches": {
"macosx64": ["llvm-debug-frame.patch"], "macosx64": [
"linux64": ["llvm-debug-frame.patch"], "llvm-debug-frame.patch",
"linux32": ["llvm-debug-frame.patch"] "query-selector-visibility.patch",
"return-empty-string-non-mangled.patch"
],
"linux64": [
"llvm-debug-frame.patch",
"query-selector-visibility.patch",
"return-empty-string-non-mangled.patch"
],
"linux32": [
"llvm-debug-frame.patch",
"query-selector-visibility.patch",
"return-empty-string-non-mangled.patch"
]
} }
} }

View File

@ -12,8 +12,20 @@
"cc": "/usr/bin/clang", "cc": "/usr/bin/clang",
"cxx": "/usr/bin/clang++", "cxx": "/usr/bin/clang++",
"patches": { "patches": {
"macosx64": ["llvm-debug-frame.patch"], "macosx64": [
"linux64": ["llvm-debug-frame.patch"], "llvm-debug-frame.patch",
"linux32": ["llvm-debug-frame.patch"] "query-selector-visibility.patch",
"return-empty-string-non-mangled.patch"
],
"linux64": [
"llvm-debug-frame.patch",
"query-selector-visibility.patch",
"return-empty-string-non-mangled.patch"
],
"linux32": [
"llvm-debug-frame.patch",
"query-selector-visibility.patch",
"return-empty-string-non-mangled.patch"
]
} }
} }

View File

@ -0,0 +1,79 @@
commit 865b9340996f9f9d04b73b187248737dc6fd845e
Author: Michael Wu <mwu@mozilla.com>
Date: Mon Sep 14 17:47:21 2015 -0400
Add support for querying the visibility of a cursor
diff --git a/llvm/tools/clang/include/clang-c/Index.h b/llvm/tools/clang/include/clang-c/Index.h
index fad9cfa..311bfcb 100644
--- a/llvm/tools/clang/include/clang-c/Index.h
+++ b/llvm/tools/clang/include/clang-c/Index.h
@@ -2440,6 +2440,24 @@ enum CXLinkageKind {
CINDEX_LINKAGE enum CXLinkageKind clang_getCursorLinkage(CXCursor cursor);
/**
+ * \brief Describe the visibility of the entity referred to by a cursor.
+ */
+enum CXVisibilityKind {
+ /** \brief This value indicates that no visibility information is available
+ * for a provided CXCursor. */
+ CXVisibility_Invalid,
+
+ /** \brief Symbol not seen by the linker. */
+ CXVisibility_Hidden,
+ /** \brief Symbol seen by the linker but resolves to a symbol inside this object. */
+ CXVisibility_Protected,
+ /** \brief Symbol seen by the linker and acts like a normal symbol. */
+ CXVisibility_Default,
+};
+
+CINDEX_LINKAGE enum CXVisibilityKind clang_getCursorVisibility(CXCursor cursor);
+
+/**
* \brief Determine the availability of the entity that this cursor refers to,
* taking the current target platform into account.
*
diff --git a/llvm/tools/clang/tools/libclang/CIndex.cpp b/llvm/tools/clang/tools/libclang/CIndex.cpp
index 8225a6c..9fa18d3 100644
--- a/llvm/tools/clang/tools/libclang/CIndex.cpp
+++ b/llvm/tools/clang/tools/libclang/CIndex.cpp
@@ -6361,6 +6361,27 @@ CXLinkageKind clang_getCursorLinkage(CXCursor cursor) {
} // end: extern "C"
//===----------------------------------------------------------------------===//
+// Operations for querying visibility of a cursor.
+//===----------------------------------------------------------------------===//
+
+extern "C" {
+CXVisibilityKind clang_getCursorVisibility(CXCursor cursor) {
+ if (!clang_isDeclaration(cursor.kind))
+ return CXVisibility_Invalid;
+
+ const Decl *D = cxcursor::getCursorDecl(cursor);
+ if (const NamedDecl *ND = dyn_cast_or_null<NamedDecl>(D))
+ switch (ND->getVisibility()) {
+ case HiddenVisibility: return CXVisibility_Hidden;
+ case ProtectedVisibility: return CXVisibility_Protected;
+ case DefaultVisibility: return CXVisibility_Default;
+ };
+
+ return CXVisibility_Invalid;
+}
+} // end: extern "C"
+
+//===----------------------------------------------------------------------===//
// Operations for querying language of a cursor.
//===----------------------------------------------------------------------===//
diff --git a/llvm/tools/clang/tools/libclang/libclang.exports b/llvm/tools/clang/tools/libclang/libclang.exports
index f6a7175..a919a8e 100644
--- a/llvm/tools/clang/tools/libclang/libclang.exports
+++ b/llvm/tools/clang/tools/libclang/libclang.exports
@@ -173,6 +173,7 @@ clang_getCursorSemanticParent
clang_getCursorSpelling
clang_getCursorType
clang_getCursorUSR
+clang_getCursorVisibility
clang_getDeclObjCTypeEncoding
clang_getDefinitionSpellingAndExtent
clang_getDiagnostic

View File

@ -0,0 +1,21 @@
commit 009de5ea7a1913f0b4619cf514787bd52af38c28
Author: Michael Wu <mwu@mozilla.com>
Date: Thu Sep 24 11:36:08 2015 -0400
Return an empty string when a symbol isn't mangled
diff --git a/llvm/tools/clang/tools/libclang/CIndex.cpp b/llvm/tools/clang/tools/libclang/CIndex.cpp
index 9fa18d3..1253832 100644
--- a/llvm/tools/clang/tools/libclang/CIndex.cpp
+++ b/llvm/tools/clang/tools/libclang/CIndex.cpp
@@ -3891,6 +3891,10 @@ CXString clang_Cursor_getMangling(CXCursor C) {
ASTContext &Ctx = ND->getASTContext();
std::unique_ptr<MangleContext> MC(Ctx.createMangleContext());
+ // Don't mangle if we don't need to.
+ if (!MC->shouldMangleCXXName(ND))
+ return cxstring::createEmpty();
+
std::string FrontendBuf;
llvm::raw_string_ostream FrontendBufOS(FrontendBuf);
MC->mangleName(ND, FrontendBufOS);

View File

@ -7,13 +7,14 @@ import subprocess
def get_all_toplevel_filenames(): def get_all_toplevel_filenames():
'''Get a list of all the files in the (Mercurial or Git) repository.''' '''Get a list of all the files in the (Mercurial or Git) repository.'''
failed_cmds = []
try: try:
cmd = ['hg', 'manifest', '-q'] cmd = ['hg', 'manifest', '-q']
all_filenames = subprocess.check_output(cmd, universal_newlines=True, all_filenames = subprocess.check_output(cmd, universal_newlines=True,
stderr=subprocess.PIPE).split('\n') stderr=subprocess.PIPE).split('\n')
return all_filenames return all_filenames
except: except:
pass failed_cmds.append(cmd)
try: try:
# Get the relative path to the top-level directory. # Get the relative path to the top-level directory.
@ -25,6 +26,6 @@ def get_all_toplevel_filenames():
stderr=subprocess.PIPE).split('\n') stderr=subprocess.PIPE).split('\n')
return all_filenames return all_filenames
except: except:
pass failed_cmds.append(cmd)
raise Exception('failed to run any of the repo manifest commands', cmds) raise Exception('failed to run any of the repo manifest commands', failed_cmds)

View File

@ -6,6 +6,7 @@
const { createStore, applyMiddleware } = require("devtools/client/shared/vendor/redux"); const { createStore, applyMiddleware } = require("devtools/client/shared/vendor/redux");
const { thunk } = require("./middleware/thunk"); const { thunk } = require("./middleware/thunk");
const { waitUntilService } = require("./middleware/wait-service"); const { waitUntilService } = require("./middleware/wait-service");
const { task } = require("./middleware/task");
const { log } = require("./middleware/log"); const { log } = require("./middleware/log");
const { promise } = require("./middleware/promise"); const { promise } = require("./middleware/promise");
@ -20,6 +21,7 @@ const { promise } = require("./middleware/promise");
*/ */
module.exports = (opts={}) => { module.exports = (opts={}) => {
const middleware = [ const middleware = [
task,
thunk, thunk,
waitUntilService, waitUntilService,
promise, promise,

View File

@ -7,6 +7,9 @@
DevToolsModules( DevToolsModules(
'log.js', 'log.js',
'promise.js', 'promise.js',
'task.js',
'thunk.js', 'thunk.js',
'wait-service.js', 'wait-service.js',
) )
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']

View File

@ -0,0 +1,42 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Task } = require("resource://gre/modules/Task.jsm");
const { executeSoon, isGenerator, isPromise, reportException } = require("devtools/shared/DevToolsUtils");
const ERROR_TYPE = exports.ERROR_TYPE = "@@redux/middleware/task#error";
/**
* A middleware that allows generator thunks (functions) and promise
* to be dispatched. If it's a generator, it is called with `dispatch` and `getState`,
* allowing the action to create multiple actions (most likely
* asynchronously) and yield on each. If called with a promise, calls `dispatch`
* on the results.
*/
function task ({ dispatch, getState }) {
return next => action => {
if (isGenerator(action)) {
return Task.spawn(action.bind(null, dispatch, getState))
.then(null, handleError.bind(null, dispatch));
}
/*
if (isPromise(action)) {
return action.then(dispatch, handleError.bind(null, dispatch));
}
*/
return next(action);
};
}
function handleError (dispatch, error) {
executeSoon(() => {
reportException(ERROR_TYPE, error);
dispatch({ type: ERROR_TYPE, error });
});
}
exports.task = task;

View File

@ -0,0 +1,27 @@
/* 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/. */
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
var { require } = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var promise = require("promise");
DevToolsUtils.testing = true;
function waitUntilState (store, predicate) {
let deferred = promise.defer();
let unsubscribe = store.subscribe(check);
function check () {
if (predicate(store.getState())) {
unsubscribe();
deferred.resolve()
}
}
// Fire the check immediately incase the action has already occurred
check();
return deferred.promise;
}

View File

@ -0,0 +1,52 @@
/* 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/. */
const { createStore, applyMiddleware } = require("devtools/client/shared/vendor/redux");
const { task } = require("devtools/client/shared/redux/middleware/task");
/**
* Tests that task middleware allows dispatching generators, promises and objects
* that return actions;
*/
function run_test() {
run_next_test();
}
add_task(function *() {
let store = applyMiddleware(task)(createStore)(reducer);
store.dispatch(fetch1("generator"));
yield waitUntilState(store, () => store.getState().length === 1);
equal(store.getState()[0].data, "generator", "task middleware async dispatches an action via generator");
store.dispatch(fetch2("sync"));
yield waitUntilState(store, () => store.getState().length === 2);
equal(store.getState()[1].data, "sync", "task middleware sync dispatches an action via sync");
});
function fetch1 (data) {
return function *(dispatch, getState) {
equal(getState().length, 0, "`getState` is accessible in a generator action");
let moreData = yield new Promise(resolve => resolve(data));
// Ensure it handles more than one yield
moreData = yield new Promise(resolve => resolve(data));
dispatch({ type: "fetch1", data: moreData });
};
}
function fetch2 (data) {
return {
type: "fetch2",
data
}
}
function reducer (state=[], action) {
do_print("Action called: " + action.type);
if (["fetch1", "fetch2"].includes(action.type)) {
state.push(action);
}
return [...state];
}

View File

@ -0,0 +1,59 @@
/* 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/. */
/**
* Tests that task middleware allows dispatching generators that dispatch
* additional sync and async actions.
*/
const { createStore, applyMiddleware } = require("devtools/client/shared/vendor/redux");
const { task } = require("devtools/client/shared/redux/middleware/task");
function run_test() {
run_next_test();
}
add_task(function *() {
let store = applyMiddleware(task)(createStore)(reducer);
store.dispatch(comboAction());
yield waitUntilState(store, () => store.getState().length === 3);
equal(store.getState()[0].type, "fetchAsync-start", "Async dispatched actions in a generator task are fired");
equal(store.getState()[1].type, "fetchAsync-end", "Async dispatched actions in a generator task are fired");
equal(store.getState()[2].type, "fetchSync", "Return values of yielded sync dispatched actions are correct");
equal(store.getState()[3].type, "fetch-done", "Return values of yielded async dispatched actions are correct");
equal(store.getState()[3].data.sync.data, "sync", "Return values of dispatched sync values are correct");
equal(store.getState()[3].data.async, "async", "Return values of dispatched async values are correct");
});
function comboAction () {
return function *(dispatch, getState) {
let data = {};
data.async = yield dispatch(fetchAsync("async"));
data.sync = yield dispatch(fetchSync("sync"));
dispatch({ type: "fetch-done", data });
}
}
function fetchSync (data) {
return { type: "fetchSync", data };
}
function fetchAsync (data) {
return function *(dispatch) {
dispatch({ type: "fetchAsync-start" });
let val = yield new Promise(resolve => resolve(data));
dispatch({ type: "fetchAsync-end" });
return val;
};
}
function reducer (state=[], action) {
do_print("Action called: " + action.type);
if (/fetch/.test(action.type)) {
state.push(action);
}
return [...state];
}

View File

@ -0,0 +1,37 @@
/* 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/. */
const { createStore, applyMiddleware } = require("devtools/client/shared/vendor/redux");
const { task, ERROR_TYPE } = require("devtools/client/shared/redux/middleware/task");
/**
* Tests that the middleware handles errors thrown in tasks, and rejected promises.
*/
function run_test() {
run_next_test();
}
add_task(function *() {
let store = applyMiddleware(task)(createStore)(reducer);
store.dispatch(generatorError());
yield waitUntilState(store, () => store.getState().length === 1);
equal(store.getState()[0].type, ERROR_TYPE, "generator errors dispatch ERROR_TYPE actions");
equal(store.getState()[0].error, "task-middleware-error-generator", "generator errors dispatch ERROR_TYPE actions with error");
});
function generatorError () {
return function *(dispatch, getState) {
throw "task-middleware-error-generator";
};
}
function reducer (state=[], action) {
do_print("Action called: " + action.type);
if (action.type === ERROR_TYPE) {
state.push(action);
}
return [...state];
}

View File

@ -0,0 +1,10 @@
[DEFAULT]
tags = devtools
head = head.js
tail =
firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_middleware-task-01.js]
[test_middleware-task-02.js]
[test_middleware-task-03.js]

View File

@ -1,4 +1,4 @@
@namespace url("images/http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
scrollbar { scrollbar {
-moz-appearance: none !important; -moz-appearance: none !important;

View File

@ -755,3 +755,11 @@ exports.openFileStream = function (filePath) {
); );
}); });
} }
exports.isGenerator = function (fn) {
return typeof fn === "function" && fn.isGenerator();
};
exports.isPromise = function (p) {
return p && typeof p.then === "function";
};

View File

@ -37,6 +37,7 @@
#include "mozilla/dom/WindowBinding.h" #include "mozilla/dom/WindowBinding.h"
#include "mozilla/dom/ElementBinding.h" #include "mozilla/dom/ElementBinding.h"
#include "Units.h" #include "Units.h"
#include "nsContentListDeclarations.h"
class nsIFrame; class nsIFrame;
class nsIDOMMozNamedAttrMap; class nsIDOMMozNamedAttrMap;
@ -61,11 +62,6 @@ namespace dom {
} // namespace mozilla } // namespace mozilla
already_AddRefed<nsContentList>
NS_GetContentList(nsINode* aRootNode,
int32_t aMatchNameSpaceId,
const nsAString& aTagname);
#define ELEMENT_FLAG_BIT(n_) NODE_FLAG_BIT(NODE_TYPE_SPECIFIC_BITS_OFFSET + (n_)) #define ELEMENT_FLAG_BIT(n_) NODE_FLAG_BIT(NODE_TYPE_SPECIFIC_BITS_OFFSET + (n_))
// Element-specific flags // Element-specific flags

View File

@ -198,7 +198,8 @@ NS_GetContentList(nsINode* aRootNode,
NS_ASSERTION(aRootNode, "content list has to have a root"); NS_ASSERTION(aRootNode, "content list has to have a root");
nsRefPtr<nsContentList> list; nsRefPtr<nsContentList> list;
nsContentListKey hashKey(aRootNode, aMatchNameSpaceId, aTagname); nsContentListKey hashKey(aRootNode, aMatchNameSpaceId, aTagname,
aRootNode->OwnerDoc()->IsHTMLDocument());
uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(hashKey); uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(hashKey);
nsContentList* cachedList = sRecentlyUsedContentLists[recentlyUsedCacheIndex]; nsContentList* cachedList = sRecentlyUsedContentLists[recentlyUsedCacheIndex];
if (cachedList && cachedList->MatchesKey(hashKey)) { if (cachedList && cachedList->MatchesKey(hashKey)) {
@ -398,7 +399,8 @@ nsContentList::nsContentList(nsINode* aRootNode,
mData(nullptr), mData(nullptr),
mState(LIST_DIRTY), mState(LIST_DIRTY),
mDeep(aDeep), mDeep(aDeep),
mFuncMayDependOnAttr(false) mFuncMayDependOnAttr(false),
mIsHTMLDocument(aRootNode->OwnerDoc()->IsHTMLDocument())
{ {
NS_ASSERTION(mRootNode, "Must have root"); NS_ASSERTION(mRootNode, "Must have root");
if (nsGkAtoms::_asterisk == mHTMLMatchAtom) { if (nsGkAtoms::_asterisk == mHTMLMatchAtom) {
@ -438,7 +440,8 @@ nsContentList::nsContentList(nsINode* aRootNode,
mState(LIST_DIRTY), mState(LIST_DIRTY),
mMatchAll(false), mMatchAll(false),
mDeep(aDeep), mDeep(aDeep),
mFuncMayDependOnAttr(aFuncMayDependOnAttr) mFuncMayDependOnAttr(aFuncMayDependOnAttr),
mIsHTMLDocument(false)
{ {
NS_ASSERTION(mRootNode, "Must have root"); NS_ASSERTION(mRootNode, "Must have root");
mRootNode->AddMutationObserver(this); mRootNode->AddMutationObserver(this);
@ -839,25 +842,20 @@ nsContentList::Match(Element *aElement)
if (!mXMLMatchAtom) if (!mXMLMatchAtom)
return false; return false;
mozilla::dom::NodeInfo *ni = aElement->NodeInfo(); NodeInfo *ni = aElement->NodeInfo();
bool unknown = mMatchNameSpaceId == kNameSpaceID_Unknown; bool wildcard = mMatchNameSpaceId == kNameSpaceID_Wildcard ||
bool wildcard = mMatchNameSpaceId == kNameSpaceID_Wildcard; mMatchNameSpaceId == kNameSpaceID_Unknown;
bool toReturn = mMatchAll; bool toReturn = mMatchAll;
if (!unknown && !wildcard) if (!wildcard)
toReturn &= ni->NamespaceEquals(mMatchNameSpaceId); toReturn &= ni->NamespaceEquals(mMatchNameSpaceId);
if (toReturn) if (toReturn)
return toReturn; return toReturn;
bool matchHTML = aElement->GetNameSpaceID() == kNameSpaceID_XHTML && bool matchHTML =
aElement->OwnerDoc()->IsHTMLDocument(); mIsHTMLDocument && aElement->GetNameSpaceID() == kNameSpaceID_XHTML;
if (unknown) {
return matchHTML ? ni->QualifiedNameEquals(mHTMLMatchAtom) :
ni->QualifiedNameEquals(mXMLMatchAtom);
}
if (wildcard) { if (wildcard) {
return matchHTML ? ni->Equals(mHTMLMatchAtom) : return matchHTML ? ni->Equals(mHTMLMatchAtom) :
ni->Equals(mXMLMatchAtom); ni->Equals(mXMLMatchAtom);
@ -962,7 +960,7 @@ nsContentList::RemoveFromHashtable()
} }
nsDependentAtomString str(mXMLMatchAtom); nsDependentAtomString str(mXMLMatchAtom);
nsContentListKey key(mRootNode, mMatchNameSpaceId, str); nsContentListKey key(mRootNode, mMatchNameSpaceId, str, mIsHTMLDocument);
uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(key); uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(key);
if (sRecentlyUsedContentLists[recentlyUsedCacheIndex] == this) { if (sRecentlyUsedContentLists[recentlyUsedCacheIndex] == this) {
sRecentlyUsedContentLists[recentlyUsedCacheIndex] = nullptr; sRecentlyUsedContentLists[recentlyUsedCacheIndex] = nullptr;

View File

@ -142,14 +142,21 @@ private:
*/ */
struct nsContentListKey struct nsContentListKey
{ {
// We have to take an aIsHTMLDocument arg for two reasons:
// 1) We don't want to include nsIDocument.h in this header.
// 2) We need to do that to make nsContentList::RemoveFromHashtable
// work, because by the time it's called the document of the
// list's root node might have changed.
nsContentListKey(nsINode* aRootNode, nsContentListKey(nsINode* aRootNode,
int32_t aMatchNameSpaceId, int32_t aMatchNameSpaceId,
const nsAString& aTagname) const nsAString& aTagname,
bool aIsHTMLDocument)
: mRootNode(aRootNode), : mRootNode(aRootNode),
mMatchNameSpaceId(aMatchNameSpaceId), mMatchNameSpaceId(aMatchNameSpaceId),
mTagname(aTagname), mTagname(aTagname),
mIsHTMLDocument(aIsHTMLDocument),
mHash(mozilla::AddToHash(mozilla::HashString(aTagname), mRootNode, mHash(mozilla::AddToHash(mozilla::HashString(aTagname), mRootNode,
mMatchNameSpaceId)) mMatchNameSpaceId, mIsHTMLDocument))
{ {
} }
@ -157,6 +164,7 @@ struct nsContentListKey
: mRootNode(aContentListKey.mRootNode), : mRootNode(aContentListKey.mRootNode),
mMatchNameSpaceId(aContentListKey.mMatchNameSpaceId), mMatchNameSpaceId(aContentListKey.mMatchNameSpaceId),
mTagname(aContentListKey.mTagname), mTagname(aContentListKey.mTagname),
mIsHTMLDocument(aContentListKey.mIsHTMLDocument),
mHash(aContentListKey.mHash) mHash(aContentListKey.mHash)
{ {
} }
@ -169,6 +177,7 @@ struct nsContentListKey
nsINode* const mRootNode; // Weak ref nsINode* const mRootNode; // Weak ref
const int32_t mMatchNameSpaceId; const int32_t mMatchNameSpaceId;
const nsAString& mTagname; const nsAString& mTagname;
bool mIsHTMLDocument;
const uint32_t mHash; const uint32_t mHash;
}; };
@ -209,7 +218,7 @@ public:
* The special value "*" always matches whatever aMatchAtom * The special value "*" always matches whatever aMatchAtom
* is matched against. * is matched against.
* @param aMatchNameSpaceId If kNameSpaceID_Unknown, then aMatchAtom is the * @param aMatchNameSpaceId If kNameSpaceID_Unknown, then aMatchAtom is the
* tagName to match. * localName to match.
* If kNameSpaceID_Wildcard, then aMatchAtom is the * If kNameSpaceID_Wildcard, then aMatchAtom is the
* localName to match. * localName to match.
* Otherwise we match nodes whose namespace is * Otherwise we match nodes whose namespace is
@ -237,7 +246,8 @@ public:
* deeper. If true, then look at the whole subtree rooted at * deeper. If true, then look at the whole subtree rooted at
* our root. * our root.
* @param aMatchAtom an atom to be passed back to aFunc * @param aMatchAtom an atom to be passed back to aFunc
* @param aMatchNameSpaceId a namespace id to be passed back to aFunc * @param aMatchNameSpaceId a namespace id to be passed back to aFunc. Is
allowed to be kNameSpaceID_Unknown.
* @param aFuncMayDependOnAttr a boolean that indicates whether this list is * @param aFuncMayDependOnAttr a boolean that indicates whether this list is
* sensitive to attribute changes. * sensitive to attribute changes.
*/ */
@ -318,13 +328,15 @@ public:
{ {
// The root node is most commonly the same: the document. And the // The root node is most commonly the same: the document. And the
// most common namespace id is kNameSpaceID_Unknown. So check the // most common namespace id is kNameSpaceID_Unknown. So check the
// string first. // string first. Cases in which whether our root's ownerDocument
// is HTML changes are extremely rare, so check those last.
NS_PRECONDITION(mXMLMatchAtom, NS_PRECONDITION(mXMLMatchAtom,
"How did we get here with a null match atom on our list?"); "How did we get here with a null match atom on our list?");
return return
mXMLMatchAtom->Equals(aKey.mTagname) && mXMLMatchAtom->Equals(aKey.mTagname) &&
mRootNode == aKey.mRootNode && mRootNode == aKey.mRootNode &&
mMatchNameSpaceId == aKey.mMatchNameSpaceId; mMatchNameSpaceId == aKey.mMatchNameSpaceId &&
mIsHTMLDocument == aKey.mIsHTMLDocument;
} }
/** /**
@ -445,6 +457,12 @@ protected:
* Whether we actually need to flush to get our state correct. * Whether we actually need to flush to get our state correct.
*/ */
uint8_t mFlushesNeeded : 1; uint8_t mFlushesNeeded : 1;
/**
* Whether the ownerDocument of our root node at list creation time was an
* HTML document. Only needed when we're doing a namespace/atom match, not
* when doing function matching, always false otherwise.
*/
uint8_t mIsHTMLDocument : 1;
#ifdef DEBUG_CONTENT_LIST #ifdef DEBUG_CONTENT_LIST
void AssertInSync(); void AssertInSync();

View File

@ -9,12 +9,14 @@
#include <stdint.h> #include <stdint.h>
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsStringFwd.h"
class nsContentList; class nsContentList;
class nsIAtom; class nsIAtom;
class nsIContent; class nsIContent;
class nsINode; class nsINode;
// Can't use nsStringFwd.h because that's internal-API-only.
class nsString;
class nsAString;
// Magic namespace id that means "match all namespaces". This is // Magic namespace id that means "match all namespaces". This is
// negative so it won't collide with actual namespace constants. // negative so it won't collide with actual namespace constants.
@ -42,8 +44,9 @@ typedef void* (*nsFuncStringContentListDataAllocator)(nsINode* aRootNode,
// If aMatchNameSpaceId is kNameSpaceID_Unknown, this will return a // If aMatchNameSpaceId is kNameSpaceID_Unknown, this will return a
// content list which matches ASCIIToLower(aTagname) against HTML // content list which matches ASCIIToLower(aTagname) against HTML
// elements in HTML documents and aTagname against everything else. // elements in HTML documents and aTagname against everything else.
// For any other value of aMatchNameSpaceId, the list will match // The comparison is done to the element's localName. For any
// aTagname against all elements. // other value of aMatchNameSpaceId, the list will match aTagname
// against all elements, again comparing to the localName.
already_AddRefed<nsContentList> already_AddRefed<nsContentList>
NS_GetContentList(nsINode* aRootNode, NS_GetContentList(nsINode* aRootNode,
int32_t aMatchNameSpaceId, int32_t aMatchNameSpaceId,

View File

@ -28,6 +28,7 @@
#include "mozilla/UseCounter.h" #include "mozilla/UseCounter.h"
#include "mozilla/WeakPtr.h" #include "mozilla/WeakPtr.h"
#include "Units.h" #include "Units.h"
#include "nsContentListDeclarations.h"
#include "nsExpirationTracker.h" #include "nsExpirationTracker.h"
#include "nsClassHashtable.h" #include "nsClassHashtable.h"
#include "prclist.h" #include "prclist.h"
@ -176,11 +177,6 @@ enum DocumentFlavor {
// Some function forward-declarations // Some function forward-declarations
class nsContentList; class nsContentList;
already_AddRefed<nsContentList>
NS_GetContentList(nsINode* aRootNode,
int32_t aMatchNameSpaceId,
const nsAString& aTagname);
//---------------------------------------------------------------------- //----------------------------------------------------------------------
// Document interface. This is implemented by all document objects in // Document interface. This is implemented by all document objects in

View File

@ -32,7 +32,7 @@ function test_getElementsByTagName()
do_check_eq(doc.getElementById("test2").getElementsByTagName("*").length, do_check_eq(doc.getElementById("test2").getElementsByTagName("*").length,
8); 8);
do_check_eq(doc.getElementById("test2").getElementsByTagName("test").length, do_check_eq(doc.getElementById("test2").getElementsByTagName("test").length,
3); 7);
// Check that the first element of getElementsByTagName on the document is // Check that the first element of getElementsByTagName on the document is
// the right thing. // the right thing.
@ -40,7 +40,7 @@ function test_getElementsByTagName()
// Check that we get the right things in the right order // Check that we get the right things in the right order
var numTests = doc.getElementsByTagName("test").length; var numTests = doc.getElementsByTagName("test").length;
do_check_eq(numTests, 5); do_check_eq(numTests, 14);
for (var i = 1; i <= numTests; ++i) { for (var i = 1; i <= numTests; ++i) {
do_check_true(doc.getElementById("test" + i) instanceof nsIDOMElement); do_check_true(doc.getElementById("test" + i) instanceof nsIDOMElement);
@ -51,15 +51,15 @@ function test_getElementsByTagName()
// Check that we handle tagnames containing ':' correctly // Check that we handle tagnames containing ':' correctly
do_check_true(doc.getElementsByTagName("foo:test") do_check_true(doc.getElementsByTagName("foo:test")
instanceof nsIDOMNodeList); instanceof nsIDOMNodeList);
do_check_eq(doc.getElementsByTagName("foo:test").length, 2); do_check_eq(doc.getElementsByTagName("foo:test").length, 0);
do_check_true(doc.getElementsByTagName("foo2:test") do_check_true(doc.getElementsByTagName("foo2:test")
instanceof nsIDOMNodeList); instanceof nsIDOMNodeList);
do_check_eq(doc.getElementsByTagName("foo2:test").length, 3); do_check_eq(doc.getElementsByTagName("foo2:test").length, 0);
do_check_true(doc.getElementsByTagName("bar:test") do_check_true(doc.getElementsByTagName("bar:test")
instanceof nsIDOMNodeList); instanceof nsIDOMNodeList);
do_check_eq(doc.getElementsByTagName("bar:test").length, 4); do_check_eq(doc.getElementsByTagName("bar:test").length, 0);
} }
function test_getElementsByTagNameNS() function test_getElementsByTagNameNS()

View File

@ -700,6 +700,10 @@ DOMInterfaces = {
'notflattened': True 'notflattened': True
}, },
'IterableIterator': {
'skipGen': True
},
'KeyEvent': { 'KeyEvent': {
'concrete': False 'concrete': False
}, },

View File

@ -1121,7 +1121,10 @@ class CGHeaders(CGWrapper):
# Now for non-callback descriptors make sure we include any # Now for non-callback descriptors make sure we include any
# headers needed by Func declarations. # headers needed by Func declarations.
for desc in descriptors: for desc in descriptors:
if desc.interface.isExternal(): # If this is an iterator interface generated for a seperate
# iterable interface, skip generating type includes, as we have
# what we need in IterableIterator.h
if desc.interface.isExternal() or desc.interface.isIteratorInterface():
continue continue
def addHeaderForFunc(func): def addHeaderForFunc(func):
@ -1148,16 +1151,15 @@ class CGHeaders(CGWrapper):
if funcList is not None: if funcList is not None:
addHeaderForFunc(funcList[0]) addHeaderForFunc(funcList[0])
for desc in descriptors: if desc.interface.maplikeOrSetlikeOrIterable:
if desc.interface.maplikeOrSetlike:
# We need ToJSValue.h for maplike/setlike type conversions # We need ToJSValue.h for maplike/setlike type conversions
bindingHeaders.add("mozilla/dom/ToJSValue.h") bindingHeaders.add("mozilla/dom/ToJSValue.h")
# Add headers for the key and value types of the maplike, since # Add headers for the key and value types of the maplike, since
# they'll be needed for convenience functions # they'll be needed for convenience functions
addHeadersForType((desc.interface.maplikeOrSetlike.keyType, addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.keyType,
desc, None)) desc, None))
if desc.interface.maplikeOrSetlike.valueType: if desc.interface.maplikeOrSetlikeOrIterable.valueType:
addHeadersForType((desc.interface.maplikeOrSetlike.valueType, addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.valueType,
desc, None)) desc, None))
for d in dictionaries: for d in dictionaries:
@ -2197,13 +2199,18 @@ class MethodDefiner(PropertyDefiner):
}) })
continue continue
# Iterable methods should be enumerable, maplike/setlike methods
# should not.
isMaplikeOrSetlikeMethod = (m.isMaplikeOrSetlikeOrIterableMethod() and
(m.maplikeOrSetlikeOrIterable.isMaplike() or
m.maplikeOrSetlikeOrIterable.isSetlike()))
method = { method = {
"name": m.identifier.name, "name": m.identifier.name,
"methodInfo": not m.isStatic(), "methodInfo": not m.isStatic(),
"length": methodLength(m), "length": methodLength(m),
# Methods generated for a maplike/setlike declaration are not # Methods generated for a maplike/setlike declaration are not
# enumerable. # enumerable.
"flags": "JSPROP_ENUMERATE" if not m.isMaplikeOrSetlikeMethod() else "0", "flags": "JSPROP_ENUMERATE" if not isMaplikeOrSetlikeMethod else "0",
"condition": PropertyDefiner.getControllingCondition(m, descriptor), "condition": PropertyDefiner.getControllingCondition(m, descriptor),
"allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"), "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
"returnsPromise": m.returnsPromise(), "returnsPromise": m.returnsPromise(),
@ -2247,19 +2254,23 @@ class MethodDefiner(PropertyDefiner):
# Generate the maplike/setlike iterator, if one wasn't already # Generate the maplike/setlike iterator, if one wasn't already
# generated by a method. If we already have an @@iterator symbol, fail. # generated by a method. If we already have an @@iterator symbol, fail.
if descriptor.interface.maplikeOrSetlike: if descriptor.interface.maplikeOrSetlikeOrIterable:
if hasIterator(methods, self.regular): if hasIterator(methods, self.regular):
raise TypeError("Cannot have maplike/setlike interface with " raise TypeError("Cannot have maplike/setlike/iterable interface with "
"other members that generate @@iterator " "other members that generate @@iterator "
"on interface %s, such as indexed getters " "on interface %s, such as indexed getters "
"or aliased functions." % "or aliased functions." %
self.descriptor.interface.identifier.name) self.descriptor.interface.identifier.name)
for m in methods: for m in methods:
if (m.isMaplikeOrSetlikeMethod() and if (m.isMaplikeOrSetlikeOrIterableMethod() and
((m.maplikeOrSetlike.isMaplike() and (((m.maplikeOrSetlikeOrIterable.isMaplike() or
m.identifier.name == "entries") or (m.maplikeOrSetlikeOrIterable.isIterable() and
(m.maplikeOrSetlike.isSetlike() and m.maplikeOrSetlikeOrIterable.hasValueType())) and
m.identifier.name == "values"))): m.identifier.name == "entries") or
(((m.maplikeOrSetlikeOrIterable.isSetlike() or
(m.maplikeOrSetlikeOrIterable.isIterable() and
not m.maplikeOrSetlikeOrIterable.hasValueType()))) and
m.identifier.name == "values"))):
self.regular.append({ self.regular.append({
"name": "@@iterator", "name": "@@iterator",
"methodName": m.identifier.name, "methodName": m.identifier.name,
@ -5810,8 +5821,10 @@ class CGArgumentConverter(CGThing):
# If we have a method generated by the maplike/setlike portion of an # If we have a method generated by the maplike/setlike portion of an
# interface, arguments can possibly be undefined, but will need to be # interface, arguments can possibly be undefined, but will need to be
# converted to the key/value type of the backing object. In this case, # converted to the key/value type of the backing object. In this case,
# use .get() instead of direct access to the argument. # use .get() instead of direct access to the argument. This won't
if member.isMethod() and member.isMaplikeOrSetlikeMethod(): # matter for iterable since generated functions for those interface
# don't take arguments.
if member.isMethod() and member.isMaplikeOrSetlikeOrIterableMethod():
self.replacementVariables["val"] = string.Template( self.replacementVariables["val"] = string.Template(
"args.get(${index})").substitute(replacer) "args.get(${index})").substitute(replacer)
else: else:
@ -7111,10 +7124,16 @@ class CGPerSignatureCall(CGThing):
# If this is a method that was generated by a maplike/setlike # If this is a method that was generated by a maplike/setlike
# interface, use the maplike/setlike generator to fill in the body. # interface, use the maplike/setlike generator to fill in the body.
# Otherwise, use CGCallGenerator to call the native method. # Otherwise, use CGCallGenerator to call the native method.
if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeMethod(): if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeOrIterableMethod():
cgThings.append(CGMaplikeOrSetlikeMethodGenerator(descriptor, if (idlNode.maplikeOrSetlikeOrIterable.isMaplike() or
idlNode.maplikeOrSetlike, idlNode.maplikeOrSetlikeOrIterable.isSetlike()):
idlNode.identifier.name)) cgThings.append(CGMaplikeOrSetlikeMethodGenerator(descriptor,
idlNode.maplikeOrSetlikeOrIterable,
idlNode.identifier.name))
else:
cgThings.append(CGIterableMethodGenerator(descriptor,
idlNode.maplikeOrSetlikeOrIterable,
idlNode.identifier.name))
else: else:
cgThings.append(CGCallGenerator( cgThings.append(CGCallGenerator(
self.getErrorReport() if self.isFallible() else None, self.getErrorReport() if self.isFallible() else None,
@ -7349,7 +7368,7 @@ class CGMethodCall(CGThing):
# Skip required arguments check for maplike/setlike interfaces, as # Skip required arguments check for maplike/setlike interfaces, as
# they can have arguments which are not passed, and are treated as # they can have arguments which are not passed, and are treated as
# if undefined had been explicitly passed. # if undefined had been explicitly passed.
if requiredArgs > 0 and not method.isMaplikeOrSetlikeMethod(): if requiredArgs > 0 and not method.isMaplikeOrSetlikeOrIterableMethod():
code = fill( code = fill(
""" """
if (MOZ_UNLIKELY(args.length() < ${requiredArgs})) { if (MOZ_UNLIKELY(args.length() < ${requiredArgs})) {
@ -12779,6 +12798,10 @@ class CGForwardDeclarations(CGWrapper):
# Needed for at least Wrap. # Needed for at least Wrap.
for d in descriptors: for d in descriptors:
# If this is a generated iterator interface, we only create these
# in the generated bindings, and don't need to forward declare.
if d.interface.isIteratorInterface():
continue
builder.add(d.nativeType) builder.add(d.nativeType)
# If we're an interface and we have a maplike/setlike declaration, # If we're an interface and we have a maplike/setlike declaration,
# we'll have helper functions exposed to the native side of our # we'll have helper functions exposed to the native side of our
@ -12786,17 +12809,23 @@ class CGForwardDeclarations(CGWrapper):
# our key/value types are interfaces, they'll be passed as # our key/value types are interfaces, they'll be passed as
# arguments to helper functions, and they'll need to be forward # arguments to helper functions, and they'll need to be forward
# declared in the header. # declared in the header.
if d.interface.maplikeOrSetlike: if d.interface.maplikeOrSetlikeOrIterable:
builder.forwardDeclareForType(d.interface.maplikeOrSetlike.keyType, builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.keyType,
config)
builder.forwardDeclareForType(d.interface.maplikeOrSetlike.valueType,
config) config)
if d.interface.maplikeOrSetlikeOrIterable.hasValueType():
builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.valueType,
config)
# We just about always need NativePropertyHooks # We just about always need NativePropertyHooks
builder.addInMozillaDom("NativePropertyHooks", isStruct=True) builder.addInMozillaDom("NativePropertyHooks", isStruct=True)
builder.addInMozillaDom("ProtoAndIfaceCache") builder.addInMozillaDom("ProtoAndIfaceCache")
# Add the atoms cache type, even if we don't need it. # Add the atoms cache type, even if we don't need it.
for d in descriptors: for d in descriptors:
# Iterators have native types that are template classes, so
# creating an 'Atoms' cache type doesn't work for them, and is one
# of the cases where we don't need it anyways.
if d.interface.isIteratorInterface():
continue
builder.add(d.nativeType + "Atoms", isStruct=True) builder.add(d.nativeType + "Atoms", isStruct=True)
for callback in mainCallbacks: for callback in mainCallbacks:
@ -12859,6 +12888,8 @@ class CGBindingRoot(CGThing):
bindingDeclareHeaders["mozilla/dom/UnionMember.h"] = len(unionStructs) > 0 bindingDeclareHeaders["mozilla/dom/UnionMember.h"] = len(unionStructs) > 0
bindingDeclareHeaders["mozilla/dom/BindingUtils.h"] = len(unionStructs) > 0 bindingDeclareHeaders["mozilla/dom/BindingUtils.h"] = len(unionStructs) > 0
bindingDeclareHeaders["mozilla/dom/IterableIterator.h"] = any(d.interface.isIteratorInterface() or
d.interface.isIterable() for d in descriptors)
def descriptorHasCrossOriginProperties(desc): def descriptorHasCrossOriginProperties(desc):
def hasCrossOriginProperty(m): def hasCrossOriginProperty(m):
@ -12922,14 +12953,6 @@ class CGBindingRoot(CGThing):
bindingHeaders["nsIGlobalObject.h"] = jsImplemented bindingHeaders["nsIGlobalObject.h"] = jsImplemented
bindingHeaders["AtomList.h"] = hasNonEmptyDictionaries or jsImplemented or callbackDescriptors bindingHeaders["AtomList.h"] = hasNonEmptyDictionaries or jsImplemented or callbackDescriptors
def addHeaderBasedOnTypes(header, typeChecker):
bindingHeaders[header] = (
bindingHeaders.get(header, False) or
any(map(typeChecker,
getAllTypes(descriptors + callbackDescriptors,
dictionaries,
mainCallbacks + workerCallbacks))))
# Only mainthread things can have hasXPConnectImpls # Only mainthread things can have hasXPConnectImpls
provider = config.getDescriptorProvider(False) provider = config.getDescriptorProvider(False)
@ -14966,7 +14989,7 @@ def getMaplikeOrSetlikeBackingObject(descriptor, maplikeOrSetlike, helperImpl=No
Generate code to get/create a JS backing object for a maplike/setlike Generate code to get/create a JS backing object for a maplike/setlike
declaration from the declaration slot. declaration from the declaration slot.
""" """
func_prefix = maplikeOrSetlike.maplikeOrSetlikeType.title() func_prefix = maplikeOrSetlike.maplikeOrSetlikeOrIterableType.title()
ret = fill( ret = fill(
""" """
JS::Rooted<JSObject*> backingObj(cx); JS::Rooted<JSObject*> backingObj(cx);
@ -15403,8 +15426,11 @@ class CGMaplikeOrSetlikeHelperGenerator(CGNamespace):
""" """
def __init__(self, descriptor, maplikeOrSetlike): def __init__(self, descriptor, maplikeOrSetlike):
self.descriptor = descriptor self.descriptor = descriptor
# Since iterables are folded in with maplike/setlike, make sure we've
# got the right type here.
assert maplikeOrSetlike.isMaplike() or maplikeOrSetlike.isSetlike()
self.maplikeOrSetlike = maplikeOrSetlike self.maplikeOrSetlike = maplikeOrSetlike
self.namespace = "%sHelpers" % (self.maplikeOrSetlike.maplikeOrSetlikeType.title()) self.namespace = "%sHelpers" % (self.maplikeOrSetlike.maplikeOrSetlikeOrIterableType.title())
self.helpers = [ self.helpers = [
CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor, CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
maplikeOrSetlike, maplikeOrSetlike,
@ -15436,6 +15462,26 @@ class CGMaplikeOrSetlikeHelperGenerator(CGNamespace):
CGNamespace.__init__(self, self.namespace, CGList(self.helpers)) CGNamespace.__init__(self, self.namespace, CGList(self.helpers))
class CGIterableMethodGenerator(CGGeneric):
"""
Creates methods for iterable interfaces. Unwrapping/wrapping
will be taken care of by the usual method generation machinery in
CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of
using CGCallGenerator.
"""
def __init__(self, descriptor, iterable, methodName):
CGGeneric.__init__(self, fill(
"""
typedef IterableIterator<${nativeType}> itrType;
nsRefPtr<itrType> result(new itrType(self,
itrType::IterableIteratorType::${itrMethod},
&${ifaceName}IteratorBinding::Wrap));
""",
nativeType=descriptor.nativeType,
ifaceName=descriptor.interface.identifier.name,
itrMethod=methodName.title()))
class GlobalGenRoots(): class GlobalGenRoots():
""" """
Roots for global codegen. Roots for global codegen.

View File

@ -26,6 +26,7 @@ class Configuration:
# |parseData|. # |parseData|.
self.descriptors = [] self.descriptors = []
self.interfaces = {} self.interfaces = {}
self.descriptorsByName = {}
self.optimizedOutDescriptorNames = set() self.optimizedOutDescriptorNames = set()
self.generatedEvents = generatedEvents self.generatedEvents = generatedEvents
self.maxProtoChainLength = 0 self.maxProtoChainLength = 0
@ -86,15 +87,18 @@ class Configuration:
else: else:
raise TypeError("Interface " + iface.identifier.name + raise TypeError("Interface " + iface.identifier.name +
" should have no more than two entries in Bindings.conf") " should have no more than two entries in Bindings.conf")
self.descriptors.extend([Descriptor(self, iface, x) for x in entry]) descs = [Descriptor(self, iface, x) for x in entry]
self.descriptors.extend(descs)
# Setting up descriptorsByName while iterating through interfaces
# means we can get the nativeType of iterable interfaces without
# having to do multiple loops.
for d in descs:
self.descriptorsByName.setdefault(d.interface.identifier.name,
[]).append(d)
# Keep the descriptor list sorted for determinism. # Keep the descriptor list sorted for determinism.
self.descriptors.sort(lambda x, y: cmp(x.name, y.name)) self.descriptors.sort(lambda x, y: cmp(x.name, y.name))
self.descriptorsByName = {}
for d in self.descriptors:
self.descriptorsByName.setdefault(d.interface.identifier.name,
[]).append(d)
self.descriptorsByFile = {} self.descriptorsByFile = {}
for d in self.descriptors: for d in self.descriptors:
@ -348,7 +352,18 @@ class Descriptor(DescriptorProvider):
# Read the desc, and fill in the relevant defaults. # Read the desc, and fill in the relevant defaults.
ifaceName = self.interface.identifier.name ifaceName = self.interface.identifier.name
if self.interface.isExternal(): # For generated iterator interfaces for other iterable interfaces, we
# just use IterableIterator as the native type, templated on the
# nativeType of the iterable interface. That way we can have a
# templated implementation for all the duplicated iterator
# functionality.
if self.interface.isIteratorInterface():
itrName = self.interface.iterableInterface.identifier.name
itrDesc = self.getDescriptor(itrName)
nativeTypeDefault = ("mozilla::dom::IterableIterator<%s>"
% itrDesc.nativeType)
elif self.interface.isExternal():
assert not self.workers assert not self.workers
nativeTypeDefault = "nsIDOM" + ifaceName nativeTypeDefault = "nsIDOM" + ifaceName
elif self.interface.isCallback(): elif self.interface.isCallback():
@ -386,6 +401,8 @@ class Descriptor(DescriptorProvider):
headerDefault = "mozilla/dom/workers/bindings/%s.h" % ifaceName headerDefault = "mozilla/dom/workers/bindings/%s.h" % ifaceName
elif not self.interface.isExternal() and self.interface.getExtendedAttribute("HeaderFile"): elif not self.interface.isExternal() and self.interface.getExtendedAttribute("HeaderFile"):
headerDefault = self.interface.getExtendedAttribute("HeaderFile")[0] headerDefault = self.interface.getExtendedAttribute("HeaderFile")[0]
elif self.interface.isIteratorInterface():
headerDefault = "mozilla/dom/IterableIterator.h"
else: else:
headerDefault = self.nativeType headerDefault = self.nativeType
headerDefault = headerDefault.replace("::", "/") + ".h" headerDefault = headerDefault.replace("::", "/") + ".h"
@ -514,6 +531,7 @@ class Descriptor(DescriptorProvider):
if desc.get('wantsQI', None) is not None: if desc.get('wantsQI', None) is not None:
self._wantsQI = desc.get('wantsQI', None) self._wantsQI = desc.get('wantsQI', None)
self.wrapperCache = (not self.interface.isCallback() and self.wrapperCache = (not self.interface.isCallback() and
not self.interface.isIteratorInterface() and
desc.get('wrapperCache', True)) desc.get('wrapperCache', True))
def make_name(name): def make_name(name):

View File

@ -0,0 +1,35 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "mozilla/dom/IterableIterator.h"
namespace mozilla {
namespace dom {
// Due to IterableIterator being a templated class, we implement the necessary
// CC bits in a superclass that IterableIterator then inherits from. This allows
// us to put the macros outside of the header. The base class has pure virtual
// functions for Traverse/Unlink that the templated subclasses will override.
NS_IMPL_CYCLE_COLLECTION_CLASS(IterableIteratorBase)
NS_IMPL_CYCLE_COLLECTING_ADDREF(IterableIteratorBase)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IterableIteratorBase)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IterableIteratorBase)
tmp->TraverseHelper(cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IterableIteratorBase)
tmp->UnlinkHelper();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IterableIteratorBase)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
}
}

View File

@ -0,0 +1,192 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
/**
* The IterableIterator class is used for WebIDL interfaces that have a
* iterable<> member defined. It handles the ES6 Iterator-like functions that
* are generated for the iterable interface.
*
* For iterable interfaces, the implementation class will need to contain three
* functions:
*
* - size_t GetIterableLength()
* - Returns the number of elements available to iterate over
* - [type] GetKeyAtIndex(size_t index)
* - Returns the key at the requested index
* - [type] GetValueAtIndex(size_t index)
* - Returns the value at the requested index, or the key again if this is
* a single type iterator.
*
* Examples of iterable interface implementations can be found in the bindings
* test directory.
*/
#ifndef mozilla_dom_IterableIterator_h
#define mozilla_dom_IterableIterator_h
#include "nsISupports.h"
#include "nsWrapperCache.h"
#include "nsPIDOMWindow.h"
#include "nsCOMPtr.h"
#include "mozilla/dom/ToJSValue.h"
#include "jswrapper.h"
#include "mozilla/dom/IterableIteratorBinding.h"
namespace mozilla {
namespace dom {
class IterableIteratorBase : public nsISupports
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(IterableIteratorBase)
typedef enum {
Keys = 0,
Values,
Entries
} IterableIteratorType;
IterableIteratorBase() {}
protected:
virtual ~IterableIteratorBase() {}
virtual void UnlinkHelper() = 0;
virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) = 0;
};
template <typename T>
class IterableIterator final : public IterableIteratorBase
{
public:
typedef bool (*WrapFunc)(JSContext* aCx,
mozilla::dom::IterableIterator<T>* aObject,
JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aReflector);
IterableIterator(T* aIterableObj, IterableIteratorType aIteratorType, WrapFunc aWrapFunc)
: mIteratorType(aIteratorType)
, mIterableObj(aIterableObj)
, mIndex(0)
, mWrapFunc(aWrapFunc)
{
MOZ_ASSERT(mIterableObj);
MOZ_ASSERT(mWrapFunc);
}
void
DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv)
{
RootedDictionary<IterableKeyOrValueResult> dict(aCx);
dict.mDone = aDone;
dict.mValue = aValue;
JS::Rooted<JS::Value> dictValue(aCx);
if (!ToJSValue(aCx, dict, &dictValue)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
aResult.set(&dictValue.toObject());
}
void
Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv)
{
JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue());
if (mIndex >= mIterableObj->GetIterableLength()) {
DictReturn(aCx, aResult, true, value, aRv);
return;
}
switch (mIteratorType) {
case IterableIteratorType::Keys:
{
if (!ToJSValue(aCx, mIterableObj->GetKeyAtIndex(mIndex), &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
DictReturn(aCx, aResult, false, value, aRv);
break;
}
case IterableIteratorType::Values:
{
if (!ToJSValue(aCx, mIterableObj->GetValueAtIndex(mIndex), &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
DictReturn(aCx, aResult, false, value, aRv);
break;
}
case IterableIteratorType::Entries:
{
JS::Rooted<JS::Value> key(aCx);
if (!ToJSValue(aCx, mIterableObj->GetKeyAtIndex(mIndex), &key)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
if (!ToJSValue(aCx, mIterableObj->GetValueAtIndex(mIndex), &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
RootedDictionary<IterableKeyAndValueResult> dict(aCx);
dict.mDone = false;
// Dictionary values are a Sequence, which is a FallibleTArray, so we need
// to check returns when appending.
if (!dict.mValue.AppendElement(key, mozilla::fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
if (!dict.mValue.AppendElement(value, mozilla::fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
JS::Rooted<JS::Value> dictValue(aCx);
if (!ToJSValue(aCx, dict, &dictValue)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
aResult.set(&dictValue.toObject());
break;
}
default:
MOZ_CRASH("Invalid iterator type!");
}
++mIndex;
}
virtual ~IterableIterator() {}
bool
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aObj)
{
return (*mWrapFunc)(aCx, this, aGivenProto, aObj);
}
protected:
// Tells whether this is a key, value, or entries iterator.
IterableIteratorType mIteratorType;
// Binding Implementation Object that we're iterating over.
nsRefPtr<T> mIterableObj;
// Current index of iteration.
uint32_t mIndex;
// Function pointer to binding-type-specific Wrap() call for this iterator.
WrapFunc mWrapFunc;
// Since we're templated on a binding, we need to possibly CC it, but can't do
// that through macros. So it happens here.
virtual void UnlinkHelper() final
{
mIterableObj = nullptr;
}
virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) override
{
IterableIterator<T>* tmp = this;
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj);
}
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_IterableIterator_h

View File

@ -27,6 +27,7 @@ EXPORTS.mozilla.dom += [
'DOMString.h', 'DOMString.h',
'Errors.msg', 'Errors.msg',
'Exceptions.h', 'Exceptions.h',
'IterableIterator.h',
'JSSlots.h', 'JSSlots.h',
'MozMap.h', 'MozMap.h',
'NonRefcountedDOMObject.h', 'NonRefcountedDOMObject.h',
@ -74,6 +75,7 @@ UNIFIED_SOURCES += [
'Date.cpp', 'Date.cpp',
'DOMJSProxyHandler.cpp', 'DOMJSProxyHandler.cpp',
'Exceptions.cpp', 'Exceptions.cpp',
'IterableIterator.cpp',
'ToJSValue.cpp', 'ToJSValue.cpp',
] ]
@ -88,12 +90,16 @@ SOURCES += [
# them are only run in debug mode. # them are only run in debug mode.
if CONFIG['MOZ_DEBUG']: if CONFIG['MOZ_DEBUG']:
EXPORTS.mozilla.dom += [ EXPORTS.mozilla.dom += [
"test/TestInterfaceIterableDouble.h",
"test/TestInterfaceIterableSingle.h",
"test/TestInterfaceMaplike.h", "test/TestInterfaceMaplike.h",
"test/TestInterfaceMaplikeObject.h", "test/TestInterfaceMaplikeObject.h",
"test/TestInterfaceSetlike.h", "test/TestInterfaceSetlike.h",
"test/TestInterfaceSetlikeNode.h" "test/TestInterfaceSetlikeNode.h"
] ]
UNIFIED_SOURCES += [ UNIFIED_SOURCES += [
"test/TestInterfaceIterableDouble.cpp",
"test/TestInterfaceIterableSingle.cpp",
"test/TestInterfaceMaplike.cpp", "test/TestInterfaceMaplike.cpp",
"test/TestInterfaceMaplikeObject.cpp", "test/TestInterfaceMaplikeObject.cpp",
"test/TestInterfaceSetlike.cpp", "test/TestInterfaceSetlike.cpp",

View File

@ -540,6 +540,9 @@ class IDLExternalInterface(IDLObjectWithIdentifier, IDLExposureMixins):
def validate(self): def validate(self):
pass pass
def isIteratorInterface(self):
return False
def isExternal(self): def isExternal(self):
return True return True
@ -640,7 +643,7 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
self._callback = False self._callback = False
self._finished = False self._finished = False
self.members = [] self.members = []
self.maplikeOrSetlike = None self.maplikeOrSetlikeOrIterable = None
self._partialInterfaces = [] self._partialInterfaces = []
self._extendedAttrDict = {} self._extendedAttrDict = {}
# namedConstructors needs deterministic ordering because bindings code # namedConstructors needs deterministic ordering because bindings code
@ -664,6 +667,9 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
self.totalMembersInSlots = 0 self.totalMembersInSlots = 0
# Tracking of the number of own own members we have in slots # Tracking of the number of own own members we have in slots
self._ownMembersInSlots = 0 self._ownMembersInSlots = 0
# If this is an iterator interface, we need to know what iterable
# interface we're iterating for in order to get its nativeType.
self.iterableInterface = None
IDLObjectWithScope.__init__(self, location, parentScope, name) IDLObjectWithScope.__init__(self, location, parentScope, name)
IDLExposureMixins.__init__(self, location) IDLExposureMixins.__init__(self, location)
@ -682,6 +688,13 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
except: except:
return None return None
def isIterable(self):
return (self.maplikeOrSetlikeOrIterable and
self.maplikeOrSetlikeOrIterable.isIterable())
def isIteratorInterface(self):
return self.iterableInterface is not None
def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject): def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject):
assert isinstance(scope, IDLScope) assert isinstance(scope, IDLScope)
assert isinstance(originalObject, IDLInterfaceMember) assert isinstance(originalObject, IDLInterfaceMember)
@ -718,22 +731,22 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
# need to be treated like regular interface members, do this before # need to be treated like regular interface members, do this before
# things like exposure setting. # things like exposure setting.
for member in self.members: for member in self.members:
if member.isMaplikeOrSetlike(): if member.isMaplikeOrSetlikeOrIterable():
# Check that we only have one interface declaration (currently # Check that we only have one interface declaration (currently
# there can only be one maplike/setlike declaration per # there can only be one maplike/setlike declaration per
# interface) # interface)
if self.maplikeOrSetlike: if self.maplikeOrSetlikeOrIterable:
raise WebIDLError("%s declaration used on " raise WebIDLError("%s declaration used on "
"interface that already has %s " "interface that already has %s "
"declaration" % "declaration" %
(member.maplikeOrSetlikeType, (member.maplikeOrSetlikeOrIterableType,
self.maplikeOrSetlike.maplikeOrSetlikeType), self.maplikeOrSetlike.maplikeOrSetlikeOrIterableType),
[self.maplikeOrSetlike.location, [self.maplikeOrSetlikeOrIterable.location,
member.location]) member.location])
self.maplikeOrSetlike = member self.maplikeOrSetlikeOrIterable = member
# If we've got a maplike or setlike declaration, we'll be building all of # If we've got a maplike or setlike declaration, we'll be building all of
# our required methods in Codegen. Generate members now. # our required methods in Codegen. Generate members now.
self.maplikeOrSetlike.expand(self.members, self.isJSImplemented()) self.maplikeOrSetlikeOrIterable.expand(self.members, self.isJSImplemented())
# Now that we've merged in our partial interfaces, set the # Now that we've merged in our partial interfaces, set the
# _exposureGlobalNames on any members that don't have it set yet. Note # _exposureGlobalNames on any members that don't have it set yet. Note
@ -884,14 +897,14 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
# If we have a maplike or setlike, and the consequential interface # If we have a maplike or setlike, and the consequential interface
# also does, throw an error. # also does, throw an error.
if iface.maplikeOrSetlike and self.maplikeOrSetlike: if iface.maplikeOrSetlikeOrIterable and self.maplikeOrSetlikeOrIterable:
raise WebIDLError("Maplike/setlike interface %s cannot have " raise WebIDLError("Maplike/setlike/iterable interface %s cannot have "
"maplike/setlike interface %s as a " "maplike/setlike/iterable interface %s as a "
"consequential interface" % "consequential interface" %
(self.identifier.name, (self.identifier.name,
iface.identifier.name), iface.identifier.name),
[self.maplikeOrSetlike.location, [self.maplikeOrSetlikeOrIterable.location,
iface.maplikeOrSetlike.location]) iface.maplikeOrSetlikeOrIterable.location])
additionalMembers = iface.originalMembers additionalMembers = iface.originalMembers
for additionalMember in additionalMembers: for additionalMember in additionalMembers:
for member in self.members: for member in self.members:
@ -905,15 +918,15 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
for ancestor in self.getInheritedInterfaces(): for ancestor in self.getInheritedInterfaces():
ancestor.interfacesBasedOnSelf.add(self) ancestor.interfacesBasedOnSelf.add(self)
if (ancestor.maplikeOrSetlike is not None and if (ancestor.maplikeOrSetlikeOrIterable is not None and
self.maplikeOrSetlike is not None): self.maplikeOrSetlikeOrIterable is not None):
raise WebIDLError("Cannot have maplike/setlike on %s that " raise WebIDLError("Cannot have maplike/setlike on %s that "
"inherits %s, which is already " "inherits %s, which is already "
"maplike/setlike" % "maplike/setlike" %
(self.identifier.name, (self.identifier.name,
ancestor.identifier.name), ancestor.identifier.name),
[self.maplikeOrSetlike.location, [self.maplikeOrSetlikeOrIterable.location,
ancestor.maplikeOrSetlike.location]) ancestor.maplikeOrSetlikeOrIterable.location])
for ancestorConsequential in ancestor.getConsequentialInterfaces(): for ancestorConsequential in ancestor.getConsequentialInterfaces():
ancestorConsequential.interfacesBasedOnSelf.add(self) ancestorConsequential.interfacesBasedOnSelf.add(self)
@ -997,12 +1010,12 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins):
# At this point, we have all of our members. If the current interface # At this point, we have all of our members. If the current interface
# uses maplike/setlike, check for collisions anywhere in the current # uses maplike/setlike, check for collisions anywhere in the current
# interface or higher in the inheritance chain. # interface or higher in the inheritance chain.
if self.maplikeOrSetlike: if self.maplikeOrSetlikeOrIterable:
testInterface = self testInterface = self
isAncestor = False isAncestor = False
while testInterface: while testInterface:
self.maplikeOrSetlike.checkCollisions(testInterface.members, self.maplikeOrSetlikeOrIterable.checkCollisions(testInterface.members,
isAncestor) isAncestor)
isAncestor = True isAncestor = True
testInterface = testInterface.parent testInterface = testInterface.parent
@ -3367,7 +3380,8 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins):
'Const', 'Const',
'Attr', 'Attr',
'Method', 'Method',
'MaplikeOrSetlike' 'MaplikeOrSetlike',
'Iterable'
) )
Special = enum( Special = enum(
@ -3393,6 +3407,10 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins):
def isConst(self): def isConst(self):
return self.tag == IDLInterfaceMember.Tags.Const return self.tag == IDLInterfaceMember.Tags.Const
def isMaplikeOrSetlikeOrIterable(self):
return (self.tag == IDLInterfaceMember.Tags.MaplikeOrSetlike or
self.tag == IDLInterfaceMember.Tags.Iterable)
def isMaplikeOrSetlike(self): def isMaplikeOrSetlike(self):
return self.tag == IDLInterfaceMember.Tags.MaplikeOrSetlike return self.tag == IDLInterfaceMember.Tags.MaplikeOrSetlike
@ -3470,58 +3488,42 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins):
self.aliases.append(alias) self.aliases.append(alias)
# MaplikeOrSetlike adds a trait to an interface, like map or iteration class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
# functions. To handle them while still getting all of the generated binding
# code taken care of, we treat them as macros that are expanded into members
# based on parsed values.
class IDLMaplikeOrSetlike(IDLInterfaceMember):
MaplikeOrSetlikeTypes = enum(
'maplike',
'setlike'
)
def __init__(self, location, identifier, maplikeOrSetlikeType,
readonly, keyType, valueType):
IDLInterfaceMember.__init__(self, location, identifier,
IDLInterfaceMember.Tags.MaplikeOrSetlike)
def __init__(self, location, identifier, ifaceType, keyType, valueType, ifaceKind):
IDLInterfaceMember.__init__(self, location, identifier, ifaceKind)
assert isinstance(keyType, IDLType) assert isinstance(keyType, IDLType)
assert isinstance(valueType, IDLType) assert ifaceType in ['maplike', 'setlike', 'iterable']
self.maplikeOrSetlikeType = maplikeOrSetlikeType if valueType is not None:
self.readonly = readonly assert isinstance(valueType, IDLType)
self.keyType = keyType self.keyType = keyType
self.valueType = valueType self.valueType = valueType
self.slotIndex = None self.maplikeOrSetlikeOrIterableType = ifaceType
self.disallowedMemberNames = [] self.disallowedMemberNames = []
self.disallowedNonMethodNames = [] self.disallowedNonMethodNames = []
# When generating JSAPI access code, we need to know the backing object
# type prefix to create the correct function. Generate here for reuse.
if self.isMaplike():
self.prefix = 'Map'
elif self.isSetlike():
self.prefix = 'Set'
def __str__(self):
return "declared '%s' with key '%s'" % (self.maplikeOrSetlikeType, self.keyType)
def isMaplike(self): def isMaplike(self):
return self.maplikeOrSetlikeType == "maplike" return self.maplikeOrSetlikeOrIterableType == "maplike"
def isSetlike(self): def isSetlike(self):
return self.maplikeOrSetlikeType == "setlike" return self.maplikeOrSetlikeOrIterableType == "setlike"
def isIterable(self):
return self.maplikeOrSetlikeOrIterableType == "iterable"
def hasValueType(self):
return self.valueType is not None
def checkCollisions(self, members, isAncestor): def checkCollisions(self, members, isAncestor):
for member in members: for member in members:
# Check that there are no disallowed members # Check that there are no disallowed members
if (member.identifier.name in self.disallowedMemberNames and if (member.identifier.name in self.disallowedMemberNames and
not ((member.isMethod() and member.isMaplikeOrSetlikeMethod()) or not ((member.isMethod() and member.isMaplikeOrSetlikeOrIterableMethod()) or
(member.isAttr() and member.isMaplikeOrSetlikeAttr()))): (member.isAttr() and member.isMaplikeOrSetlikeAttr()))):
raise WebIDLError("Member '%s' conflicts " raise WebIDLError("Member '%s' conflicts "
"with reserved %s name." % "with reserved %s name." %
(member.identifier.name, (member.identifier.name,
self.maplikeOrSetlikeType), self.maplikeOrSetlikeOrIterableType),
[self.location, member.location]) [self.location, member.location])
# Check that there are no disallowed non-method members # Check that there are no disallowed non-method members
if (isAncestor or (member.isAttr() or member.isConst()) and if (isAncestor or (member.isAttr() or member.isConst()) and
@ -3529,168 +3531,76 @@ class IDLMaplikeOrSetlike(IDLInterfaceMember):
raise WebIDLError("Member '%s' conflicts " raise WebIDLError("Member '%s' conflicts "
"with reserved %s method." % "with reserved %s method." %
(member.identifier.name, (member.identifier.name,
self.maplikeOrSetlikeType), self.maplikeOrSetlikeOrIterableType),
[self.location, member.location]) [self.location, member.location])
def expand(self, members, isJSImplemented): def addMethod(self, name, members, allowExistingOperations, returnType, args=[],
chromeOnly=False, isPure=False, affectsNothing=False, newObject=False):
""" """
In order to take advantage of all of the method machinery in Codegen, Create an IDLMethod based on the parameters passed in.
we generate our functions as if they were part of the interface
specification during parsing. - members is the member list to add this function to, since this is
called during the member expansion portion of interface object
building.
- chromeOnly is only True for read-only js implemented classes, to
implement underscore prefixed convenience functions which would
otherwise not be available, unlike the case of C++ bindings.
- isPure is only True for idempotent functions, so it is not valid for
things like keys, values, etc. that return a new object every time.
- affectsNothing means that nothing changes due to this method, which
affects JIT optimization behavior
- newObject means the method creates and returns a new object.
""" """
def addMethod(name, allowExistingOperations, returnType, args=[], # Only add name to lists for collision checks if it's not chrome
chromeOnly=False, isPure=False, affectsNothing=False): # only.
""" if chromeOnly:
Create an IDLMethod based on the parameters passed in. chromeOnly is only name = "__" + name
True for read-only js implemented classes, to implement underscore else:
prefixed convenience functions would otherwise not be available, if not allowExistingOperations:
unlike the case of C++ bindings. isPure is only True for self.disallowedMemberNames.append(name)
idempotent functions, so it is not valid for things like keys,
values, etc. that return a new object every time.
"""
# Only add name to lists for collision checks if it's not chrome
# only.
if chromeOnly:
name = "__" + name
else: else:
if not allowExistingOperations: self.disallowedNonMethodNames.append(name)
self.disallowedMemberNames.append(name) # If allowExistingOperations is True, and another operation exists
else: # with the same name as the one we're trying to add, don't add the
self.disallowedNonMethodNames.append(name) # maplike/setlike operation. However, if the operation is static,
# then fail by way of creating the function, which will cause a
# If allowExistingOperations is True, and another operation exists # naming conflict, per the spec.
# with the same name as the one we're trying to add, don't add the if allowExistingOperations:
# maplike/setlike operation. However, if the operation is static, for m in members:
# then fail by way of creating the function, which will cause a if m.identifier.name == name and m.isMethod() and not m.isStatic():
# naming conflict, per the spec. return
if allowExistingOperations: method = IDLMethod(self.location,
for m in members: IDLUnresolvedIdentifier(self.location, name, allowDoubleUnderscore=chromeOnly),
if m.identifier.name == name and m.isMethod() and not m.isStatic(): returnType, args, maplikeOrSetlikeOrIterable=self)
return # We need to be able to throw from declaration methods
method.addExtendedAttributes(
method = IDLMethod(self.location, [IDLExtendedAttribute(self.location, ("Throws",))])
IDLUnresolvedIdentifier(self.location, name, allowDoubleUnderscore=chromeOnly), if chromeOnly:
returnType, args, maplikeOrSetlike=self)
# We need to be able to throw from declaration methods
method.addExtendedAttributes( method.addExtendedAttributes(
[IDLExtendedAttribute(self.location, ("Throws",))]) [IDLExtendedAttribute(self.location, ("ChromeOnly",))])
if chromeOnly: if isPure:
method.addExtendedAttributes( method.addExtendedAttributes(
[IDLExtendedAttribute(self.location, ("ChromeOnly",))]) [IDLExtendedAttribute(self.location, ("Pure",))])
if isPure: # Following attributes are used for keys/values/entries. Can't mark
method.addExtendedAttributes( # them pure, since they return a new object each time they are run.
[IDLExtendedAttribute(self.location, ("Pure",))]) if affectsNothing:
# Following attributes are used for keys/values/entries. Can't mark method.addExtendedAttributes(
# them pure, since they return a new object each time they are run. [IDLExtendedAttribute(self.location, ("DependsOn", "Everything")),
if affectsNothing: IDLExtendedAttribute(self.location, ("Affects", "Nothing"))])
method.addExtendedAttributes( if newObject:
[IDLExtendedAttribute(self.location, ("DependsOn", "Everything")), method.addExtendedAttributes(
IDLExtendedAttribute(self.location, ("Affects", "Nothing"))]) [IDLExtendedAttribute(self.location, ("NewObject",))])
members.append(method) members.append(method)
# Both maplike and setlike have a size attribute
members.append(IDLAttribute(self.location,
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), "size"),
BuiltinTypes[IDLBuiltinType.Types.unsigned_long],
True,
maplikeOrSetlike=self))
self.reserved_ro_names = ["size"]
# object entries()
addMethod("entries", False, BuiltinTypes[IDLBuiltinType.Types.object],
affectsNothing=True)
# object keys()
addMethod("keys", False, BuiltinTypes[IDLBuiltinType.Types.object],
affectsNothing=True)
# object values()
addMethod("values", False, BuiltinTypes[IDLBuiltinType.Types.object],
affectsNothing=True)
# void forEach(callback(valueType, keyType), thisVal)
foreachArguments = [IDLArgument(self.location,
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
"callback"),
BuiltinTypes[IDLBuiltinType.Types.object]),
IDLArgument(self.location,
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
"thisArg"),
BuiltinTypes[IDLBuiltinType.Types.any],
optional=True)]
addMethod("forEach", False, BuiltinTypes[IDLBuiltinType.Types.void],
foreachArguments)
def getKeyArg():
return IDLArgument(self.location,
IDLUnresolvedIdentifier(self.location, "key"),
self.keyType)
# boolean has(keyType key)
addMethod("has", False, BuiltinTypes[IDLBuiltinType.Types.boolean],
[getKeyArg()], isPure=True)
if not self.readonly:
# void clear()
addMethod("clear", True, BuiltinTypes[IDLBuiltinType.Types.void],
[])
# boolean delete(keyType key)
addMethod("delete", True,
BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()])
# Always generate underscored functions (e.g. __add, __clear) for js
# implemented interfaces as convenience functions.
if isJSImplemented:
# void clear()
addMethod("clear", True, BuiltinTypes[IDLBuiltinType.Types.void],
[], chromeOnly=True)
# boolean delete(keyType key)
addMethod("delete", True,
BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()],
chromeOnly=True)
if self.isSetlike():
if not self.readonly:
# Add returns the set object it just added to.
# object add(keyType key)
addMethod("add", True,
BuiltinTypes[IDLBuiltinType.Types.object], [getKeyArg()])
if isJSImplemented:
addMethod("add", True,
BuiltinTypes[IDLBuiltinType.Types.object], [getKeyArg()],
chromeOnly=True)
return
# If we get this far, we're a maplike declaration.
# valueType get(keyType key)
#
# Note that instead of the value type, we're using any here. The
# validity checks should happen as things are inserted into the map,
# and using any as the return type makes code generation much simpler.
#
# TODO: Bug 1155340 may change this to use specific type to provide
# more info to JIT.
addMethod("get", False, BuiltinTypes[IDLBuiltinType.Types.any],
[getKeyArg()], isPure=True)
def getValueArg():
return IDLArgument(self.location,
IDLUnresolvedIdentifier(self.location, "value"),
self.valueType)
if not self.readonly:
addMethod("set", True, BuiltinTypes[IDLBuiltinType.Types.object],
[getKeyArg(), getValueArg()])
if isJSImplemented:
addMethod("set", True, BuiltinTypes[IDLBuiltinType.Types.object],
[getKeyArg(), getValueArg()], chromeOnly=True)
def resolve(self, parentScope): def resolve(self, parentScope):
self.keyType.resolveType(parentScope) self.keyType.resolveType(parentScope)
self.valueType.resolveType(parentScope) if self.valueType:
self.valueType.resolveType(parentScope)
def finish(self, scope): def finish(self, scope):
IDLInterfaceMember.finish(self, scope) IDLInterfaceMember.finish(self, scope)
@ -3701,7 +3611,7 @@ class IDLMaplikeOrSetlike(IDLInterfaceMember):
assert not isinstance(t, IDLTypedefType) assert not isinstance(t, IDLTypedefType)
assert not isinstance(t.name, IDLUnresolvedIdentifier) assert not isinstance(t.name, IDLUnresolvedIdentifier)
self.keyType = t self.keyType = t
if not self.valueType.isComplete(): if self.valueType and not self.valueType.isComplete():
t = self.valueType.complete(scope) t = self.valueType.complete(scope)
assert not isinstance(t, IDLUnresolvedType) assert not isinstance(t, IDLUnresolvedType)
@ -3716,8 +3626,161 @@ class IDLMaplikeOrSetlike(IDLInterfaceMember):
IDLInterfaceMember.handleExtendedAttribute(self, attr) IDLInterfaceMember.handleExtendedAttribute(self, attr)
def _getDependentObjects(self): def _getDependentObjects(self):
return set([self.keyType, self.valueType]) if self.valueType:
return set([self.keyType, self.valueType])
return set([self.keyType])
# Iterable adds ES6 iterator style functions and traits
# (keys/values/entries/@@iterator) to an interface.
class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase):
def __init__(self, location, identifier, keyType, valueType=None, scope=None):
IDLMaplikeOrSetlikeOrIterableBase.__init__(self, location, identifier,
"iterable", keyType, valueType,
IDLInterfaceMember.Tags.Iterable)
self.iteratorType = None
def __str__(self):
return "declared iterable with key '%s' and value '%s'" % (self.keyType, self.valueType)
def expand(self, members, isJSImplemented):
"""
In order to take advantage of all of the method machinery in Codegen,
we generate our functions as if they were part of the interface
specification during parsing.
"""
# object entries()
self.addMethod("entries", members, False, self.iteratorType,
affectsNothing=True, newObject=True)
# object keys()
self.addMethod("keys", members, False, self.iteratorType,
affectsNothing=True, newObject=True)
# object values()
self.addMethod("values", members, False, self.iteratorType,
affectsNothing=True, newObject=True)
# MaplikeOrSetlike adds ES6 map-or-set-like traits to an interface.
class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase):
def __init__(self, location, identifier, maplikeOrSetlikeType,
readonly, keyType, valueType):
IDLMaplikeOrSetlikeOrIterableBase.__init__(self, location, identifier, maplikeOrSetlikeType,
keyType, valueType, IDLInterfaceMember.Tags.MaplikeOrSetlike)
self.readonly = readonly
self.slotIndex = None
# When generating JSAPI access code, we need to know the backing object
# type prefix to create the correct function. Generate here for reuse.
if self.isMaplike():
self.prefix = 'Map'
elif self.isSetlike():
self.prefix = 'Set'
def __str__(self):
return "declared '%s' with key '%s'" % (self.maplikeOrSetlikeOrIterableType, self.keyType)
def expand(self, members, isJSImplemented):
"""
In order to take advantage of all of the method machinery in Codegen,
we generate our functions as if they were part of the interface
specification during parsing.
"""
# Both maplike and setlike have a size attribute
members.append(IDLAttribute(self.location,
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), "size"),
BuiltinTypes[IDLBuiltinType.Types.unsigned_long],
True,
maplikeOrSetlike=self))
self.reserved_ro_names = ["size"]
# object entries()
self.addMethod("entries", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
affectsNothing=True)
# object keys()
self.addMethod("keys", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
affectsNothing=True)
# object values()
self.addMethod("values", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
affectsNothing=True)
# void forEach(callback(valueType, keyType), thisVal)
foreachArguments = [IDLArgument(self.location,
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
"callback"),
BuiltinTypes[IDLBuiltinType.Types.object]),
IDLArgument(self.location,
IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
"thisArg"),
BuiltinTypes[IDLBuiltinType.Types.any],
optional=True)]
self.addMethod("forEach", members, False, BuiltinTypes[IDLBuiltinType.Types.void],
foreachArguments)
def getKeyArg():
return IDLArgument(self.location,
IDLUnresolvedIdentifier(self.location, "key"),
self.keyType)
# boolean has(keyType key)
self.addMethod("has", members, False, BuiltinTypes[IDLBuiltinType.Types.boolean],
[getKeyArg()], isPure=True)
if not self.readonly:
# void clear()
self.addMethod("clear", members, True, BuiltinTypes[IDLBuiltinType.Types.void],
[])
# boolean delete(keyType key)
self.addMethod("delete", members, True,
BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()])
# Always generate underscored functions (e.g. __add, __clear) for js
# implemented interfaces as convenience functions.
if isJSImplemented:
# void clear()
self.addMethod("clear", members, True, BuiltinTypes[IDLBuiltinType.Types.void],
[], chromeOnly=True)
# boolean delete(keyType key)
self.addMethod("delete", members, True,
BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()],
chromeOnly=True)
if self.isSetlike():
if not self.readonly:
# Add returns the set object it just added to.
# object add(keyType key)
self.addMethod("add", members, True,
BuiltinTypes[IDLBuiltinType.Types.object], [getKeyArg()])
if isJSImplemented:
self.addMethod("add", members, True,
BuiltinTypes[IDLBuiltinType.Types.object], [getKeyArg()],
chromeOnly=True)
return
# If we get this far, we're a maplike declaration.
# valueType get(keyType key)
#
# Note that instead of the value type, we're using any here. The
# validity checks should happen as things are inserted into the map,
# and using any as the return type makes code generation much simpler.
#
# TODO: Bug 1155340 may change this to use specific type to provide
# more info to JIT.
self.addMethod("get", members, False, BuiltinTypes[IDLBuiltinType.Types.any],
[getKeyArg()], isPure=True)
def getValueArg():
return IDLArgument(self.location,
IDLUnresolvedIdentifier(self.location, "value"),
self.valueType)
if not self.readonly:
self.addMethod("set", members, True, BuiltinTypes[IDLBuiltinType.Types.object],
[getKeyArg(), getValueArg()])
if isJSImplemented:
self.addMethod("set", members, True, BuiltinTypes[IDLBuiltinType.Types.object],
[getKeyArg(), getValueArg()], chromeOnly=True)
class IDLConst(IDLInterfaceMember): class IDLConst(IDLInterfaceMember):
def __init__(self, location, identifier, type, value): def __init__(self, location, identifier, type, value):
@ -4319,7 +4382,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
static=False, getter=False, setter=False, creator=False, static=False, getter=False, setter=False, creator=False,
deleter=False, specialType=NamedOrIndexed.Neither, deleter=False, specialType=NamedOrIndexed.Neither,
legacycaller=False, stringifier=False, jsonifier=False, legacycaller=False, stringifier=False, jsonifier=False,
maplikeOrSetlike=None): maplikeOrSetlikeOrIterable=None):
# REVIEW: specialType is NamedOrIndexed -- wow, this is messed up. # REVIEW: specialType is NamedOrIndexed -- wow, this is messed up.
IDLInterfaceMember.__init__(self, location, identifier, IDLInterfaceMember.__init__(self, location, identifier,
IDLInterfaceMember.Tags.Method) IDLInterfaceMember.Tags.Method)
@ -4347,8 +4410,8 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
self._stringifier = stringifier self._stringifier = stringifier
assert isinstance(jsonifier, bool) assert isinstance(jsonifier, bool)
self._jsonifier = jsonifier self._jsonifier = jsonifier
assert maplikeOrSetlike is None or isinstance(maplikeOrSetlike, IDLMaplikeOrSetlike) assert maplikeOrSetlikeOrIterable is None or isinstance(maplikeOrSetlikeOrIterable, IDLMaplikeOrSetlikeOrIterableBase)
self.maplikeOrSetlike = maplikeOrSetlike self.maplikeOrSetlikeOrIterable = maplikeOrSetlikeOrIterable
self._specialType = specialType self._specialType = specialType
self._unforgeable = False self._unforgeable = False
self.dependsOn = "Everything" self.dependsOn = "Everything"
@ -4430,12 +4493,12 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
def isJsonifier(self): def isJsonifier(self):
return self._jsonifier return self._jsonifier
def isMaplikeOrSetlikeMethod(self): def isMaplikeOrSetlikeOrIterableMethod(self):
""" """
True if this method was generated as part of a True if this method was generated as part of a
maplike/setlike/etc interface (e.g. has/get methods) maplike/setlike/etc interface (e.g. has/get methods)
""" """
return self.maplikeOrSetlike is not None return self.maplikeOrSetlikeOrIterable is not None
def isSpecial(self): def isSpecial(self):
return (self.isGetter() or return (self.isGetter() or
@ -4458,7 +4521,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
an non-identifier name, they actually DO have an identifier. an non-identifier name, they actually DO have an identifier.
""" """
return (self.identifier.name[:2] == "__" and return (self.identifier.name[:2] == "__" and
not self.isMaplikeOrSetlikeMethod()) not self.isMaplikeOrSetlikeOrIterableMethod())
def resolve(self, parentScope): def resolve(self, parentScope):
assert isinstance(parentScope, IDLScope) assert isinstance(parentScope, IDLScope)
@ -4966,7 +5029,8 @@ class Tokenizer(object):
"SharedArrayBuffer": "SHAREDARRAYBUFFER", "SharedArrayBuffer": "SHAREDARRAYBUFFER",
"or": "OR", "or": "OR",
"maplike": "MAPLIKE", "maplike": "MAPLIKE",
"setlike": "SETLIKE" "setlike": "SETLIKE",
"iterable": "ITERABLE"
} }
tokens.extend(keywords.values()) tokens.extend(keywords.values())
@ -5120,8 +5184,9 @@ class Parser(Tokenizer):
raise ex raise ex
pass pass
p[0] = IDLInterface(location, self.globalScope(), identifier, parent, iface = IDLInterface(location, self.globalScope(), identifier, parent,
members, isKnownNonPartial=True) members, isKnownNonPartial=True)
p[0] = iface
def p_InterfaceForwardDecl(self, p): def p_InterfaceForwardDecl(self, p):
""" """
@ -5207,7 +5272,7 @@ class Parser(Tokenizer):
def p_InterfaceMember(self, p): def p_InterfaceMember(self, p):
""" """
InterfaceMember : Const InterfaceMember : Const
| AttributeOrOperationOrMaplikeOrSetlike | AttributeOrOperationOrMaplikeOrSetlikeOrIterable
""" """
p[0] = p[1] p[0] = p[1]
@ -5422,15 +5487,30 @@ class Parser(Tokenizer):
""" """
p[0] = False p[0] = False
def p_AttributeOrOperationOrMaplikeOrSetlike(self, p): def p_AttributeOrOperationOrMaplikeOrSetlikeOrIterable(self, p):
""" """
AttributeOrOperationOrMaplikeOrSetlike : Attribute AttributeOrOperationOrMaplikeOrSetlikeOrIterable : Attribute
| Maplike | Maplike
| Setlike | Setlike
| Operation | Iterable
| Operation
""" """
p[0] = p[1] p[0] = p[1]
def p_Iterable(self, p):
"""
Iterable : ITERABLE LT Type GT SEMICOLON
| ITERABLE LT Type COMMA Type GT SEMICOLON
"""
location = self.getLocation(p, 2)
identifier = IDLUnresolvedIdentifier(location, "__iterable",
allowDoubleUnderscore=True)
keyType = p[3]
valueType = None
if (len(p) > 6):
valueType = p[5]
p[0] = IDLIterable(location, identifier, keyType, valueType, self.globalScope())
def p_Setlike(self, p): def p_Setlike(self, p):
""" """
Setlike : ReadOnly SETLIKE LT Type GT SEMICOLON Setlike : ReadOnly SETLIKE LT Type GT SEMICOLON
@ -5803,6 +5883,7 @@ class Parser(Tokenizer):
| IMPLEMENTS | IMPLEMENTS
| INHERIT | INHERIT
| INTERFACE | INTERFACE
| ITERABLE
| LEGACYCALLER | LEGACYCALLER
| MAPLIKE | MAPLIKE
| PARTIAL | PARTIAL
@ -6481,7 +6562,46 @@ class Parser(Tokenizer):
self._filename = None self._filename = None
def finish(self): def finish(self):
# First, finish all the IDLImplementsStatements. In particular, we # If we have interfaces that are iterable, create their
# iterator interfaces and add them to the productions array.
interfaceStatements = [p for p in self._productions if
isinstance(p, IDLInterface)]
for iface in interfaceStatements:
iterable = None
# We haven't run finish() on the interface yet, so we don't know
# whether our interface is maplike/setlike/iterable or not. This
# means we have to loop through the members to see if we have an
# iterable member.
for m in iface.members:
if isinstance(m, IDLIterable):
iterable = m
break
if iterable:
itr_ident = IDLUnresolvedIdentifier(iface.location,
iface.identifier.name + "Iterator")
itr_iface = IDLInterface(iface.location, self.globalScope(),
itr_ident, None, [],
isKnownNonPartial=True)
itr_iface.addExtendedAttributes([IDLExtendedAttribute(iface.location,
("NoInterfaceObject", ))])
# Always append generated iterable interfaces and their
# matching implements statements after the interface they're a
# member of, otherwise nativeType generation won't work
# correctly.
itr_iface.iterableInterface = iface
self._productions.append(itr_iface)
iterable.iteratorType = IDLWrapperType(iface.location, itr_iface)
itrPlaceholder = IDLIdentifierPlaceholder(iface.location,
IDLUnresolvedIdentifier(iface.location,
"IterableIterator"))
implements = IDLImplementsStatement(iface.location,
IDLIdentifierPlaceholder(iface.location,
itr_ident),
itrPlaceholder)
self._productions.append(implements)
# Then, finish all the IDLImplementsStatements. In particular, we
# have to make sure we do those before we do the IDLInterfaces. # have to make sure we do those before we do the IDLInterfaces.
# XXX khuey hates this bit and wants to nuke it from orbit. # XXX khuey hates this bit and wants to nuke it from orbit.
implementsStatements = [p for p in self._productions if implementsStatements = [p for p in self._productions if

View File

@ -41,11 +41,11 @@ def WebIDLTest(parser, harness):
prefix + " - Interface failed but not as a WebIDLError exception") prefix + " - Interface failed but not as a WebIDLError exception")
iterableMembers = [(x, WebIDL.IDLMethod) for x in ["entries", "keys", iterableMembers = [(x, WebIDL.IDLMethod) for x in ["entries", "keys",
"values", "forEach"]] "values"]]
iterableMembers.extend([("size", WebIDL.IDLAttribute)]) setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has", "foreach"]] +
setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has"]] +
[("__setlike", WebIDL.IDLMaplikeOrSetlike)] + [("__setlike", WebIDL.IDLMaplikeOrSetlike)] +
iterableMembers) iterableMembers)
setROMembers.extend([("size", WebIDL.IDLAttribute)])
setRWMembers = ([(x, WebIDL.IDLMethod) for x in ["add", setRWMembers = ([(x, WebIDL.IDLMethod) for x in ["add",
"clear", "clear",
"delete"]] + "delete"]] +
@ -58,9 +58,10 @@ def WebIDLTest(parser, harness):
"__clear", "__clear",
"__delete"]] + "__delete"]] +
setRWMembers) setRWMembers)
mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has"]] + mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has", "foreach"]] +
[("__maplike", WebIDL.IDLMaplikeOrSetlike)] + [("__maplike", WebIDL.IDLMaplikeOrSetlike)] +
iterableMembers) iterableMembers)
mapROMembers.extend([("size", WebIDL.IDLAttribute)])
mapRWMembers = ([(x, WebIDL.IDLMethod) for x in ["set", mapRWMembers = ([(x, WebIDL.IDLMethod) for x in ["set",
"clear", "clear",
"delete"]] + mapROMembers) "delete"]] + mapROMembers)
@ -69,8 +70,8 @@ def WebIDLTest(parser, harness):
"__delete"]] + "__delete"]] +
mapRWMembers) mapRWMembers)
disallowedMemberNames = ["keys", "entries", "values", "forEach", "has", disallowedIterableNames = ["keys", "entries", "values"]
"size"] disallowedMemberNames = ["forEach", "has", "size"] + disallowedIterableNames
mapDisallowedMemberNames = ["get"] + disallowedMemberNames mapDisallowedMemberNames = ["get"] + disallowedMemberNames
disallowedNonMethodNames = ["clear", "delete"] disallowedNonMethodNames = ["clear", "delete"]
mapDisallowedNonMethodNames = ["set"] + disallowedNonMethodNames mapDisallowedNonMethodNames = ["set"] + disallowedNonMethodNames
@ -80,6 +81,27 @@ def WebIDLTest(parser, harness):
# Simple Usage Tests # Simple Usage Tests
# #
shouldPass("Iterable (key only)",
"""
interface Foo1 {
iterable<long>;
};
""", iterableMembers)
shouldPass("Iterable (key and value)",
"""
interface Foo1 {
iterable<long, long>;
};
""", iterableMembers)
shouldPass("Maplike (readwrite)",
"""
interface Foo1 {
maplike<long, long>;
};
""", mapRWMembers)
shouldPass("Maplike (readwrite)", shouldPass("Maplike (readwrite)",
""" """
interface Foo1 { interface Foo1 {
@ -157,6 +179,22 @@ def WebIDLTest(parser, harness):
}; };
""") """)
shouldFail("Two iterable/setlikes on same interface",
"""
interface Foo1 {
iterable<long>;
maplike<long, long>;
};
""")
shouldFail("Two iterables on same interface",
"""
interface Foo1 {
iterable<long>;
iterable<long, long>;
};
""")
shouldFail("Two maplike/setlikes in partials", shouldFail("Two maplike/setlikes in partials",
""" """
interface Foo1 { interface Foo1 {
@ -177,6 +215,16 @@ def WebIDLTest(parser, harness):
}; };
""") """)
shouldFail("Conflicting maplike/iterable across inheritance",
"""
interface Foo1 {
maplike<long, long>;
};
interface Foo2 : Foo1 {
iterable<long>;
};
""")
shouldFail("Conflicting maplike/setlikes across multistep inheritance", shouldFail("Conflicting maplike/setlikes across multistep inheritance",
""" """
interface Foo1 { interface Foo1 {
@ -283,6 +331,8 @@ def WebIDLTest(parser, harness):
}; };
""" % (likeMember, conflictName)) """ % (likeMember, conflictName))
for member in disallowedIterableNames:
testConflictingMembers("iterable<long, long>", member, iterableMembers, False)
for member in mapDisallowedMemberNames: for member in mapDisallowedMemberNames:
testConflictingMembers("maplike<long, long>", member, mapRWMembers, False) testConflictingMembers("maplike<long, long>", member, mapRWMembers, False)
for member in disallowedMemberNames: for member in disallowedMemberNames:

View File

@ -0,0 +1,82 @@
/* 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 "mozilla/dom/TestInterfaceIterableDouble.h"
#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
#include "nsPIDOMWindow.h"
#include "mozilla/dom/BindingUtils.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceIterableDouble, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceIterableDouble)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceIterableDouble)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceIterableDouble)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
TestInterfaceIterableDouble::TestInterfaceIterableDouble(nsPIDOMWindow* aParent)
: mParent(aParent)
{
mValues.AppendElement(std::pair<nsString, nsString>(NS_LITERAL_STRING("a"),
NS_LITERAL_STRING("b")));
mValues.AppendElement(std::pair<nsString, nsString>(NS_LITERAL_STRING("c"),
NS_LITERAL_STRING("d")));
mValues.AppendElement(std::pair<nsString, nsString>(NS_LITERAL_STRING("e"),
NS_LITERAL_STRING("f")));
}
//static
already_AddRefed<TestInterfaceIterableDouble>
TestInterfaceIterableDouble::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<TestInterfaceIterableDouble> r = new TestInterfaceIterableDouble(window);
return r.forget();
}
JSObject*
TestInterfaceIterableDouble::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return TestInterfaceIterableDoubleBinding::Wrap(aCx, this, aGivenProto);
}
nsPIDOMWindow*
TestInterfaceIterableDouble::GetParentObject() const
{
return mParent;
}
size_t
TestInterfaceIterableDouble::GetIterableLength()
{
return mValues.Length();
}
nsAString&
TestInterfaceIterableDouble::GetKeyAtIndex(uint32_t aIndex)
{
MOZ_ASSERT(aIndex < mValues.Length());
return mValues.ElementAt(aIndex).first;
}
nsAString&
TestInterfaceIterableDouble::GetValueAtIndex(uint32_t aIndex)
{
MOZ_ASSERT(aIndex < mValues.Length());
return mValues.ElementAt(aIndex).second;
}
} // namespace dom
} // namespace mozilla

View File

@ -0,0 +1,51 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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_TestInterfaceIterableDouble_h
#define mozilla_dom_TestInterfaceIterableDouble_h
#include "nsWrapperCache.h"
#include "nsCOMPtr.h"
class nsPIDOMWindow;
namespace mozilla {
class ErrorResult;
namespace dom {
class GlobalObject;
// Implementation of test binding for webidl iterable interfaces, using
// primitives for value type
class TestInterfaceIterableDouble final : public nsISupports,
public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceIterableDouble)
explicit TestInterfaceIterableDouble(nsPIDOMWindow* aParent);
nsPIDOMWindow* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<TestInterfaceIterableDouble>
Constructor(const GlobalObject& aGlobal, ErrorResult& rv);
size_t GetIterableLength();
nsAString& GetKeyAtIndex(uint32_t aIndex);
nsAString& GetValueAtIndex(uint32_t aIndex);
private:
virtual ~TestInterfaceIterableDouble() {}
nsCOMPtr<nsPIDOMWindow> mParent;
nsTArray<std::pair<nsString, nsString>> mValues;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TestInterfaceIterableDouble_h

View File

@ -0,0 +1,78 @@
/* 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 "mozilla/dom/TestInterfaceIterableSingle.h"
#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
#include "nsPIDOMWindow.h"
#include "mozilla/dom/BindingUtils.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceIterableSingle, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceIterableSingle)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceIterableSingle)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceIterableSingle)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
TestInterfaceIterableSingle::TestInterfaceIterableSingle(nsPIDOMWindow* aParent)
: mParent(aParent)
{
for(int i = 0; i < 3; ++i) {
mValues.AppendElement(i);
}
}
//static
already_AddRefed<TestInterfaceIterableSingle>
TestInterfaceIterableSingle::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<TestInterfaceIterableSingle> r = new TestInterfaceIterableSingle(window);
return r.forget();
}
JSObject*
TestInterfaceIterableSingle::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return TestInterfaceIterableSingleBinding::Wrap(aCx, this, aGivenProto);
}
nsPIDOMWindow*
TestInterfaceIterableSingle::GetParentObject() const
{
return mParent;
}
size_t
TestInterfaceIterableSingle::GetIterableLength() const
{
return mValues.Length();
}
uint32_t
TestInterfaceIterableSingle::GetKeyAtIndex(uint32_t index) const
{
MOZ_ASSERT(index < mValues.Length());
return mValues.ElementAt(index);
}
uint32_t
TestInterfaceIterableSingle::GetValueAtIndex(uint32_t index) const
{
return GetKeyAtIndex(index);
}
} // namespace dom
} // namespace mozilla

View File

@ -0,0 +1,51 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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_TestInterfaceIterableSingle_h
#define mozilla_dom_TestInterfaceIterableSingle_h
#include "nsWrapperCache.h"
#include "nsCOMPtr.h"
class nsPIDOMWindow;
namespace mozilla {
class ErrorResult;
namespace dom {
class GlobalObject;
// Implementation of test binding for webidl iterable interfaces, using
// primitives for value type
class TestInterfaceIterableSingle final : public nsISupports,
public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceIterableSingle)
explicit TestInterfaceIterableSingle(nsPIDOMWindow* aParent);
nsPIDOMWindow* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<TestInterfaceIterableSingle>
Constructor(const GlobalObject& aGlobal, ErrorResult& rv);
size_t GetIterableLength() const;
uint32_t GetKeyAtIndex(uint32_t aIndex) const;
uint32_t GetValueAtIndex(uint32_t aIndex) const;
private:
virtual ~TestInterfaceIterableSingle() {}
nsCOMPtr<nsPIDOMWindow> mParent;
nsTArray<uint32_t> mValues;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TestInterfaceIterableSingle_h

View File

@ -3,7 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/TestInterfaceMaplike.h" #include "mozilla/dom/TestInterfaceMaplike.h"
#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h" #include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
#include "nsPIDOMWindow.h" #include "nsPIDOMWindow.h"
#include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/BindingUtils.h"

View File

@ -4,7 +4,7 @@
#include "mozilla/dom/TestInterfaceMaplikeObject.h" #include "mozilla/dom/TestInterfaceMaplikeObject.h"
#include "mozilla/dom/TestInterfaceMaplike.h" #include "mozilla/dom/TestInterfaceMaplike.h"
#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h" #include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
#include "nsPIDOMWindow.h" #include "nsPIDOMWindow.h"
#include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/BindingUtils.h"

View File

@ -3,7 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/TestInterfaceSetlike.h" #include "mozilla/dom/TestInterfaceSetlike.h"
#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h" #include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
#include "nsPIDOMWindow.h" #include "nsPIDOMWindow.h"
#include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/BindingUtils.h"

View File

@ -3,7 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/TestInterfaceSetlikeNode.h" #include "mozilla/dom/TestInterfaceSetlikeNode.h"
#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h" #include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
#include "nsPIDOMWindow.h" #include "nsPIDOMWindow.h"
#include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/BindingUtils.h"

View File

@ -68,3 +68,5 @@ skip-if = debug == false
skip-if = debug == false skip-if = debug == false
[test_jsimplemented_eventhandler.html] [test_jsimplemented_eventhandler.html]
skip-if = debug == false skip-if = debug == false
[test_iterable.html]
skip-if = debug == false

View File

@ -57,7 +57,6 @@
var m; var m;
var testSet; var testSet;
var testIndex; var testIndex;
var iterable;
// Simple map creation and functionality test // Simple map creation and functionality test
info("SimpleMap: Testing simple map creation and functionality"); info("SimpleMap: Testing simple map creation and functionality");
m = new TestInterfaceMaplike(); m = new TestInterfaceMaplike();

View File

@ -0,0 +1,112 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test Iterable Interface</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, function() {
base_properties = [["entries", "function", 0],
["keys", "function", 0],
["values", "function", 0]]
var testExistence = function testExistence(prefix, obj, properties) {
for (var [name, type, args] of properties) {
// Properties are somewhere up the proto chain, hasOwnProperty won't work
isnot(obj[name], undefined,
`${prefix} object has property ${name}`);
is(typeof obj[name], type,
`${prefix} object property ${name} is a ${type}`);
// Check function length
if (type == "function") {
is(obj[name].length, args,
`${prefix} object property ${name} is length ${args}`);
is(obj[name].name, name,
`${prefix} object method name is ${name}`);
}
// Find where property is on proto chain, check for enumerablility there.
var owner = obj;
while (owner) {
var propDesc = Object.getOwnPropertyDescriptor(owner, name);
if (propDesc) {
ok(propDesc.enumerable,
`${prefix} object property ${name} is enumerable`);
break;
}
owner = Object.getPrototypeOf(owner);
}
}
}
var itr;
// Simple single type iterable creation and functionality test
info("IterableSingle: Testing simple iterable creation and functionality");
itr = new TestInterfaceIterableSingle();
testExistence("IterableSingle: ", itr, base_properties);
var key_itr = itr.keys();
var value_itr = itr.values();
var entries_itr = itr.entries();
for (var i = 0; i < 3; ++i) {
var key = key_itr.next();
var value = value_itr.next();
var entry = entries_itr.next();
is(key.value, i, "IterableSingle: Key iterator value should be " + i);
is(value.value, key.value, "IterableSingle: Value iterator value should be " + key.value);
is(entry.value[0], i, "IterableSingle: Entry iterator value 0 should be " + i);
is(entry.value[1], i, "IterableSingle: Entry iterator value 1 should be " + i);
}
var key = key_itr.next();
var value = value_itr.next();
var entry = entries_itr.next();
is(key.value, undefined, "IterableSingle: Key iterator value should be undefined");
is(key.done, true, "IterableSingle: Key iterator done should be true");
is(value.value, undefined, "IterableSingle: Value iterator value should be undefined");
is(value.done, true, "IterableSingle: Value iterator done should be true");
is(entry.value, undefined, "IterableDouble: Entry iterator value should be undefined");
is(entry.done, true, "IterableSingle: Entry iterator done should be true");
is(Object.prototype.toString.call(Object.getPrototypeOf(key_itr)),
"[object TestInterfaceIterableSingleIteratorPrototype]",
"iterator prototype should have the right brand");
// Simple dual type iterable creation and functionality test
info("IterableDouble: Testing simple iterable creation and functionality");
itr = new TestInterfaceIterableDouble();
testExistence("IterableDouble: ", itr, base_properties);
var elements = [["a", "b"], ["c", "d"], ["e", "f"]]
var key_itr = itr.keys();
var value_itr = itr.values();
var entries_itr = itr.entries();
for (var i = 0; i < 3; ++i) {
var key = key_itr.next();
var value = value_itr.next();
var entry = entries_itr.next();
is(key.value, elements[i][0], "IterableDouble: Key iterator value should be " + elements[i][0]);
is(value.value, elements[i][1], "IterableDouble: Value iterator value should be " + elements[i][1]);
is(entry.value[0], elements[i][0], "IterableDouble: Entry iterator value 0 should be " + elements[i][0]);
is(entry.value[1], elements[i][1], "IterableDouble: Entry iterator value 1 should be " + elements[i][1]);
}
var key = key_itr.next();
var value = value_itr.next();
var entry = entries_itr.next()
is(key.value, undefined, "IterableDouble: Key iterator value should be undefined");
is(key.done, true, "IterableDouble: Key iterator done should be true");
is(value.value, undefined, "IterableDouble: Value iterator value should be undefined");
is(value.done, true, "IterableDouble: Value iterator done should be true");
is(entry.value, undefined, "IterableDouble: Entry iterator value should be undefined");
is(entry.done, true, "IterableDouble: Entry iterator done should be true");
is(Object.prototype.toString.call(Object.getPrototypeOf(key_itr)),
"[object TestInterfaceIterableDoubleIteratorPrototype]",
"iterator prototype should have the right brand");
SimpleTest.finish();
});
</script>
</body>
</html>

10
dom/cache/Context.cpp vendored
View File

@ -1001,7 +1001,15 @@ Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
MOZ_ASSERT(!mDirectoryLock); MOZ_ASSERT(!mDirectoryLock);
mDirectoryLock = aDirectoryLock; mDirectoryLock = aDirectoryLock;
if (mState == STATE_CONTEXT_CANCELED || NS_FAILED(aRv)) { // If we opening the context failed, but we were not explicitly canceled,
// still treat the entire context as canceled. We don't want to allow
// new actions to be dispatched. We also cannot leave the context in
// the INIT state after failing to open.
if (NS_FAILED(aRv)) {
mState = STATE_CONTEXT_CANCELED;
}
if (mState == STATE_CONTEXT_CANCELED) {
for (uint32_t i = 0; i < mPendingActions.Length(); ++i) { for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv); mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv);
} }

View File

@ -202,7 +202,7 @@ TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut,
{ {
aOut.type() = aIn.Type(); aOut.type() = aIn.Type();
aIn.GetUrl(aOut.url()); aIn.GetUnfilteredUrl(aOut.url());
if (aOut.url() != EmptyCString()) { if (aOut.url() != EmptyCString()) {
// Pass all Response URL schemes through... The spec only requires we take // Pass all Response URL schemes through... The spec only requires we take

View File

@ -400,7 +400,9 @@ HTMLCanvasElement::CreateContext(CanvasContextType aContextType)
// Add Observer for webgl canvas. // Add Observer for webgl canvas.
if (aContextType == CanvasContextType::WebGL1 || if (aContextType == CanvasContextType::WebGL1 ||
aContextType == CanvasContextType::WebGL2) { aContextType == CanvasContextType::WebGL2) {
mContextObserver = new HTMLCanvasElementObserver(this); if (!mContextObserver) {
mContextObserver = new HTMLCanvasElementObserver(this);
}
} }
ret->SetCanvasElement(this); ret->SetCanvasElement(this);
@ -779,7 +781,9 @@ HTMLCanvasElement::TransferControlToOffscreen(ErrorResult& aRv)
sz.height, sz.height,
GetCompositorBackendType(), GetCompositorBackendType(),
renderer); renderer);
mContextObserver = new HTMLCanvasElementObserver(this); if (!mContextObserver) {
mContextObserver = new HTMLCanvasElementObserver(this);
}
} else { } else {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
} }

View File

@ -151,6 +151,7 @@ public:
return nsRefPtr<ImportLoader>(mImportLoader).forget(); return nsRefPtr<ImportLoader>(mImportLoader).forget();
} }
virtual CORSMode GetCORSMode() const override;
protected: protected:
virtual ~HTMLLinkElement(); virtual ~HTMLLinkElement();
@ -161,7 +162,6 @@ protected:
nsAString& aMedia, nsAString& aMedia,
bool* aIsScoped, bool* aIsScoped,
bool* aIsAlternate) override; bool* aIsAlternate) override;
virtual CORSMode GetCORSMode() const override;
protected: protected:
// nsGenericHTMLElement // nsGenericHTMLElement
virtual void GetItemValueText(DOMString& text) override; virtual void GetItemValueText(DOMString& text) override;

View File

@ -5,9 +5,7 @@ support-files =
[test_Document-createElement-namespace.html.json] [test_Document-createElement-namespace.html.json]
[test_Document-createElementNS.html.json] [test_Document-createElementNS.html.json]
[test_Document-getElementsByTagName.html.json]
[test_Node-properties.html.json] [test_Node-properties.html.json]
[test_attributes.html.json] [test_attributes.html.json]
[test_case.html.json] [test_case.html.json]
[test_getElementsByClassName-10.xml.json]
[test_getElementsByClassName-11.xml.json] [test_getElementsByClassName-11.xml.json]

View File

@ -1,4 +0,0 @@
{
"Document.getElementsByTagName 1": true,
"Document.getElementsByTagName 2": true
}

View File

@ -1,7 +1,5 @@
{ {
"getElementsByTagName abc": true, "getElementsByTagName abc": true,
"getElementsByTagName Abc": true, "getElementsByTagName Abc": true,
"getElementsByTagName ABC": true, "getElementsByTagName ABC": true
"getElementsByTagName \u00e4": true,
"getElementsByTagName \u00c4": true
} }

View File

@ -1,3 +0,0 @@
{
"document.getElementsByClassName(): compound": true
}

View File

@ -36,7 +36,7 @@ public:
nsresult Flush(); nsresult Flush();
// Shutdown decoder and rejects the init promise. // Shutdown decoder and rejects the init promise.
nsresult Shutdown(); virtual nsresult Shutdown();
// True if sample is queued. // True if sample is queued.
bool HasQueuedSample(); bool HasQueuedSample();

View File

@ -59,16 +59,22 @@ GonkVideoDecoderManager::GonkVideoDecoderManager(
nsIntSize frameSize(mVideoWidth, mVideoHeight); nsIntSize frameSize(mVideoWidth, mVideoHeight);
mPicture = pictureRect; mPicture = pictureRect;
mInitialFrame = frameSize; mInitialFrame = frameSize;
mVideoListener = new VideoResourceListener(this); mVideoListener = new VideoResourceListener();
} }
GonkVideoDecoderManager::~GonkVideoDecoderManager() GonkVideoDecoderManager::~GonkVideoDecoderManager()
{ {
mVideoListener->NotifyManagerRelease();
MOZ_COUNT_DTOR(GonkVideoDecoderManager); MOZ_COUNT_DTOR(GonkVideoDecoderManager);
} }
nsresult
GonkVideoDecoderManager::Shutdown()
{
mVideoCodecRequest.DisconnectIfExists();
return GonkDecoderManager::Shutdown();
}
nsRefPtr<MediaDataDecoder::InitPromise> nsRefPtr<MediaDataDecoder::InitPromise>
GonkVideoDecoderManager::Init() GonkVideoDecoderManager::Init()
{ {
@ -107,6 +113,16 @@ GonkVideoDecoderManager::Init()
} }
nsRefPtr<InitPromise> p = mInitPromise.Ensure(__func__); nsRefPtr<InitPromise> p = mInitPromise.Ensure(__func__);
android::sp<GonkVideoDecoderManager> self = this;
mVideoCodecRequest.Begin(mVideoListener->Init()
->Then(mReaderTaskQueue, __func__,
[self] (bool) -> void {
self->mVideoCodecRequest.Complete();
self->codecReserved();
}, [self] (bool) -> void {
self->mVideoCodecRequest.Complete();
self->codecCanceled();
}));
mDecoder = MediaCodecProxy::CreateByType(mDecodeLooper, mMimeType.get(), false, mVideoListener); mDecoder = MediaCodecProxy::CreateByType(mDecodeLooper, mMimeType.get(), false, mVideoListener);
mDecoder->AsyncAskMediaCodec(); mDecoder->AsyncAskMediaCodec();
@ -399,6 +415,9 @@ void GonkVideoDecoderManager::ReleaseVideoBuffer() {
void void
GonkVideoDecoderManager::codecReserved() GonkVideoDecoderManager::codecReserved()
{ {
if (mInitPromise.IsEmpty()) {
return;
}
GVDM_LOG("codecReserved"); GVDM_LOG("codecReserved");
sp<AMessage> format = new AMessage; sp<AMessage> format = new AMessage;
sp<Surface> surface; sp<Surface> surface;
@ -427,7 +446,7 @@ GonkVideoDecoderManager::codecReserved()
return; return;
} }
mInitPromise.ResolveIfExists(TrackType::kVideoTrack, __func__); mInitPromise.Resolve(TrackType::kVideoTrack, __func__);
} }
void void
@ -456,66 +475,24 @@ GonkVideoDecoderManager::onMessageReceived(const sp<AMessage> &aMessage)
} }
} }
GonkVideoDecoderManager::VideoResourceListener::VideoResourceListener(GonkVideoDecoderManager *aManager) GonkVideoDecoderManager::VideoResourceListener::VideoResourceListener()
: mManager(aManager)
{ {
} }
GonkVideoDecoderManager::VideoResourceListener::~VideoResourceListener() GonkVideoDecoderManager::VideoResourceListener::~VideoResourceListener()
{ {
mManager = nullptr;
} }
void void
GonkVideoDecoderManager::VideoResourceListener::codecReserved() GonkVideoDecoderManager::VideoResourceListener::codecReserved()
{ {
// This class holds VideoResourceListener reference to prevent it's destroyed. mVideoCodecPromise.Resolve(true, __func__);
class CodecListenerHolder : public nsRunnable {
public:
CodecListenerHolder(VideoResourceListener* aListener)
: mVideoListener(aListener) {}
NS_IMETHOD Run()
{
mVideoListener->NotifyCodecReserved();
mVideoListener = nullptr;
return NS_OK;
}
android::sp<VideoResourceListener> mVideoListener;
};
if (mManager) {
nsRefPtr<CodecListenerHolder> runner = new CodecListenerHolder(this);
mManager->mReaderTaskQueue->Dispatch(runner.forget());
}
} }
void void
GonkVideoDecoderManager::VideoResourceListener::codecCanceled() GonkVideoDecoderManager::VideoResourceListener::codecCanceled()
{ {
if (mManager) { mVideoCodecPromise.Reject(true, __func__);
MOZ_ASSERT(mManager->mReaderTaskQueue->IsCurrentThreadIn());
nsCOMPtr<nsIRunnable> r =
NS_NewNonOwningRunnableMethod(mManager, &GonkVideoDecoderManager::codecCanceled);
mManager->mReaderTaskQueue->Dispatch(r.forget());
}
}
void
GonkVideoDecoderManager::VideoResourceListener::NotifyManagerRelease()
{
MOZ_ASSERT_IF(mManager, mManager->mReaderTaskQueue->IsCurrentThreadIn());
mManager = nullptr;
}
void
GonkVideoDecoderManager::VideoResourceListener::NotifyCodecReserved()
{
if (mManager) {
MOZ_ASSERT(mManager->mReaderTaskQueue->IsCurrentThreadIn());
mManager->codecReserved();
}
} }
uint8_t * uint8_t *

View File

@ -36,6 +36,8 @@ typedef android::MediaCodecProxy MediaCodecProxy;
typedef mozilla::layers::TextureClient TextureClient; typedef mozilla::layers::TextureClient TextureClient;
public: public:
typedef MozPromise<bool /* aIgnored */, bool /* aIgnored */, /* IsExclusive = */ true> MediaResourcePromise;
GonkVideoDecoderManager(mozilla::layers::ImageContainer* aImageContainer, GonkVideoDecoderManager(mozilla::layers::ImageContainer* aImageContainer,
const VideoInfo& aConfig); const VideoInfo& aConfig);
@ -46,6 +48,8 @@ public:
nsresult Output(int64_t aStreamOffset, nsresult Output(int64_t aStreamOffset,
nsRefPtr<MediaData>& aOutput) override; nsRefPtr<MediaData>& aOutput) override;
nsresult Shutdown() override;
static void RecycleCallback(TextureClient* aClient, void* aClosure); static void RecycleCallback(TextureClient* aClient, void* aClosure);
private: private:
@ -67,24 +71,25 @@ private:
class VideoResourceListener : public android::MediaCodecProxy::CodecResourceListener class VideoResourceListener : public android::MediaCodecProxy::CodecResourceListener
{ {
public: public:
VideoResourceListener(GonkVideoDecoderManager *aManager); VideoResourceListener();
~VideoResourceListener(); ~VideoResourceListener();
nsRefPtr<MediaResourcePromise> Init()
{
nsRefPtr<MediaResourcePromise> p = mVideoCodecPromise.Ensure(__func__);
return p.forget();
}
void codecReserved() override; void codecReserved() override;
void codecCanceled() override; void codecCanceled() override;
void NotifyManagerRelease();
void NotifyCodecReserved();
private: private:
// Forbidden // Forbidden
VideoResourceListener() = delete;
VideoResourceListener(const VideoResourceListener &rhs) = delete; VideoResourceListener(const VideoResourceListener &rhs) = delete;
const VideoResourceListener &operator=(const VideoResourceListener &rhs) = delete; const VideoResourceListener &operator=(const VideoResourceListener &rhs) = delete;
GonkVideoDecoderManager *mManager; MozPromiseHolder<MediaResourcePromise> mVideoCodecPromise;
}; };
friend class VideoResourceListener;
bool SetVideoFormat(); bool SetVideoFormat();
@ -113,6 +118,7 @@ private:
MediaInfo mInfo; MediaInfo mInfo;
android::sp<VideoResourceListener> mVideoListener; android::sp<VideoResourceListener> mVideoListener;
MozPromiseRequestHolder<MediaResourcePromise> mVideoCodecRequest;
FrameInfo mFrameInfo; FrameInfo mFrameInfo;
// color converter // color converter

View File

@ -1847,6 +1847,10 @@ nsPluginHost::SiteHasData(nsIPluginTag* plugin, const nsACString& domain,
nsPluginHost::SpecialType nsPluginHost::SpecialType
nsPluginHost::GetSpecialType(const nsACString & aMIMEType) nsPluginHost::GetSpecialType(const nsACString & aMIMEType)
{ {
if (aMIMEType.LowerCaseEqualsASCII("application/x-test")) {
return eSpecialType_Test;
}
if (aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash") || if (aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash") ||
aMIMEType.LowerCaseEqualsASCII("application/futuresplash")) { aMIMEType.LowerCaseEqualsASCII("application/futuresplash")) {
return eSpecialType_Flash; return eSpecialType_Flash;
@ -2446,6 +2450,7 @@ nsPluginHost::FindPluginsInContent(bool aCreatePluginList, bool* aPluginsChanged
nsTArray<nsCString>(tag.extensions()), nsTArray<nsCString>(tag.extensions()),
tag.isJavaPlugin(), tag.isJavaPlugin(),
tag.isFlashPlugin(), tag.isFlashPlugin(),
tag.supportsAsyncInit(),
tag.lastModifiedTime(), tag.lastModifiedTime(),
tag.isFromExtension()); tag.isFromExtension());
AddPluginTag(pluginTag); AddPluginTag(pluginTag);
@ -2682,6 +2687,7 @@ nsPluginHost::FindPluginsForContent(uint32_t aPluginEpoch,
tag->Extensions(), tag->Extensions(),
tag->mIsJavaPlugin, tag->mIsJavaPlugin,
tag->mIsFlashPlugin, tag->mIsFlashPlugin,
tag->mSupportsAsyncInit,
tag->FileName(), tag->FileName(),
tag->Version(), tag->Version(),
tag->mLastModifiedTime, tag->mLastModifiedTime,

Some files were not shown because too many files have changed in this diff Show More