зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to b2g-inbound
This commit is contained in:
Коммит
1355b0a297
|
@ -6,6 +6,7 @@
|
|||
(?i)(^|/)TAGS$
|
||||
(^|/)ID$
|
||||
(^|/)\.DS_Store$
|
||||
.*\.egg-info
|
||||
|
||||
# Vim swap files.
|
||||
^\.sw.$
|
||||
|
|
|
@ -32,5 +32,5 @@ exports.locale = function locale() {
|
|||
// Returns the short locale code: ja, en, fr
|
||||
exports.language = function language() {
|
||||
return bestMatchingLocale ? bestMatchingLocale.split("-")[0].toLowerCase()
|
||||
: null;
|
||||
: "en";
|
||||
}
|
||||
|
|
|
@ -58,12 +58,12 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
|
|||
'browser.search.update': False,
|
||||
'browser.search.suggest.enabled' : False,
|
||||
'browser.safebrowsing.enabled' : False,
|
||||
'browser.safebrowsing.updateURL': 'http://localhost/safebrowsing-dummy/update',
|
||||
'browser.safebrowsing.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.provider.google.updateURL': 'http://localhost/safebrowsing-dummy/update',
|
||||
'browser.safebrowsing.provider.google.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
|
||||
'browser.selfsupport.url': 'https://localhost/selfsupport-dummy',
|
||||
'browser.trackingprotection.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.trackingprotection.updateURL': 'http://localhost/safebrowsing-dummy/update',
|
||||
'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update',
|
||||
|
||||
# Disable app update
|
||||
'app.update.enabled' : False,
|
||||
|
@ -120,8 +120,10 @@ DEFAULT_FIREFOX_PREFS = {
|
|||
# Make url-classifier updates so rare that they won't affect tests.
|
||||
'urlclassifier.updateinterval' : 172800,
|
||||
# Point the url-classifier to a nonexistent local URL for fast failures.
|
||||
'browser.safebrowsing.provider.0.gethashURL' : 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.provider.0.updateURL' : 'http://localhost/safebrowsing-dummy/update',
|
||||
'browser.safebrowsing.provider.google.gethashURL' : 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.provider.google.updateURL' : 'http://localhost/safebrowsing-dummy/update',
|
||||
'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update',
|
||||
}
|
||||
|
||||
# When launching a temporary new Thunderbird profile, use these preferences.
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
"devtools.errorconsole.enabled": true,
|
||||
"devtools.chrome.enabled": true,
|
||||
"urlclassifier.updateinterval": 172800,
|
||||
"browser.safebrowsing.provider.0.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.safebrowsing.provider.0.updateURL": "http://localhost/safebrowsing-dummy/update"
|
||||
"browser.safebrowsing.provider.google.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.safebrowsing.provider.google.updateURL": "http://localhost/safebrowsing-dummy/update",
|
||||
"browser.safebrowsing.provider.mozilla.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.safebrowsing.provider.mozilla.updateURL": "http://localhost/safebrowsing-dummy/update"
|
||||
}
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
"browser.search.update": false,
|
||||
"browser.search.suggest.enabled": false,
|
||||
"browser.safebrowsing.enabled": false,
|
||||
"browser.safebrowsing.updateURL": "http://localhost/safebrowsing-dummy/update",
|
||||
"browser.safebrowsing.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.safebrowsing.malware.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
|
||||
"browser.safebrowsing.provider.google.updateURL": "http://localhost/safebrowsing-dummy/update",
|
||||
"browser.safebrowsing.provider.google.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.safebrowsing.provider.google.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
|
||||
"browser.selfsupport.url": "https://localhost/selfsupport-dummy",
|
||||
"browser.trackingprotection.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.trackingprotection.updateURL": "http://localhost/safebrowsing-dummy/update",
|
||||
"browser.safebrowsing.provider.mozilla.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.safebrowsing.provider.mozilla.updateURL": "http://localhost/safebrowsing-dummy/update",
|
||||
"browser.newtabpage.directory.source": "data:application/json,{'jetpack':1}",
|
||||
"browser.newtabpage.directory.ping": "",
|
||||
"extensions.update.url": "http://localhost/extensions-dummy/updateURL",
|
||||
|
|
|
@ -356,44 +356,29 @@ pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
|
|||
pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
|
||||
|
||||
#ifdef MOZ_SAFE_BROWSING
|
||||
// Safe browsing does nothing unless this pref is set
|
||||
pref("browser.safebrowsing.enabled", false);
|
||||
|
||||
// Prevent loading of pages identified as malware
|
||||
pref("browser.safebrowsing.malware.enabled", false);
|
||||
|
||||
pref("browser.safebrowsing.downloads.enabled", false);
|
||||
pref("browser.safebrowsing.downloads.remote.enabled", false);
|
||||
pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
|
||||
pref("browser.safebrowsing.debug", false);
|
||||
pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
|
||||
pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
|
||||
|
||||
pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
|
||||
pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
|
||||
pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
|
||||
pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
|
||||
pref("browser.safebrowsing.provider.google.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
|
||||
|
||||
pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
|
||||
|
||||
pref("browser.safebrowsing.id", "Firefox");
|
||||
|
||||
// Tables for application reputation.
|
||||
pref("urlclassifier.downloadBlockTable", "goog-badbinurl-shavar");
|
||||
|
||||
// Non-enhanced mode (local url lists) URL list to check for updates
|
||||
pref("browser.safebrowsing.provider.0.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client={moz:client}&appver={moz:version}&pver=2.2&key=%GOOGLE_API_KEY%");
|
||||
|
||||
pref("browser.safebrowsing.dataProvider", 0);
|
||||
|
||||
// Does the provider name need to be localizable?
|
||||
pref("browser.safebrowsing.provider.0.name", "Google");
|
||||
pref("browser.safebrowsing.provider.0.reportURL", "https://safebrowsing.google.com/safebrowsing/report?");
|
||||
pref("browser.safebrowsing.provider.0.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client={moz:client}&appver={moz:version}&pver=2.2");
|
||||
|
||||
// HTML report pages
|
||||
pref("browser.safebrowsing.provider.0.reportGenericURL", "http://{moz:locale}.phish-generic.mozilla.com/?hl={moz:locale}");
|
||||
pref("browser.safebrowsing.provider.0.reportErrorURL", "http://{moz:locale}.phish-error.mozilla.com/?hl={moz:locale}");
|
||||
pref("browser.safebrowsing.provider.0.reportPhishURL", "http://{moz:locale}.phish-report.mozilla.com/?hl={moz:locale}");
|
||||
pref("browser.safebrowsing.provider.0.reportMalwareURL", "http://{moz:locale}.malware-report.mozilla.com/?hl={moz:locale}");
|
||||
pref("browser.safebrowsing.provider.0.reportMalwareErrorURL", "http://{moz:locale}.malware-error.mozilla.com/?hl={moz:locale}");
|
||||
|
||||
// FAQ URLs
|
||||
|
||||
// The number of random entries to send with a gethash request.
|
||||
pref("urlclassifier.gethashnoise", 4);
|
||||
|
||||
|
@ -405,9 +390,6 @@ pref("urlclassifier.gethash.timeout_ms", 5000);
|
|||
// the database.
|
||||
pref("urlclassifier.max-complete-age", 2700);
|
||||
|
||||
// URL for checking the reason for a malware warning.
|
||||
pref("browser.safebrowsing.malware.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
|
||||
|
||||
// Tracking protection
|
||||
pref("privacy.trackingprotection.enabled", true);
|
||||
pref("privacy.trackingprotection.pbmode.enabled", false);
|
||||
|
|
|
@ -93,12 +93,23 @@ this.FxAccountsMgmtService = {
|
|||
delete data.accountId;
|
||||
}
|
||||
|
||||
// XXX dirty hack because Gaia is sending getAccounts.
|
||||
// Bug 1202450 dirty hack because Gaia is sending getAccounts.
|
||||
if (data.method == "getAccounts") {
|
||||
data.method = "getAccount";
|
||||
}
|
||||
|
||||
switch(data.method) {
|
||||
case "getAssertion":
|
||||
let principal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||
let audience = msg.audience || principal.originNoSuffix;
|
||||
FxAccountsManager.getAssertion(audience, principal, {
|
||||
silent: msg.silent || false
|
||||
}).then(result => {
|
||||
self._onFulfill(msg.id, result);
|
||||
}, reason => {
|
||||
self._onReject(msg.id, reason);
|
||||
});
|
||||
break;
|
||||
case "getAccount":
|
||||
case "getKeys":
|
||||
FxAccountsManager[data.method]().then(
|
||||
|
|
|
@ -957,15 +957,15 @@ pref("browser.safebrowsing.downloads.remote.enabled", true);
|
|||
pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
|
||||
pref("browser.safebrowsing.debug", false);
|
||||
|
||||
pref("browser.safebrowsing.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
|
||||
pref("browser.safebrowsing.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
|
||||
pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
|
||||
pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
|
||||
pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
|
||||
pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
|
||||
pref("browser.safebrowsing.provider.google.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
|
||||
|
||||
pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
|
||||
pref("browser.safebrowsing.malware.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
|
||||
|
||||
pref("browser.safebrowsing.appRepURL", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
|
||||
|
||||
#ifdef MOZILLA_OFFICIAL
|
||||
// Normally the "client ID" sent in updates is appinfo.name, but for
|
||||
// official Firefox releases from Mozilla we use a special identifier.
|
||||
|
|
|
@ -103,9 +103,11 @@ let gSyncUI = {
|
|||
// We want to treat "account needs verification" as "needs setup". So
|
||||
// "reach in" to Weave.Status._authManager to check whether we the signed-in
|
||||
// user is verified.
|
||||
// Referencing Weave.Status spins a nested event loop to initialize the
|
||||
// authManager, so this should always return a value directly.
|
||||
// This only applies to fxAccounts-based Sync.
|
||||
// NOTE: We used to have this _authManager hack to avoid a nested
|
||||
// event-loop from querying Weave.Status.checkSetup() - while that's no
|
||||
// longer true, we do still have the FxA-specific requirement of checking
|
||||
// the verified state - so the hack remains. We should consider refactoring
|
||||
// Sync's "check setup" capabilities to take this into account at some point...
|
||||
if (Weave.Status._authManager._signedInUser !== undefined) {
|
||||
// If we have a signed in user already, and that user is not verified,
|
||||
// revert to the "needs setup" state.
|
||||
|
|
|
@ -340,7 +340,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
let engine = Services.search.getEngineByName(action.params.engineName);
|
||||
let query = action.params.searchSuggestion ||
|
||||
action.params.searchQuery;
|
||||
let submission = engine.getSubmission(query);
|
||||
let submission = engine.getSubmission(query, null, "keyword");
|
||||
|
||||
url = submission.uri.spec;
|
||||
postData = submission.postData;
|
||||
|
@ -1390,7 +1390,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
let engine = Services.search.getEngineByName(action.params.engineName);
|
||||
let query = action.params.searchSuggestion ||
|
||||
action.params.searchQuery;
|
||||
let submission = engine.getSubmission(query);
|
||||
let submission = engine.getSubmission(query, null, "keyword");
|
||||
url = submission.uri.spec;
|
||||
options.postData = submission.postData;
|
||||
break;
|
||||
|
|
|
@ -293,6 +293,15 @@ html[dir="rtl"] .tab-view li:nth-child(2).selected ~ .slide-bar {
|
|||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.room-list-loading {
|
||||
margin: 5rem 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.room-list-loading > img {
|
||||
width: 66px;
|
||||
}
|
||||
|
||||
|
||||
/* Rooms */
|
||||
.rooms {
|
||||
|
@ -321,10 +330,10 @@ html[dir="rtl"] .tab-view li:nth-child(2).selected ~ .slide-bar {
|
|||
}
|
||||
|
||||
.new-room-view > .context {
|
||||
border-top: 1px solid #ebebeb;
|
||||
flex: 1;
|
||||
border-radius: 3px 3px 0 0;
|
||||
margin: 1rem 0 .5rem;
|
||||
padding: 1rem 15px;
|
||||
margin: .5rem 0;
|
||||
padding: .5rem 15px 1rem;
|
||||
}
|
||||
|
||||
.new-room-view > .context > .context-enabled {
|
||||
|
|
|
@ -809,6 +809,21 @@ loop.panel = (function(_, mozL10n) {
|
|||
mozL10n.get("display_name_guest");
|
||||
},
|
||||
|
||||
/**
|
||||
* Let the user know we're loading rooms
|
||||
* @returns {Object} React render
|
||||
*/
|
||||
_renderLoadingRoomsView: function() {
|
||||
return (
|
||||
React.createElement("div", {className: "room-list"},
|
||||
React.createElement("div", {className: "room-list-loading"},
|
||||
React.createElement("img", {src: "loop/shared/img/animated-spinner.svg"})
|
||||
),
|
||||
this._renderNewRoomButton()
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
_renderNoRoomsView: function() {
|
||||
return (
|
||||
React.createElement("div", {className: "room-list"},
|
||||
|
@ -841,6 +856,10 @@ loop.panel = (function(_, mozL10n) {
|
|||
console.error("RoomList error", this.state.error);
|
||||
}
|
||||
|
||||
if (this.state.pendingInitialRetrieval) {
|
||||
return this._renderLoadingRoomsView();
|
||||
}
|
||||
|
||||
if (!this.state.rooms.length) {
|
||||
return this._renderNoRoomsView();
|
||||
}
|
||||
|
|
|
@ -809,6 +809,21 @@ loop.panel = (function(_, mozL10n) {
|
|||
mozL10n.get("display_name_guest");
|
||||
},
|
||||
|
||||
/**
|
||||
* Let the user know we're loading rooms
|
||||
* @returns {Object} React render
|
||||
*/
|
||||
_renderLoadingRoomsView: function() {
|
||||
return (
|
||||
<div className="room-list">
|
||||
<div className="room-list-loading">
|
||||
<img src="loop/shared/img/animated-spinner.svg" />
|
||||
</div>
|
||||
{this._renderNewRoomButton()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
_renderNoRoomsView: function() {
|
||||
return (
|
||||
<div className="room-list">
|
||||
|
@ -841,6 +856,10 @@ loop.panel = (function(_, mozL10n) {
|
|||
console.error("RoomList error", this.state.error);
|
||||
}
|
||||
|
||||
if (this.state.pendingInitialRetrieval) {
|
||||
return this._renderLoadingRoomsView();
|
||||
}
|
||||
|
||||
if (!this.state.rooms.length) {
|
||||
return this._renderNoRoomsView();
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ loop.store = loop.store || {};
|
|||
activeRoom: this.activeRoomStore ? this.activeRoomStore.getStoreState() : {},
|
||||
error: null,
|
||||
pendingCreation: false,
|
||||
pendingInitialRetrieval: false,
|
||||
pendingInitialRetrieval: true,
|
||||
rooms: [],
|
||||
savingContext: false
|
||||
};
|
||||
|
|
|
@ -593,8 +593,20 @@ loop.roomViews = (function(mozL10n) {
|
|||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if the invitation controls should be shown.
|
||||
*
|
||||
* @return {Boolean} True if there's no guests.
|
||||
*/
|
||||
_shouldRenderInvitationOverlay: function() {
|
||||
return (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS);
|
||||
var hasGuests = typeof this.state.participants === "object" &&
|
||||
this.state.participants.filter(function(participant) {
|
||||
return !participant.owner;
|
||||
}).length > 0;
|
||||
|
||||
// Don't show if the room has participants whether from the room state or
|
||||
// there being non-owner guests in the participants array.
|
||||
return this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS && !hasGuests;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -593,8 +593,20 @@ loop.roomViews = (function(mozL10n) {
|
|||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if the invitation controls should be shown.
|
||||
*
|
||||
* @return {Boolean} True if there's no guests.
|
||||
*/
|
||||
_shouldRenderInvitationOverlay: function() {
|
||||
return (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS);
|
||||
var hasGuests = typeof this.state.participants === "object" &&
|
||||
this.state.participants.filter(function(participant) {
|
||||
return !participant.owner;
|
||||
}).length > 0;
|
||||
|
||||
// Don't show if the room has participants whether from the room state or
|
||||
// there being non-owner guests in the participants array.
|
||||
return this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS && !hasGuests;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g fill="#0095FF"><path opacity=".15" d="M4.9 11.8c.2-.4.7-.5 1.1-.3.4.2.5.7.3 1.1l-1.2 2.1c-.2.3-.7.5-1.1.2-.4-.2-.5-.7-.3-1.1l1.2-2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite"/></path><path opacity=".2" d="M3.4 9.7c.4-.2.9-.1 1.1.3.2.4.1.9-.3 1.1l-2.1 1.2c-.4.2-.9.1-1.1-.3-.2-.4-.1-.9.3-1.1l2.1-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.08s"/></path><path opacity=".25" d="M3.2 7.2c.4 0 .8.4.8.8s-.4.8-.8.8H.8C.4 8.8 0 8.4 0 8s.4-.8.8-.8h2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.16s"/></path><path opacity=".35" d="M1.4 5.1C1 4.9.8 4.4 1.1 4c.2-.4.7-.5 1.1-.3l2.1 1.2c.3.2.5.7.2 1.1-.2.4-.7.5-1.1.3l-2-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.24s"/></path><path opacity=".45" d="M3.7 2.2c-.2-.4-.1-.9.3-1.1.4-.2.9-.1 1.1.3l1.2 2.1c.2.3.1.8-.3 1-.4.3-.9.1-1.1-.3l-1.2-2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.32s"/></path><path opacity=".5" d="M8.8 3.2c0 .4-.4.8-.8.8s-.8-.4-.8-.8V.8c0-.4.4-.8.8-.8s.8.4.8.8v2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.40s"/></path><path opacity=".55" d="M10.9 1.4c.2-.4.7-.6 1.1-.3.4.2.5.7.3 1.1l-1.2 2.1c-.2.4-.7.5-1.1.3-.4-.3-.5-.8-.3-1.2l1.2-2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.48s"/></path><path opacity=".6" d="M13.8 3.7c.4-.2.9-.1 1.1.3.2.4.1.9-.3 1.1l-2.1 1.2c-.4.2-.9.1-1.1-.3-.2-.4-.1-.9.3-1.1l2.1-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.56s"/></path><path opacity=".65" d="M15.2 7.2c.4 0 .8.4.8.8s-.4.8-.8.8h-2.4c-.4 0-.8-.4-.8-.8s.4-.8.8-.8h2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.64s"/></path><path opacity=".7" d="M11.8 11.1c-.4-.2-.5-.7-.3-1.1.2-.4.7-.5 1.1-.3l2.1 1.2c.4.2.5.7.3 1.1-.2.4-.7.5-1.1.3l-2.1-1.2z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.70s"/></path><path opacity=".8" d="M9.7 12.6c-.2-.4-.1-.9.3-1.1.4-.2.9-.1 1.1.3l1.2 2.1c.2.4.1.9-.3 1.1-.4.2-.9.1-1.1-.3l-1.2-2.1z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.78s"/></path><path d="M8.8 15.2c0 .4-.4.8-.8.8s-.8-.4-.8-.8v-2.4c0-.4.4-.8.8-.8s.8.4.8.8v2.4z"><animate attributeType="CSS" attributeName="opacity" from="1" to=".15" dur="0.96s" repeatCount="indefinite" begin="0.86s"/></path></g><path fill-rule="evenodd" clip-rule="evenodd" fill="#0095FF" d="M8 8.7c-1.5 0-2.6-.5-2.6-.5S5.9 10 8 10s2.6-1.9 2.6-1.9-1.1.6-2.6.6zM9.3 6c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.3-.7-.7.3-.7.7-.7zM6.7 6c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.3-.7-.7.3-.7.7-.7z"/></svg>
|
После Ширина: | Высота: | Размер: 3.1 KiB |
|
@ -51,6 +51,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||
var OPTIONAL_ROOMINFO_FIELDS = {
|
||||
urls: "roomContextUrls",
|
||||
description: "roomDescription",
|
||||
participants: "participants",
|
||||
roomInfoFailure: "roomInfoFailure",
|
||||
roomName: "roomName",
|
||||
roomState: "roomState"
|
||||
|
@ -296,6 +297,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||
}
|
||||
|
||||
this.dispatchAction(new sharedActions.SetupRoomInfo({
|
||||
participants: roomData.participants,
|
||||
roomToken: actionData.roomToken,
|
||||
roomContextUrls: roomData.decryptedContext.urls,
|
||||
roomDescription: roomData.decryptedContext.description,
|
||||
|
@ -418,6 +420,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||
}
|
||||
|
||||
this.setStoreState({
|
||||
participants: actionData.participants,
|
||||
roomContextUrls: actionData.roomContextUrls,
|
||||
roomDescription: actionData.roomDescription,
|
||||
roomName: actionData.roomName,
|
||||
|
@ -449,7 +452,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||
// Iterate over the optional fields that _may_ be present on the actionData
|
||||
// object.
|
||||
Object.keys(OPTIONAL_ROOMINFO_FIELDS).forEach(function(field) {
|
||||
if (actionData[field]) {
|
||||
if (actionData[field] !== undefined) {
|
||||
newState[OPTIONAL_ROOMINFO_FIELDS[field]] = actionData[field];
|
||||
}
|
||||
});
|
||||
|
@ -478,6 +481,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||
this.dispatchAction(new sharedActions.UpdateRoomInfo({
|
||||
urls: roomData.decryptedContext.urls,
|
||||
description: roomData.decryptedContext.description,
|
||||
participants: roomData.participants,
|
||||
roomName: roomData.decryptedContext.roomName,
|
||||
roomUrl: roomData.roomUrl
|
||||
}));
|
||||
|
@ -792,7 +796,16 @@ loop.store.ActiveRoomStore = (function() {
|
|||
* one participantleaves.
|
||||
*/
|
||||
remotePeerDisconnected: function() {
|
||||
// Update the participants to just the owner.
|
||||
var participants = this.getStoreState("participants");
|
||||
if (participants) {
|
||||
participants = participants.filter(function(participant) {
|
||||
return participant.owner;
|
||||
});
|
||||
}
|
||||
|
||||
this.setStoreState({
|
||||
participants: participants,
|
||||
roomState: ROOM_STATES.SESSION_CONNECTED,
|
||||
remoteSrcVideoObject: null
|
||||
});
|
||||
|
|
|
@ -94,6 +94,7 @@ browser.jar:
|
|||
content/browser/loop/shared/img/empty_contacts.svg (content/shared/img/empty_contacts.svg)
|
||||
content/browser/loop/shared/img/empty_conversations.svg (content/shared/img/empty_conversations.svg)
|
||||
content/browser/loop/shared/img/empty_search.svg (content/shared/img/empty_search.svg)
|
||||
content/browser/loop/shared/img/animated-spinner.svg (content/shared/img/animated-spinner.svg)
|
||||
content/browser/loop/shared/img/avatars.svg (content/shared/img/avatars.svg)
|
||||
content/browser/loop/shared/img/firefox-avatar.svg (content/shared/img/firefox-avatar.svg)
|
||||
|
||||
|
|
|
@ -850,6 +850,13 @@ describe("loop.panel", function() {
|
|||
sinon.assert.calledWithExactly(document.mozL10n.get,
|
||||
"no_conversations_message_heading");
|
||||
});
|
||||
|
||||
it("should display a loading animation when rooms are pending", function() {
|
||||
var view = createTestComponent();
|
||||
roomStore.setStoreState({pendingInitialRetrieval: true});
|
||||
|
||||
expect(view.getDOMNode().querySelectorAll(".room-list-loading").length).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("loop.panel.NewRoomView", function() {
|
||||
|
|
|
@ -69,7 +69,7 @@ describe("loop.store.RoomStore", function () {
|
|||
var defaultStoreState = {
|
||||
error: undefined,
|
||||
pendingCreation: false,
|
||||
pendingInitialRetrieval: false,
|
||||
pendingInitialRetrieval: true,
|
||||
rooms: [],
|
||||
activeRoom: {}
|
||||
};
|
||||
|
|
|
@ -470,8 +470,51 @@ describe("loop.roomViews", function () {
|
|||
|
||||
view = mountTestComponent();
|
||||
|
||||
expect(TestUtils.findRenderedComponentWithType(view,
|
||||
loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.not.eql(null);
|
||||
});
|
||||
|
||||
it("should render the DesktopRoomInvitationView if roomState is `JOINED` with just owner",
|
||||
function() {
|
||||
activeRoomStore.setStoreState({
|
||||
participants: [{owner: true}],
|
||||
roomState: ROOM_STATES.JOINED
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
expect(TestUtils.findRenderedComponentWithType(view,
|
||||
loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.not.eql(null);
|
||||
});
|
||||
|
||||
it("should render the DesktopRoomConversationView if roomState is `JOINED` with remote participant",
|
||||
function() {
|
||||
activeRoomStore.setStoreState({
|
||||
participants: [{}],
|
||||
roomState: ROOM_STATES.JOINED
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(view,
|
||||
loop.roomViews.DesktopRoomInvitationView);
|
||||
loop.roomViews.DesktopRoomConversationView);
|
||||
expect(TestUtils.findRenderedComponentWithType(view,
|
||||
loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.eql(null);
|
||||
});
|
||||
|
||||
it("should render the DesktopRoomConversationView if roomState is `JOINED` with participants",
|
||||
function() {
|
||||
activeRoomStore.setStoreState({
|
||||
participants: [{owner: true}, {}],
|
||||
roomState: ROOM_STATES.JOINED
|
||||
});
|
||||
|
||||
view = mountTestComponent();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(view,
|
||||
loop.roomViews.DesktopRoomConversationView);
|
||||
expect(TestUtils.findRenderedComponentWithType(view,
|
||||
loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.eql(null);
|
||||
});
|
||||
|
||||
it("should render the DesktopRoomConversationView if roomState is `HAS_PARTICIPANTS`",
|
||||
|
@ -482,6 +525,8 @@ describe("loop.roomViews", function () {
|
|||
|
||||
TestUtils.findRenderedComponentWithType(view,
|
||||
loop.roomViews.DesktopRoomConversationView);
|
||||
expect(TestUtils.findRenderedComponentWithType(view,
|
||||
loop.roomViews.DesktopRoomInvitationView).getDOMNode()).to.eql(null);
|
||||
});
|
||||
|
||||
it("should call onCallTerminated when the call ended", function() {
|
||||
|
|
|
@ -310,6 +310,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
decryptedContext: {
|
||||
roomName: "Monkeys"
|
||||
},
|
||||
participants: [],
|
||||
roomUrl: "http://invalid"
|
||||
};
|
||||
|
||||
|
@ -350,6 +351,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
new sharedActions.SetupRoomInfo({
|
||||
roomContextUrls: undefined,
|
||||
roomDescription: undefined,
|
||||
participants: [],
|
||||
roomToken: fakeToken,
|
||||
roomName: fakeRoomData.decryptedContext.roomName,
|
||||
roomUrl: fakeRoomData.roomUrl,
|
||||
|
@ -1277,6 +1279,30 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
|
||||
expect(store.getStoreState().remoteSrcVideoObject).eql(null);
|
||||
});
|
||||
|
||||
it("should remove non-owner participants", function() {
|
||||
store.setStoreState({
|
||||
participants: [{owner: true}, {}]
|
||||
});
|
||||
|
||||
store.remotePeerDisconnected();
|
||||
|
||||
var participants = store.getStoreState().participants;
|
||||
expect(participants).to.have.length.of(1);
|
||||
expect(participants[0].owner).eql(true);
|
||||
});
|
||||
|
||||
it("should keep the owner participant", function() {
|
||||
store.setStoreState({
|
||||
participants: [{owner: true}]
|
||||
});
|
||||
|
||||
store.remotePeerDisconnected();
|
||||
|
||||
var participants = store.getStoreState().participants;
|
||||
expect(participants).to.have.length.of(1);
|
||||
expect(participants[0].owner).eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#connectionStatus", function() {
|
||||
|
@ -1518,6 +1544,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo({
|
||||
description: "fakeDescription",
|
||||
participants: undefined,
|
||||
roomName: fakeRoomData.decryptedContext.roomName,
|
||||
roomUrl: fakeRoomData.roomUrl,
|
||||
urls: {
|
||||
|
|
|
@ -186,6 +186,17 @@ let gSyncPane = {
|
|||
}
|
||||
},
|
||||
|
||||
_closeSyncStatusMessageBox: function() {
|
||||
document.getElementById("syncStatusMessage").removeAttribute("message-type");
|
||||
document.getElementById("syncStatusMessageTitle").textContent = "";
|
||||
document.getElementById("syncStatusMessageDescription").textContent = "";
|
||||
let learnMoreLink = document.getElementById("learnMoreLink");
|
||||
if (learnMoreLink) {
|
||||
learnMoreLink.parentNode.removeChild(learnMoreLink);
|
||||
}
|
||||
document.getElementById("sync-migration-buttons-deck").hidden = true;
|
||||
},
|
||||
|
||||
_setupEventListeners: function() {
|
||||
function setEventListener(aId, aEventType, aCallback)
|
||||
{
|
||||
|
@ -193,6 +204,9 @@ let gSyncPane = {
|
|||
.addEventListener(aEventType, aCallback.bind(gSyncPane));
|
||||
}
|
||||
|
||||
setEventListener("syncStatusMessageClose", "command", function () {
|
||||
gSyncPane._closeSyncStatusMessageBox();
|
||||
});
|
||||
setEventListener("noAccountSetup", "click", function (aEvent) {
|
||||
aEvent.stopPropagation();
|
||||
gSyncPane.openSetup(null);
|
||||
|
@ -431,16 +445,17 @@ let gSyncPane = {
|
|||
},
|
||||
|
||||
updateMigrationState: function(subject, state) {
|
||||
this._closeSyncStatusMessageBox();
|
||||
let selIndex;
|
||||
let sb = this._accountsStringBundle;
|
||||
switch (state) {
|
||||
case fxaMigrator.STATE_USER_FXA: {
|
||||
let sb = this._accountsStringBundle;
|
||||
// There are 2 cases here - no email address means it is an offer on
|
||||
// the first device (so the user is prompted to create an account).
|
||||
// If there is an email address it is the "join the party" flow, so the
|
||||
// user is prompted to sign in with the address they previously used.
|
||||
let email = subject ? subject.QueryInterface(Components.interfaces.nsISupportsString).data : null;
|
||||
let elt = document.getElementById("sync-migrate-upgrade-description");
|
||||
let elt = document.getElementById("syncStatusMessageDescription");
|
||||
elt.textContent = email ?
|
||||
sb.formatStringFromName("signInAfterUpgradeOnOtherDevice.description",
|
||||
[email], 1) :
|
||||
|
@ -449,11 +464,12 @@ let gSyncPane = {
|
|||
// The "Learn more" link.
|
||||
if (!email) {
|
||||
let learnMoreLink = document.createElement("label");
|
||||
learnMoreLink.id = "learnMoreLink";
|
||||
learnMoreLink.className = "text-link";
|
||||
let { text, href } = fxaMigrator.learnMoreLink;
|
||||
learnMoreLink.setAttribute("value", text);
|
||||
learnMoreLink.href = href;
|
||||
elt.appendChild(learnMoreLink);
|
||||
elt.parentNode.insertBefore(learnMoreLink, elt.nextSibling);
|
||||
}
|
||||
|
||||
// The "upgrade" button.
|
||||
|
@ -481,7 +497,7 @@ let gSyncPane = {
|
|||
let sb = this._accountsStringBundle;
|
||||
let email = subject.QueryInterface(Components.interfaces.nsISupportsString).data;
|
||||
let label = sb.formatStringFromName("needVerifiedUserLong", [email], 1);
|
||||
let elt = document.getElementById("sync-migrate-verify-label");
|
||||
let elt = document.getElementById("syncStatusMessageDescription");
|
||||
elt.setAttribute("value", label);
|
||||
// The "resend" button.
|
||||
let button = document.getElementById("sync-migrate-resend");
|
||||
|
@ -501,8 +517,8 @@ let gSyncPane = {
|
|||
document.getElementById("sync-migration").hidden = true;
|
||||
return;
|
||||
}
|
||||
document.getElementById("sync-migration").hidden = false;
|
||||
document.getElementById("sync-migration-deck").selectedIndex = selIndex;
|
||||
document.getElementById("sync-migration-buttons-deck").selectedIndex = selIndex;
|
||||
document.getElementById("syncStatusMessage").setAttribute("message-type", "migration");
|
||||
},
|
||||
|
||||
// Called whenever one of the sync engine preferences is changed.
|
||||
|
@ -678,23 +694,39 @@ let gSyncPane = {
|
|||
},
|
||||
|
||||
verifyFirefoxAccount: function() {
|
||||
fxAccounts.resendVerificationEmail().then(() => {
|
||||
fxAccounts.getSignedInUser().then(data => {
|
||||
let sb = this._accountsStringBundle;
|
||||
let title = sb.GetStringFromName("verificationSentTitle");
|
||||
let heading = sb.formatStringFromName("verificationSentHeading",
|
||||
[data.email], 1);
|
||||
let description = sb.GetStringFromName("verificationSentDescription");
|
||||
this._closeSyncStatusMessageBox();
|
||||
let changesyncStatusMessage = (data) => {
|
||||
let isError = !data;
|
||||
let syncStatusMessage = document.getElementById("syncStatusMessage");
|
||||
let syncStatusMessageTitle = document.getElementById("syncStatusMessageTitle");
|
||||
let syncStatusMessageDescription = document.getElementById("syncStatusMessageDescription");
|
||||
let maybeNot = isError ? "Not" : "";
|
||||
let sb = this._accountsStringBundle;
|
||||
let title = sb.GetStringFromName("verification" + maybeNot + "SentTitle");
|
||||
let email = !isError && data ? data.email : "";
|
||||
let description = sb.formatStringFromName("verification" + maybeNot + "SentFull", [email], 1)
|
||||
|
||||
let factory = Cc["@mozilla.org/prompter;1"]
|
||||
.getService(Ci.nsIPromptFactory);
|
||||
let prompt = factory.getPrompt(window, Ci.nsIPrompt);
|
||||
let bag = prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
|
||||
bag.setPropertyAsBool("allowTabModal", true);
|
||||
syncStatusMessageTitle.textContent = title;
|
||||
syncStatusMessageDescription.textContent = description;
|
||||
let messageType = isError ? "verify-error" : "verify-success";
|
||||
syncStatusMessage.setAttribute("message-type", messageType);
|
||||
}
|
||||
|
||||
prompt.alert(title, heading + "\n\n" + description);
|
||||
});
|
||||
});
|
||||
let onError = () => {
|
||||
changesyncStatusMessage();
|
||||
};
|
||||
|
||||
let onSuccess = data => {
|
||||
if (data) {
|
||||
changesyncStatusMessage(data);
|
||||
} else {
|
||||
onError();
|
||||
}
|
||||
};
|
||||
|
||||
fxAccounts.resendVerificationEmail()
|
||||
.then(fxAccounts.getSignedInUser, onError)
|
||||
.then(onSuccess, onError);
|
||||
},
|
||||
|
||||
openOldSyncSupportPage: function() {
|
||||
|
|
|
@ -38,31 +38,29 @@
|
|||
<label class="header-name">&paneSync.title;</label>
|
||||
</hbox>
|
||||
|
||||
<hbox id="sync-migration-container"
|
||||
data-category="paneSync"
|
||||
hidden="true">
|
||||
|
||||
<vbox id="sync-migration" flex="1" hidden="true">
|
||||
|
||||
<deck id="sync-migration-deck">
|
||||
<!-- When we are in the "need FxA user" state -->
|
||||
<hbox align="center">
|
||||
<description id="sync-migrate-upgrade-description" flex="1"/>
|
||||
<spacer flex="1"/>
|
||||
<button id="sync-migrate-unlink"/>
|
||||
<button id="sync-migrate-upgrade"/>
|
||||
</hbox>
|
||||
|
||||
<!-- When we are in the "need the user to be verified" state -->
|
||||
<hbox align="center">
|
||||
<label id="sync-migrate-verify-label"/>
|
||||
<spacer flex="1"/>
|
||||
<button id="sync-migrate-forget"/>
|
||||
<button id="sync-migrate-resend"/>
|
||||
</hbox>
|
||||
</deck>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<vbox id="syncStatusMessage-container" data-category="paneSync" hidden="true">
|
||||
<hbox id="syncStatusMessage">
|
||||
<vbox id="syncStatusMessageWrapper">
|
||||
<label id="syncStatusMessageTitle"></label>
|
||||
<description id="syncStatusMessageDescription"></description>
|
||||
<deck id="sync-migration-buttons-deck">
|
||||
<!-- When we are in the "need FxA user" state -->
|
||||
<hbox>
|
||||
<button id="sync-migrate-unlink"/>
|
||||
<button id="sync-migrate-upgrade"/>
|
||||
</hbox>
|
||||
<!-- When we are in the "need the user to be verified" state -->
|
||||
<hbox>
|
||||
<button id="sync-migrate-forget"/>
|
||||
<button id="sync-migrate-resend"/>
|
||||
</hbox>
|
||||
</deck>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<button id="syncStatusMessageClose" class="close-icon"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
|
||||
<!-- These panels are for the "legacy" sync provider -->
|
||||
|
|
|
@ -19,10 +19,11 @@ function checkElements(expectedPane) {
|
|||
continue;
|
||||
}
|
||||
let attributeValue = element.getAttribute("data-category");
|
||||
let suffix = " (id=" + element.id + ")";
|
||||
if (attributeValue == "pane" + expectedPane) {
|
||||
is_element_visible(element, expectedPane + " elements should be visible");
|
||||
is_element_visible(element, expectedPane + " elements should be visible" + suffix);
|
||||
} else {
|
||||
is_element_hidden(element, "Elements not in " + expectedPane + " should be hidden");
|
||||
is_element_hidden(element, "Elements not in " + expectedPane + " should be hidden" + suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ loader.lazyRequireGetter(this, "AnimationsFront",
|
|||
|
||||
const STRINGS_URI = "chrome://browser/locale/devtools/animationinspector.properties";
|
||||
const L10N = new ViewHelpers.L10N(STRINGS_URI);
|
||||
const V3_UI_PREF = "devtools.inspector.animationInspectorV3";
|
||||
|
||||
// Global toolbox/inspector, set when startup is called.
|
||||
let gToolbox, gInspector;
|
||||
|
@ -76,19 +77,20 @@ function destroy() {
|
|||
* @return {Object} An object with boolean properties.
|
||||
*/
|
||||
let getServerTraits = Task.async(function*(target) {
|
||||
let config = [{
|
||||
name: "hasToggleAll", actor: "animations", method: "toggleAll"
|
||||
}, {
|
||||
name: "hasSetCurrentTime", actor: "animationplayer", method: "setCurrentTime"
|
||||
}, {
|
||||
name: "hasMutationEvents", actor: "animations", method: "stopAnimationPlayerUpdates"
|
||||
}, {
|
||||
name: "hasSetPlaybackRate", actor: "animationplayer", method: "setPlaybackRate"
|
||||
}, {
|
||||
name: "hasTargetNode", actor: "domwalker", method: "getNodeFromActor"
|
||||
}, {
|
||||
name: "hasSetCurrentTimes", actor: "animations", method: "setCurrentTimes"
|
||||
}];
|
||||
let config = [
|
||||
{ name: "hasToggleAll", actor: "animations",
|
||||
method: "toggleAll" },
|
||||
{ name: "hasSetCurrentTime", actor: "animationplayer",
|
||||
method: "setCurrentTime" },
|
||||
{ name: "hasMutationEvents", actor: "animations",
|
||||
method: "stopAnimationPlayerUpdates" },
|
||||
{ name: "hasSetPlaybackRate", actor: "animationplayer",
|
||||
method: "setPlaybackRate" },
|
||||
{ name: "hasTargetNode", actor: "domwalker",
|
||||
method: "getNodeFromActor" },
|
||||
{ name: "hasSetCurrentTimes", actor: "animations",
|
||||
method: "setCurrentTimes" }
|
||||
];
|
||||
|
||||
let traits = {};
|
||||
for (let {name, actor, method} of config) {
|
||||
|
@ -96,7 +98,7 @@ let getServerTraits = Task.async(function*(target) {
|
|||
}
|
||||
|
||||
// Special pref-based UI trait.
|
||||
traits.isNewUI = Services.prefs.getBoolPref("devtools.inspector.animationInspectorV3");
|
||||
traits.isNewUI = Services.prefs.getBoolPref(V3_UI_PREF);
|
||||
|
||||
return traits;
|
||||
});
|
||||
|
@ -114,7 +116,8 @@ let getServerTraits = Task.async(function*(target) {
|
|||
*
|
||||
* Usage example:
|
||||
*
|
||||
* AnimationsController.on(AnimationsController.PLAYERS_UPDATED_EVENT, onPlayers);
|
||||
* AnimationsController.on(AnimationsController.PLAYERS_UPDATED_EVENT,
|
||||
* onPlayers);
|
||||
* function onPlayers() {
|
||||
* for (let player of AnimationsController.animationPlayers) {
|
||||
* // do something with player
|
||||
|
@ -126,7 +129,8 @@ let AnimationsController = {
|
|||
|
||||
initialize: Task.async(function*() {
|
||||
if (this.initialized) {
|
||||
return this.initialized.promise;
|
||||
yield this.initialized.promise;
|
||||
return;
|
||||
}
|
||||
this.initialized = promise.defer();
|
||||
|
||||
|
@ -157,7 +161,8 @@ let AnimationsController = {
|
|||
}
|
||||
|
||||
if (this.destroyed) {
|
||||
return this.destroyed.promise;
|
||||
yield this.destroyed.promise;
|
||||
return;
|
||||
}
|
||||
this.destroyed = promise.defer();
|
||||
|
||||
|
@ -272,7 +277,8 @@ let AnimationsController = {
|
|||
refreshAnimationPlayers: Task.async(function*(nodeFront) {
|
||||
yield this.destroyAnimationPlayers();
|
||||
|
||||
this.animationPlayers = yield this.animationsFront.getAnimationPlayersForNode(nodeFront);
|
||||
this.animationPlayers = yield this.animationsFront
|
||||
.getAnimationPlayersForNode(nodeFront);
|
||||
this.startAllAutoRefresh();
|
||||
|
||||
// Start listening for animation mutations only after the first method call
|
||||
|
@ -308,6 +314,25 @@ let AnimationsController = {
|
|||
this.emit(this.PLAYERS_UPDATED_EVENT, this.animationPlayers);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get the latest known current time of document.timeline.
|
||||
* This value is sent along with all AnimationPlayerActors' states, but it
|
||||
* isn't updated after that, so this function loops over all know animations
|
||||
* to find the highest value.
|
||||
* @return {Number|Boolean} False is returned if this server version doesn't
|
||||
* provide document's current time.
|
||||
*/
|
||||
get documentCurrentTime() {
|
||||
let time = 0;
|
||||
for (let {state} of this.animationPlayers) {
|
||||
if (!state.documentCurrentTime) {
|
||||
return false;
|
||||
}
|
||||
time = Math.max(time, state.documentCurrentTime);
|
||||
}
|
||||
return time;
|
||||
},
|
||||
|
||||
startAllAutoRefresh: function() {
|
||||
if (this.traits.isNewUI) {
|
||||
return;
|
||||
|
|
|
@ -30,7 +30,8 @@ let AnimationsPanel = {
|
|||
return;
|
||||
}
|
||||
if (this.initialized) {
|
||||
return this.initialized.promise;
|
||||
yield this.initialized.promise;
|
||||
return;
|
||||
}
|
||||
this.initialized = promise.defer();
|
||||
|
||||
|
@ -74,7 +75,8 @@ let AnimationsPanel = {
|
|||
}
|
||||
|
||||
if (this.destroyed) {
|
||||
return this.destroyed.promise;
|
||||
yield this.destroyed.promise;
|
||||
return;
|
||||
}
|
||||
this.destroyed = promise.defer();
|
||||
|
||||
|
@ -157,7 +159,8 @@ let AnimationsPanel = {
|
|||
currentWidgetStateChange.push(btnClass.contains("paused")
|
||||
? widget.play() : widget.pause());
|
||||
}
|
||||
yield promise.all(currentWidgetStateChange).catch(e => console.error(e));
|
||||
yield promise.all(currentWidgetStateChange)
|
||||
.catch(error => console.error(error));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,7 +173,8 @@ let AnimationsPanel = {
|
|||
},
|
||||
|
||||
onTimelineTimeChanged: function(e, time) {
|
||||
AnimationsController.setCurrentTimeAll(time, true).catch(e => console.error(e));
|
||||
AnimationsController.setCurrentTimeAll(time, true)
|
||||
.catch(error => console.error(error));
|
||||
},
|
||||
|
||||
refreshAnimations: Task.async(function*() {
|
||||
|
@ -183,7 +187,8 @@ let AnimationsPanel = {
|
|||
// Re-render the timeline component.
|
||||
if (this.animationsTimelineComponent) {
|
||||
this.animationsTimelineComponent.render(
|
||||
AnimationsController.animationPlayers);
|
||||
AnimationsController.animationPlayers,
|
||||
AnimationsController.documentCurrentTime);
|
||||
}
|
||||
|
||||
// If there are no players to show, show the error message instead and
|
||||
|
|
|
@ -567,7 +567,12 @@ let TimeScale = {
|
|||
addAnimation: function(state) {
|
||||
let {startTime, delay, duration, iterationCount, playbackRate} = state;
|
||||
|
||||
this.minStartTime = Math.min(this.minStartTime, startTime);
|
||||
// Negative-delayed animations have their startTimes set such that we would
|
||||
// be displaying the delay outside the time window if we didn't take it into
|
||||
// account here.
|
||||
let relevantDelay = delay < 0 ? delay / playbackRate : 0;
|
||||
|
||||
this.minStartTime = Math.min(this.minStartTime, startTime + relevantDelay);
|
||||
let length = (delay / playbackRate) +
|
||||
((duration / playbackRate) *
|
||||
(!iterationCount ? 1 : iterationCount));
|
||||
|
@ -791,7 +796,7 @@ AnimationsTimeline.prototype = {
|
|||
this.emit("current-time-changed", time);
|
||||
},
|
||||
|
||||
render: function(animations) {
|
||||
render: function(animations, documentCurrentTime) {
|
||||
this.unrender();
|
||||
|
||||
this.animations = animations;
|
||||
|
@ -849,12 +854,11 @@ AnimationsTimeline.prototype = {
|
|||
// doesn't provide it, hide the scrubber entirely).
|
||||
// Note that because the currentTime was sent via the protocol, some time
|
||||
// may have gone by since then, and so the scrubber might be a bit late.
|
||||
let time = this.animations[0].state.documentCurrentTime;
|
||||
if (!time) {
|
||||
if (!documentCurrentTime) {
|
||||
this.scrubberEl.style.display = "none";
|
||||
} else {
|
||||
this.scrubberEl.style.display = "block";
|
||||
this.startAnimatingScrubber(time);
|
||||
this.startAnimatingScrubber(documentCurrentTime);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -953,23 +957,31 @@ AnimationsTimeline.prototype = {
|
|||
});
|
||||
|
||||
// The animation name is displayed over the iterations.
|
||||
// Note that in case of negative delay, we push the name towards the right
|
||||
// so the delay can be shown.
|
||||
createNode({
|
||||
parent: iterations,
|
||||
attributes: {
|
||||
"class": "name",
|
||||
"title": this.getAnimationTooltipText(state)
|
||||
"title": this.getAnimationTooltipText(state),
|
||||
"style": delay < 0
|
||||
? "margin-left:" +
|
||||
TimeScale.durationToDistance(Math.abs(delay), width) + "px"
|
||||
: ""
|
||||
},
|
||||
textContent: state.name
|
||||
});
|
||||
|
||||
// Delay.
|
||||
if (delay) {
|
||||
let w = TimeScale.durationToDistance(delay / rate, width);
|
||||
// Negative delays need to start at 0.
|
||||
let x = TimeScale.durationToDistance((delay < 0 ? 0 : delay) / rate, width);
|
||||
let w = TimeScale.durationToDistance(Math.abs(delay) / rate, width);
|
||||
createNode({
|
||||
parent: iterations,
|
||||
attributes: {
|
||||
"class": "delay",
|
||||
"style": `left:-${w}px;
|
||||
"style": `left:-${x}px;
|
||||
width:${w}px;`
|
||||
}
|
||||
});
|
||||
|
|
|
@ -22,18 +22,23 @@ add_task(function*() {
|
|||
let timeBlocks = timelineEl.querySelectorAll(".time-block");
|
||||
is(timeBlocks.length, 2, "2 animations are displayed");
|
||||
|
||||
info("The first animation has its rate to 1, let's measure it");
|
||||
info("The first animation has its rate set to 1, let's measure it");
|
||||
|
||||
let el = timeBlocks[0];
|
||||
let duration = el.querySelector(".iterations").getBoundingClientRect().width;
|
||||
let delay = el.querySelector(".delay").getBoundingClientRect().width;
|
||||
let duration = parseInt(el.querySelector(".iterations").style.width, 10);
|
||||
let delay = parseInt(el.querySelector(".delay").style.width, 10);
|
||||
|
||||
info("The second animation has its rate set to 2, so should be shorter");
|
||||
|
||||
let el2 = timeBlocks[1];
|
||||
let duration2 = el2.querySelector(".iterations").getBoundingClientRect().width;
|
||||
let delay2 = el2.querySelector(".delay").getBoundingClientRect().width;
|
||||
let duration2 = parseInt(el2.querySelector(".iterations").style.width, 10);
|
||||
let delay2 = parseInt(el2.querySelector(".delay").style.width, 10);
|
||||
|
||||
is(duration, 2 * duration2, "The duration width is correct");
|
||||
is(delay, 2 * delay2, "The delay width is correct");
|
||||
// The width are calculated by the animation-inspector dynamically depending
|
||||
// on the size of the panel, and therefore depends on the test machine/OS.
|
||||
// Let's not try to be too precise here and compare numbers.
|
||||
let durationDelta = (2 * duration2) - duration;
|
||||
ok(durationDelta <= 1, "The duration width is correct");
|
||||
let delayDelta = (2 * delay2) - delay;
|
||||
ok(delayDelta <= 1, "The delay width is correct");
|
||||
});
|
||||
|
|
|
@ -496,9 +496,6 @@
|
|||
#ifdef XP_MACOSX
|
||||
@RESPATH@/browser/components/SafariProfileMigrator.js
|
||||
#endif
|
||||
#ifdef MOZ_ENABLE_DBUS
|
||||
@RESPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
|
||||
#endif
|
||||
#ifdef MOZ_ENABLE_GNOME_COMPONENT
|
||||
@RESPATH@/components/@DLL_PREFIX@mozgnome@DLL_SUFFIX@
|
||||
#endif
|
||||
|
|
|
@ -46,7 +46,10 @@ verificationSentTitle = Verification Sent
|
|||
# LOCALIZATION NOTE (verificationSentHeading) - %S = Email address of user's Firefox Account
|
||||
verificationSentHeading = A verification link has been sent to %S
|
||||
verificationSentDescription = Please check your email and click the link to begin syncing.
|
||||
# LOCALIZATION NOTE (verificationSentFull) - %S = Email address of user's Firefox Account
|
||||
verificationSentFull = A verification link has been sent to %S. Please check your email and click the link to begin syncing.
|
||||
|
||||
verificationNotSentTitle = Unable to Send Verification
|
||||
verificationNotSentHeading = We are unable to send a verification mail at this time
|
||||
verificationNotSentDescription = Please try again later.
|
||||
verificationNotSentFull = We are unable to send a verification mail at this time, please try again later.
|
||||
|
|
|
@ -1614,7 +1614,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
|
|||
}
|
||||
|
||||
#urlbar {
|
||||
-moz-padding-end: 4px;
|
||||
border-radius: @toolbarbuttonCornerRadius@;
|
||||
}
|
||||
|
||||
|
@ -1906,6 +1905,7 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
|
|||
var(--urlbar-separator-color) 15%,
|
||||
var(--urlbar-separator-color) 85%,
|
||||
transparent 85%);
|
||||
border-image-slice: 1;
|
||||
}
|
||||
|
||||
#urlbar-go-button {
|
||||
|
|
|
@ -238,7 +238,7 @@ body {
|
|||
border: 1px solid var(--timelime-border-color);
|
||||
|
||||
/* The background color is set independently */
|
||||
background: var(--timeline-background-color);
|
||||
background-color: var(--timeline-background-color);
|
||||
}
|
||||
|
||||
.animation-timeline .animation .cssanimation {
|
||||
|
@ -287,25 +287,16 @@ body {
|
|||
.animation-timeline .animation .delay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
/* Make sure the delay covers up the animation border */
|
||||
transform: translate(-1px, -1px);
|
||||
height: 100%;
|
||||
background-image: linear-gradient(to bottom,
|
||||
transparent,
|
||||
transparent 9px,
|
||||
var(--timelime-border-color) 9px,
|
||||
var(--timelime-border-color) 11px,
|
||||
transparent 11px,
|
||||
transparent);
|
||||
}
|
||||
|
||||
.animation-timeline .animation .delay::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: 0;
|
||||
width: 2px;
|
||||
height: 8px;
|
||||
top: 50%;
|
||||
margin-top: -4px;
|
||||
background: var(--timelime-border-color);
|
||||
background-image: repeating-linear-gradient(45deg,
|
||||
transparent,
|
||||
transparent 1px,
|
||||
var(--theme-selection-color) 1px,
|
||||
var(--theme-selection-color) 4px);
|
||||
background-color: var(--timelime-border-color);
|
||||
border: 1px solid var(--timelime-border-color);
|
||||
}
|
||||
|
||||
/* Animation target node gutter, contains a preview of the dom node */
|
||||
|
|
|
@ -415,6 +415,101 @@ description > html|a {
|
|||
-moz-box-align: start;
|
||||
}
|
||||
|
||||
#syncStatusMessage {
|
||||
visibility: collapse;
|
||||
opacity: 0;
|
||||
transition: opacity 1s linear;
|
||||
padding: 14px 8px 14px 14px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#syncStatusMessage[message-type] {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#syncStatusMessage[message-type="verify-success"] {
|
||||
background-color: #74BF43;
|
||||
}
|
||||
|
||||
#syncStatusMessage[message-type="verify-error"] {
|
||||
background-color: #D74345;
|
||||
}
|
||||
|
||||
#syncStatusMessage[message-type="migration"] {
|
||||
background-color: #FF9500;
|
||||
}
|
||||
|
||||
#sync-migration-buttons-deck {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
#learnMoreLink {
|
||||
margin: 0;
|
||||
color: #FBFBFB;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#syncStatusMessage[message-type="migration"] #sync-migration-buttons-deck {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#sync-migration-buttons-deck {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#sync-migration-buttons-deck button {
|
||||
margin: 0 10px 0 0;
|
||||
border: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#sync-migrate-upgrade,
|
||||
#sync-migrate-resend {
|
||||
background-color: #0095DD;
|
||||
color: #FBFBFB;
|
||||
}
|
||||
|
||||
#sync-migrate-upgrade:hover,
|
||||
#sync-migrate-resend:hover {
|
||||
background-color: #008ACB;
|
||||
}
|
||||
|
||||
#sync-migrate-upgrade:hover:active,
|
||||
#sync-migrate-resend:hover:active {
|
||||
background-color: #006B9D;
|
||||
}
|
||||
|
||||
#syncStatusMessageWrapper {
|
||||
-moz-box-flex: 1;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#syncStatusMessageTitle, #syncStatusMessageDescription {
|
||||
color: #FBFBFB;
|
||||
}
|
||||
|
||||
#syncStatusMessage[message-type="migration"] #syncStatusMessageTitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#syncStatusMessageTitle {
|
||||
font-weight: bold !important;
|
||||
font-size: 16px;
|
||||
line-height: 157%;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
#syncStatusMessageDescription {
|
||||
font-size: 14px;
|
||||
line-height: 158%;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#syncStatusMessageClose {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#fxaSyncEngines > vbox:first-child {
|
||||
margin-right: 80px;
|
||||
}
|
||||
|
|
|
@ -1230,10 +1230,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
|
|||
border: 1px solid ThreeDShadow;
|
||||
}
|
||||
|
||||
#urlbar {
|
||||
-moz-padding-end: 2px;
|
||||
}
|
||||
|
||||
/* overlap the urlbar's border */
|
||||
#PopupAutoCompleteRichResult {
|
||||
margin-top: -1px;
|
||||
|
@ -1267,7 +1263,6 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
|
|||
.searchbar-textbox:not(:-moz-lwtheme) {
|
||||
border-color: hsl(0,0%,90%);
|
||||
padding: 1px;
|
||||
-moz-padding-end: 3px;
|
||||
transition-property: border-color, box-shadow;
|
||||
transition-duration: .1s;
|
||||
}
|
||||
|
|
|
@ -2405,7 +2405,7 @@ Element::SetAttrAndNotify(int32_t aNamespaceID,
|
|||
}
|
||||
|
||||
if (aFireMutation) {
|
||||
InternalMutationEvent mutation(true, NS_MUTATION_ATTRMODIFIED);
|
||||
InternalMutationEvent mutation(true, eLegacyAttrModified);
|
||||
|
||||
nsAutoString ns;
|
||||
nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNamespaceID, ns);
|
||||
|
@ -2647,7 +2647,7 @@ Element::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
|||
}
|
||||
|
||||
if (hasMutationListeners) {
|
||||
InternalMutationEvent mutation(true, NS_MUTATION_ATTRMODIFIED);
|
||||
InternalMutationEvent mutation(true, eLegacyAttrModified);
|
||||
|
||||
mutation.mRelatedNode = attrNode;
|
||||
mutation.mAttrName = aName;
|
||||
|
|
|
@ -1217,7 +1217,7 @@ FragmentOrElement::FireNodeInserted(nsIDocument* aDoc,
|
|||
|
||||
if (nsContentUtils::HasMutationListeners(childContent,
|
||||
NS_EVENT_BITS_MUTATION_NODEINSERTED, aParent)) {
|
||||
InternalMutationEvent mutation(true, NS_MUTATION_NODEINSERTED);
|
||||
InternalMutationEvent mutation(true, eLegacyNodeInserted);
|
||||
mutation.mRelatedNode = do_QueryInterface(aParent);
|
||||
|
||||
mozAutoSubtreeModified subtree(aDoc, aParent);
|
||||
|
|
|
@ -4085,7 +4085,7 @@ nsContentUtils::MaybeFireNodeRemoved(nsINode* aChild, nsINode* aParent,
|
|||
|
||||
if (HasMutationListeners(aChild,
|
||||
NS_EVENT_BITS_MUTATION_NODEREMOVED, aParent)) {
|
||||
InternalMutationEvent mutation(true, NS_MUTATION_NODEREMOVED);
|
||||
InternalMutationEvent mutation(true, eLegacyNodeRemoved);
|
||||
mutation.mRelatedNode = do_QueryInterface(aParent);
|
||||
|
||||
mozAutoSubtreeModified subtree(aOwnerDoc, aParent);
|
||||
|
|
|
@ -9436,7 +9436,7 @@ nsDocument::MutationEventDispatched(nsINode* aTarget)
|
|||
|
||||
int32_t realTargetCount = realTargets.Count();
|
||||
for (int32_t k = 0; k < realTargetCount; ++k) {
|
||||
InternalMutationEvent mutation(true, NS_MUTATION_SUBTREEMODIFIED);
|
||||
InternalMutationEvent mutation(true, eLegacySubtreeModified);
|
||||
(new AsyncEventDispatcher(realTargets[k], mutation))->
|
||||
RunDOMEventWhenSafe();
|
||||
}
|
||||
|
|
|
@ -387,7 +387,7 @@ nsGenericDOMDataNode::SetTextInternal(uint32_t aOffset, uint32_t aCount,
|
|||
nsNodeUtils::CharacterDataChanged(this, &info);
|
||||
|
||||
if (haveMutationListeners) {
|
||||
InternalMutationEvent mutation(true, NS_MUTATION_CHARACTERDATAMODIFIED);
|
||||
InternalMutationEvent mutation(true, eLegacyCharacterDataModified);
|
||||
|
||||
mutation.mPrevAttrValue = oldValue;
|
||||
if (aLength > 0) {
|
||||
|
|
|
@ -1610,7 +1610,7 @@ nsINode::doInsertChildAt(nsIContent* aKid, uint32_t aIndex,
|
|||
|
||||
if (nsContentUtils::HasMutationListeners(aKid,
|
||||
NS_EVENT_BITS_MUTATION_NODEINSERTED, this)) {
|
||||
InternalMutationEvent mutation(true, NS_MUTATION_NODEINSERTED);
|
||||
InternalMutationEvent mutation(true, eLegacyNodeInserted);
|
||||
mutation.mRelatedNode = do_QueryInterface(this);
|
||||
|
||||
mozAutoSubtreeModified subtree(OwnerDoc(), this);
|
||||
|
|
|
@ -792,6 +792,34 @@ ContentEventHandler::GetLineBreakType(bool aUseNativeLineBreak)
|
|||
LINE_BREAK_TYPE_NATIVE : LINE_BREAK_TYPE_XP;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ContentEventHandler::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
|
||||
{
|
||||
switch (aEvent->mMessage) {
|
||||
case NS_QUERY_SELECTED_TEXT:
|
||||
return OnQuerySelectedText(aEvent);
|
||||
case NS_QUERY_TEXT_CONTENT:
|
||||
return OnQueryTextContent(aEvent);
|
||||
case NS_QUERY_CARET_RECT:
|
||||
return OnQueryCaretRect(aEvent);
|
||||
case NS_QUERY_TEXT_RECT:
|
||||
return OnQueryTextRect(aEvent);
|
||||
case NS_QUERY_EDITOR_RECT:
|
||||
return OnQueryEditorRect(aEvent);
|
||||
case NS_QUERY_CONTENT_STATE:
|
||||
return OnQueryContentState(aEvent);
|
||||
case NS_QUERY_SELECTION_AS_TRANSFERABLE:
|
||||
return OnQuerySelectionAsTransferable(aEvent);
|
||||
case NS_QUERY_CHARACTER_AT_POINT:
|
||||
return OnQueryCharacterAtPoint(aEvent);
|
||||
case NS_QUERY_DOM_WIDGET_HITTEST:
|
||||
return OnQueryDOMWidgetHittest(aEvent);
|
||||
default:
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Similar to nsFrameSelection::GetFrameForNodeOffset,
|
||||
// but this is more flexible for OnQueryTextRect to use
|
||||
static nsresult GetFrameForTextRect(nsINode* aNode,
|
||||
|
|
|
@ -39,6 +39,9 @@ public:
|
|||
|
||||
explicit ContentEventHandler(nsPresContext* aPresContext);
|
||||
|
||||
// Handle aEvent in the current process.
|
||||
nsresult HandleQueryContentEvent(WidgetQueryContentEvent* aEvent);
|
||||
|
||||
// NS_QUERY_SELECTED_TEXT event handler
|
||||
nsresult OnQuerySelectedText(WidgetQueryContentEvent* aEvent);
|
||||
// NS_QUERY_TEXT_CONTENT event handler
|
||||
|
|
|
@ -73,19 +73,19 @@ static uint32_t
|
|||
MutationBitForEventType(EventMessage aEventType)
|
||||
{
|
||||
switch (aEventType) {
|
||||
case NS_MUTATION_SUBTREEMODIFIED:
|
||||
case eLegacySubtreeModified:
|
||||
return NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED;
|
||||
case NS_MUTATION_NODEINSERTED:
|
||||
case eLegacyNodeInserted:
|
||||
return NS_EVENT_BITS_MUTATION_NODEINSERTED;
|
||||
case NS_MUTATION_NODEREMOVED:
|
||||
case eLegacyNodeRemoved:
|
||||
return NS_EVENT_BITS_MUTATION_NODEREMOVED;
|
||||
case NS_MUTATION_NODEREMOVEDFROMDOCUMENT:
|
||||
case eLegacyNodeRemovedFromDocument:
|
||||
return NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT;
|
||||
case NS_MUTATION_NODEINSERTEDINTODOCUMENT:
|
||||
case eLegacyNodeInsertedIntoDocument:
|
||||
return NS_EVENT_BITS_MUTATION_NODEINSERTEDINTODOCUMENT;
|
||||
case NS_MUTATION_ATTRMODIFIED:
|
||||
case eLegacyAttrModified:
|
||||
return NS_EVENT_BITS_MUTATION_ATTRMODIFIED;
|
||||
case NS_MUTATION_CHARACTERDATAMODIFIED:
|
||||
case eLegacyCharacterDataModified:
|
||||
return NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED;
|
||||
default:
|
||||
break;
|
||||
|
@ -299,8 +299,8 @@ EventListenerManager::AddEventListenerInternal(
|
|||
if (window) {
|
||||
window->SetHasPaintEventListeners();
|
||||
}
|
||||
} else if (aEventMessage >= NS_MUTATION_START &&
|
||||
aEventMessage <= NS_MUTATION_END) {
|
||||
} else if (aEventMessage >= eLegacyMutationEventFirst &&
|
||||
aEventMessage <= eLegacyMutationEventLast) {
|
||||
// For mutation listeners, we need to update the global bit on the DOM window.
|
||||
// Otherwise we won't actually fire the mutation event.
|
||||
mMayHaveMutationListeners = true;
|
||||
|
@ -311,10 +311,10 @@ EventListenerManager::AddEventListenerInternal(
|
|||
if (doc) {
|
||||
doc->WarnOnceAbout(nsIDocument::eMutationEvent);
|
||||
}
|
||||
// If aEventMessage is NS_MUTATION_SUBTREEMODIFIED, we need to listen all
|
||||
// If aEventMessage is eLegacySubtreeModified, we need to listen all
|
||||
// mutations. nsContentUtils::HasMutationListeners relies on this.
|
||||
window->SetMutationListeners(
|
||||
(aEventMessage == NS_MUTATION_SUBTREEMODIFIED) ?
|
||||
(aEventMessage == eLegacySubtreeModified) ?
|
||||
kAllMutationBits : MutationBitForEventType(aEventMessage));
|
||||
}
|
||||
} else if (aTypeAtom == nsGkAtoms::ondeviceorientation) {
|
||||
|
@ -1226,8 +1226,8 @@ EventListenerManager::HasMutationListeners()
|
|||
uint32_t count = mListeners.Length();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
Listener* listener = &mListeners.ElementAt(i);
|
||||
if (listener->mEventMessage >= NS_MUTATION_START &&
|
||||
listener->mEventMessage <= NS_MUTATION_END) {
|
||||
if (listener->mEventMessage >= eLegacyMutationEventFirst &&
|
||||
listener->mEventMessage <= eLegacyMutationEventLast) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1244,9 +1244,9 @@ EventListenerManager::MutationListenerBits()
|
|||
uint32_t count = mListeners.Length();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
Listener* listener = &mListeners.ElementAt(i);
|
||||
if (listener->mEventMessage >= NS_MUTATION_START &&
|
||||
listener->mEventMessage <= NS_MUTATION_END) {
|
||||
if (listener->mEventMessage == NS_MUTATION_SUBTREEMODIFIED) {
|
||||
if (listener->mEventMessage >= eLegacyMutationEventFirst &&
|
||||
listener->mEventMessage <= eLegacyMutationEventLast) {
|
||||
if (listener->mEventMessage == eLegacySubtreeModified) {
|
||||
return kAllMutationBits;
|
||||
}
|
||||
bits |= MutationBitForEventType(listener->mEventMessage);
|
||||
|
|
|
@ -504,11 +504,11 @@ WINDOW_EVENT(online,
|
|||
EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly,
|
||||
eBasicEventClass)
|
||||
WINDOW_EVENT(pagehide,
|
||||
NS_PAGE_HIDE,
|
||||
ePageHide,
|
||||
EventNameType_HTMLBodyOrFramesetOnly,
|
||||
eBasicEventClass)
|
||||
WINDOW_EVENT(pageshow,
|
||||
NS_PAGE_SHOW,
|
||||
ePageShow,
|
||||
EventNameType_HTMLBodyOrFramesetOnly,
|
||||
eBasicEventClass)
|
||||
WINDOW_EVENT(popstate,
|
||||
|
@ -591,31 +591,31 @@ NON_IDL_EVENT(MozMouseHittest,
|
|||
eMouseEventClass)
|
||||
|
||||
NON_IDL_EVENT(DOMAttrModified,
|
||||
NS_MUTATION_ATTRMODIFIED,
|
||||
eLegacyAttrModified,
|
||||
EventNameType_HTMLXUL,
|
||||
eMutationEventClass)
|
||||
NON_IDL_EVENT(DOMCharacterDataModified,
|
||||
NS_MUTATION_CHARACTERDATAMODIFIED,
|
||||
eLegacyCharacterDataModified,
|
||||
EventNameType_HTMLXUL,
|
||||
eMutationEventClass)
|
||||
NON_IDL_EVENT(DOMNodeInserted,
|
||||
NS_MUTATION_NODEINSERTED,
|
||||
eLegacyNodeInserted,
|
||||
EventNameType_HTMLXUL,
|
||||
eMutationEventClass)
|
||||
NON_IDL_EVENT(DOMNodeRemoved,
|
||||
NS_MUTATION_NODEREMOVED,
|
||||
eLegacyNodeRemoved,
|
||||
EventNameType_HTMLXUL,
|
||||
eMutationEventClass)
|
||||
NON_IDL_EVENT(DOMNodeInsertedIntoDocument,
|
||||
NS_MUTATION_NODEINSERTEDINTODOCUMENT,
|
||||
eLegacyNodeInsertedIntoDocument,
|
||||
EventNameType_HTMLXUL,
|
||||
eMutationEventClass)
|
||||
NON_IDL_EVENT(DOMNodeRemovedFromDocument,
|
||||
NS_MUTATION_NODEREMOVEDFROMDOCUMENT,
|
||||
eLegacyNodeRemovedFromDocument,
|
||||
EventNameType_HTMLXUL,
|
||||
eMutationEventClass)
|
||||
NON_IDL_EVENT(DOMSubtreeModified,
|
||||
NS_MUTATION_SUBTREEMODIFIED,
|
||||
eLegacySubtreeModified,
|
||||
EventNameType_HTMLXUL,
|
||||
eMutationEventClass)
|
||||
|
||||
|
@ -742,24 +742,24 @@ NON_IDL_EVENT(underflow,
|
|||
|
||||
// Various SVG events
|
||||
NON_IDL_EVENT(SVGLoad,
|
||||
NS_SVG_LOAD,
|
||||
eSVGLoad,
|
||||
EventNameType_None,
|
||||
eBasicEventClass)
|
||||
NON_IDL_EVENT(SVGUnload,
|
||||
NS_SVG_UNLOAD,
|
||||
eSVGUnload,
|
||||
EventNameType_None,
|
||||
eBasicEventClass)
|
||||
NON_IDL_EVENT(SVGResize,
|
||||
NS_SVG_RESIZE,
|
||||
eSVGResize,
|
||||
EventNameType_None,
|
||||
eBasicEventClass)
|
||||
NON_IDL_EVENT(SVGScroll,
|
||||
NS_SVG_SCROLL,
|
||||
eSVGScroll,
|
||||
EventNameType_None,
|
||||
eBasicEventClass)
|
||||
|
||||
NON_IDL_EVENT(SVGZoom,
|
||||
NS_SVG_ZOOM,
|
||||
eSVGZoom,
|
||||
EventNameType_None,
|
||||
eSVGZoomEventClass)
|
||||
|
||||
|
@ -767,7 +767,7 @@ NON_IDL_EVENT(SVGZoom,
|
|||
#ifndef MESSAGE_TO_EVENT
|
||||
// This is a bit hackish, but SVG's event names are weird.
|
||||
NON_IDL_EVENT(zoom,
|
||||
NS_SVG_ZOOM,
|
||||
eSVGZoom,
|
||||
EventNameType_SVGSVG,
|
||||
eBasicEventClass)
|
||||
#endif
|
||||
|
|
|
@ -542,6 +542,11 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
|
|||
|
||||
*aStatus = nsEventStatus_eIgnore;
|
||||
|
||||
if (aEvent->mClass == eQueryContentEventClass) {
|
||||
HandleQueryContentEvent(aEvent->AsQueryContentEvent());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
switch (aEvent->mMessage) {
|
||||
case eContextMenu:
|
||||
if (sIsPointerLocked) {
|
||||
|
@ -738,73 +743,6 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
|
|||
InitLineOrPageDelta(aTargetFrame, this, wheelEvent);
|
||||
}
|
||||
break;
|
||||
case NS_QUERY_SELECTED_TEXT:
|
||||
DoQuerySelectedText(aEvent->AsQueryContentEvent());
|
||||
break;
|
||||
case NS_QUERY_TEXT_CONTENT:
|
||||
{
|
||||
if (RemoteQueryContentEvent(aEvent)) {
|
||||
break;
|
||||
}
|
||||
ContentEventHandler handler(mPresContext);
|
||||
handler.OnQueryTextContent(aEvent->AsQueryContentEvent());
|
||||
}
|
||||
break;
|
||||
case NS_QUERY_CARET_RECT:
|
||||
{
|
||||
if (RemoteQueryContentEvent(aEvent)) {
|
||||
break;
|
||||
}
|
||||
ContentEventHandler handler(mPresContext);
|
||||
handler.OnQueryCaretRect(aEvent->AsQueryContentEvent());
|
||||
}
|
||||
break;
|
||||
case NS_QUERY_TEXT_RECT:
|
||||
{
|
||||
if (RemoteQueryContentEvent(aEvent)) {
|
||||
break;
|
||||
}
|
||||
ContentEventHandler handler(mPresContext);
|
||||
handler.OnQueryTextRect(aEvent->AsQueryContentEvent());
|
||||
}
|
||||
break;
|
||||
case NS_QUERY_EDITOR_RECT:
|
||||
{
|
||||
if (RemoteQueryContentEvent(aEvent)) {
|
||||
break;
|
||||
}
|
||||
ContentEventHandler handler(mPresContext);
|
||||
handler.OnQueryEditorRect(aEvent->AsQueryContentEvent());
|
||||
}
|
||||
break;
|
||||
case NS_QUERY_CONTENT_STATE:
|
||||
{
|
||||
// XXX remote event
|
||||
ContentEventHandler handler(mPresContext);
|
||||
handler.OnQueryContentState(aEvent->AsQueryContentEvent());
|
||||
}
|
||||
break;
|
||||
case NS_QUERY_SELECTION_AS_TRANSFERABLE:
|
||||
{
|
||||
// XXX remote event
|
||||
ContentEventHandler handler(mPresContext);
|
||||
handler.OnQuerySelectionAsTransferable(aEvent->AsQueryContentEvent());
|
||||
}
|
||||
break;
|
||||
case NS_QUERY_CHARACTER_AT_POINT:
|
||||
{
|
||||
// XXX remote event
|
||||
ContentEventHandler handler(mPresContext);
|
||||
handler.OnQueryCharacterAtPoint(aEvent->AsQueryContentEvent());
|
||||
}
|
||||
break;
|
||||
case NS_QUERY_DOM_WIDGET_HITTEST:
|
||||
{
|
||||
// XXX remote event
|
||||
ContentEventHandler handler(mPresContext);
|
||||
handler.OnQueryDOMWidgetHittest(aEvent->AsQueryContentEvent());
|
||||
}
|
||||
break;
|
||||
case NS_SELECTION_SET:
|
||||
IMEStateManager::HandleSelectionEvent(aPresContext, GetFocusedContent(),
|
||||
aEvent->AsSelectionEvent());
|
||||
|
@ -832,7 +770,7 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
|
|||
WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
|
||||
WidgetQueryContentEvent selectedText(true, NS_QUERY_SELECTED_TEXT,
|
||||
compositionEvent->widget);
|
||||
DoQuerySelectedText(&selectedText);
|
||||
HandleQueryContentEvent(&selectedText);
|
||||
NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
|
||||
compositionEvent->mData = selectedText.mReply.mString;
|
||||
}
|
||||
|
@ -843,6 +781,42 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
EventStateManager::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
|
||||
{
|
||||
switch (aEvent->mMessage) {
|
||||
case NS_QUERY_SELECTED_TEXT:
|
||||
case NS_QUERY_TEXT_CONTENT:
|
||||
case NS_QUERY_CARET_RECT:
|
||||
case NS_QUERY_TEXT_RECT:
|
||||
case NS_QUERY_EDITOR_RECT:
|
||||
if (!IsTargetCrossProcess(aEvent)) {
|
||||
break;
|
||||
}
|
||||
// Will not be handled locally, remote the event
|
||||
GetCrossProcessTarget()->HandleQueryContentEvent(*aEvent);
|
||||
return;
|
||||
// Following events have not been supported in e10s mode yet.
|
||||
case NS_QUERY_CONTENT_STATE:
|
||||
case NS_QUERY_SELECTION_AS_TRANSFERABLE:
|
||||
case NS_QUERY_CHARACTER_AT_POINT:
|
||||
case NS_QUERY_DOM_WIDGET_HITTEST:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is an IMEContentObserver, we need to handle QueryContentEvent
|
||||
// with it.
|
||||
if (mIMEContentObserver) {
|
||||
mIMEContentObserver->HandleQueryContentEvent(aEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
ContentEventHandler handler(mPresContext);
|
||||
handler.HandleQueryContentEvent(aEvent);
|
||||
}
|
||||
|
||||
// static
|
||||
int32_t
|
||||
EventStateManager::GetAccessModifierMaskFor(nsISupports* aDocShell)
|
||||
|
@ -3368,18 +3342,6 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
EventStateManager::RemoteQueryContentEvent(WidgetEvent* aEvent)
|
||||
{
|
||||
WidgetQueryContentEvent* queryEvent = aEvent->AsQueryContentEvent();
|
||||
if (!IsTargetCrossProcess(queryEvent)) {
|
||||
return false;
|
||||
}
|
||||
// Will not be handled locally, remote the event
|
||||
GetCrossProcessTarget()->HandleQueryContentEvent(*queryEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
TabParent*
|
||||
EventStateManager::GetCrossProcessTarget()
|
||||
{
|
||||
|
@ -5189,16 +5151,6 @@ EventStateManager::DoContentCommandScrollEvent(
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
EventStateManager::DoQuerySelectedText(WidgetQueryContentEvent* aEvent)
|
||||
{
|
||||
if (RemoteQueryContentEvent(aEvent)) {
|
||||
return;
|
||||
}
|
||||
ContentEventHandler handler(mPresContext);
|
||||
handler.OnQuerySelectedText(aEvent);
|
||||
}
|
||||
|
||||
void
|
||||
EventStateManager::SetActiveManager(EventStateManager* aNewESM,
|
||||
nsIContent* aContent)
|
||||
|
|
|
@ -809,9 +809,6 @@ protected:
|
|||
nsresult DoContentCommandEvent(WidgetContentCommandEvent* aEvent);
|
||||
nsresult DoContentCommandScrollEvent(WidgetContentCommandEvent* aEvent);
|
||||
|
||||
void DoQuerySelectedText(WidgetQueryContentEvent* aEvent);
|
||||
|
||||
bool RemoteQueryContentEvent(WidgetEvent* aEvent);
|
||||
dom::TabParent *GetCrossProcessTarget();
|
||||
bool IsTargetCrossProcess(WidgetGUIEvent* aEvent);
|
||||
|
||||
|
@ -823,6 +820,8 @@ protected:
|
|||
|
||||
void ReleaseCurrentIMEContentObserver();
|
||||
|
||||
void HandleQueryContentEvent(WidgetQueryContentEvent* aEvent);
|
||||
|
||||
private:
|
||||
static inline void DoStateChange(dom::Element* aElement,
|
||||
EventStates aState, bool aAddState);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/Logging.h"
|
||||
|
||||
#include "ContentEventHandler.h"
|
||||
#include "IMEContentObserver.h"
|
||||
#include "mozilla/AsyncEventDispatcher.h"
|
||||
|
@ -37,6 +39,105 @@ namespace mozilla {
|
|||
|
||||
using namespace widget;
|
||||
|
||||
PRLogModuleInfo* sIMECOLog = nullptr;
|
||||
|
||||
static const char*
|
||||
ToChar(bool aBool)
|
||||
{
|
||||
return aBool ? "true" : "false";
|
||||
}
|
||||
|
||||
static const char*
|
||||
ToChar(EventMessage aEventMessage)
|
||||
{
|
||||
switch (aEventMessage) {
|
||||
case NS_QUERY_SELECTED_TEXT:
|
||||
return "NS_QUERY_SELECTED_TEXT";
|
||||
case NS_QUERY_TEXT_CONTENT:
|
||||
return "NS_QUERY_TEXT_CONTENT";
|
||||
case NS_QUERY_CARET_RECT:
|
||||
return "NS_QUERY_CARET_RECT";
|
||||
case NS_QUERY_TEXT_RECT:
|
||||
return "NS_QUERY_TEXT_RECT";
|
||||
case NS_QUERY_EDITOR_RECT:
|
||||
return "NS_QUERY_EDITOR_RECT";
|
||||
case NS_QUERY_CONTENT_STATE:
|
||||
return "NS_QUERY_CONTENT_STATE";
|
||||
case NS_QUERY_SELECTION_AS_TRANSFERABLE:
|
||||
return "NS_QUERY_SELECTION_AS_TRANSFERABLE";
|
||||
case NS_QUERY_CHARACTER_AT_POINT:
|
||||
return "NS_QUERY_CHARACTER_AT_POINT";
|
||||
case NS_QUERY_DOM_WIDGET_HITTEST:
|
||||
return "NS_QUERY_DOM_WIDGET_HITTEST";
|
||||
default:
|
||||
return "Unsupprted message";
|
||||
}
|
||||
}
|
||||
|
||||
class WritingModeToString final : public nsAutoCString
|
||||
{
|
||||
public:
|
||||
explicit WritingModeToString(const WritingMode& aWritingMode)
|
||||
{
|
||||
if (!aWritingMode.IsVertical()) {
|
||||
AssignLiteral("Horizontal");
|
||||
return;
|
||||
}
|
||||
if (aWritingMode.IsVerticalLR()) {
|
||||
AssignLiteral("Vertical (LR)");
|
||||
return;
|
||||
}
|
||||
AssignLiteral("Vertical (RL)");
|
||||
}
|
||||
virtual ~WritingModeToString() {}
|
||||
};
|
||||
|
||||
class SelectionChangeDataToString final : public nsAutoCString
|
||||
{
|
||||
public:
|
||||
explicit SelectionChangeDataToString(
|
||||
const IMENotification::SelectionChangeDataBase& aData)
|
||||
{
|
||||
if (!aData.IsValid()) {
|
||||
AppendLiteral("{ IsValid()=false }");
|
||||
return;
|
||||
}
|
||||
AppendPrintf("{ mOffset=%u, ", aData.mOffset);
|
||||
if (aData.mString->Length() > 20) {
|
||||
AppendPrintf("mString.Length()=%u, ", aData.mString->Length());
|
||||
} else {
|
||||
AppendPrintf("mString=\"%s\" (Length()=%u), ",
|
||||
NS_ConvertUTF16toUTF8(*aData.mString).get(),
|
||||
aData.mString->Length());
|
||||
}
|
||||
AppendPrintf("GetWritingMode()=%s, mReversed=%s, mCausedByComposition=%s, "
|
||||
"mCausedBySelectionEvent=%s }",
|
||||
WritingModeToString(aData.GetWritingMode()).get(),
|
||||
ToChar(aData.mReversed),
|
||||
ToChar(aData.mCausedByComposition),
|
||||
ToChar(aData.mCausedBySelectionEvent));
|
||||
}
|
||||
virtual ~SelectionChangeDataToString() {}
|
||||
};
|
||||
|
||||
class TextChangeDataToString final : public nsAutoCString
|
||||
{
|
||||
public:
|
||||
explicit TextChangeDataToString(
|
||||
const IMENotification::TextChangeDataBase& aData)
|
||||
{
|
||||
if (!aData.IsValid()) {
|
||||
AppendLiteral("{ IsValid()=false }");
|
||||
return;
|
||||
}
|
||||
AppendPrintf("{ mStartOffset=%u, mRemovedEndOffset=%u, mAddedEndOffset=%u, "
|
||||
"mCausedByComposition=%s }", aData.mStartOffset,
|
||||
aData.mRemovedEndOffset, aData.mAddedEndOffset,
|
||||
ToChar(aData.mCausedByComposition));
|
||||
}
|
||||
virtual ~TextChangeDataToString() {}
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
* mozilla::IMEContentObserver
|
||||
******************************************************************************/
|
||||
|
@ -63,6 +164,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWidget)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedWidget)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelection)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootContent)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditableNode)
|
||||
|
@ -102,6 +204,9 @@ IMEContentObserver::IMEContentObserver()
|
|||
#ifdef DEBUG
|
||||
mTextChangeData.Test();
|
||||
#endif
|
||||
if (!sIMECOLog) {
|
||||
sIMECOLog = PR_NewLogModule("IMEContentObserver");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -256,6 +361,12 @@ IMEContentObserver::NotifyIMEOfBlur()
|
|||
// mWidget must have been non-nullptr if IME has focus.
|
||||
MOZ_RELEASE_ASSERT(widget);
|
||||
|
||||
nsRefPtr<IMEContentObserver> kungFuDeathGrip(this);
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Info,
|
||||
("IMECO: 0x%p IMEContentObserver::NotifyIMEOfBlur(), "
|
||||
"sending NOTIFY_IME_OF_BLUR", this));
|
||||
|
||||
// For now, we need to send blur notification in any condition because
|
||||
// we don't have any simple ways to send blur notification asynchronously.
|
||||
// After this call, Destroy() or Unlink() will stop observing the content
|
||||
|
@ -266,6 +377,10 @@ IMEContentObserver::NotifyIMEOfBlur()
|
|||
// focus. So, this may not cause any problem.
|
||||
mIMEHasFocus = false;
|
||||
IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR), widget);
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::NotifyIMEOfBlur(), "
|
||||
"sent NOTIFY_IME_OF_BLUR", this));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -285,6 +400,8 @@ IMEContentObserver::UnregisterObservers()
|
|||
if (selPrivate) {
|
||||
selPrivate->RemoveSelectionListener(this);
|
||||
}
|
||||
mSelectionData.Clear();
|
||||
mFocusedWidget = nullptr;
|
||||
}
|
||||
|
||||
if (mUpdatePreference.WantTextChange() && mRootContent) {
|
||||
|
@ -443,6 +560,41 @@ IMEContentObserver::ReflowInterruptible(DOMHighResTimeStamp aStart,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
IMEContentObserver::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::HandleQueryContentEvent(aEvent={ "
|
||||
"mMessage=%s })", this, ToChar(aEvent->mMessage)));
|
||||
|
||||
// If the instance has cache, it should use the cached selection which was
|
||||
// sent to the widget.
|
||||
if (aEvent->mMessage == NS_QUERY_SELECTED_TEXT &&
|
||||
aEvent->mUseNativeLineBreak &&
|
||||
mSelectionData.IsValid()) {
|
||||
aEvent->mReply.mContentsRoot = mRootContent;
|
||||
aEvent->mReply.mHasSelection = !mSelectionData.IsCollapsed();
|
||||
aEvent->mReply.mOffset = mSelectionData.mOffset;
|
||||
aEvent->mReply.mString = mSelectionData.String();
|
||||
aEvent->mReply.mWritingMode = mSelectionData.GetWritingMode();
|
||||
aEvent->mReply.mReversed = mSelectionData.mReversed;
|
||||
aEvent->mSucceeded = true;
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::HandleQueryContentEvent(aEvent={ "
|
||||
"mMessage=%s })", this, ToChar(aEvent->mMessage)));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
ContentEventHandler handler(GetPresContext());
|
||||
nsresult rv = handler.HandleQueryContentEvent(aEvent);
|
||||
if (aEvent->mSucceeded) {
|
||||
// We need to guarantee that mRootContent should be always same value for
|
||||
// the observing editor.
|
||||
aEvent->mReply.mContentsRoot = mRootContent;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool
|
||||
IMEContentObserver::OnMouseButtonEvent(nsPresContext* aPresContext,
|
||||
WidgetMouseEvent* aMouseEvent)
|
||||
|
@ -786,9 +938,35 @@ IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
|
|||
MaybeNotifyIMEOfTextChange(data);
|
||||
}
|
||||
|
||||
void
|
||||
IMEContentObserver::SuppressNotifyingIME()
|
||||
{
|
||||
mSuppressNotifications++;
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::SuppressNotifyingIME(), "
|
||||
"mSuppressNotifications=%u", this, mSuppressNotifications));
|
||||
}
|
||||
|
||||
void
|
||||
IMEContentObserver::UnsuppressNotifyingIME()
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::UnsuppressNotifyingIME(), "
|
||||
"mSuppressNotifications=%u", this, mSuppressNotifications));
|
||||
|
||||
if (!mSuppressNotifications || --mSuppressNotifications) {
|
||||
return;
|
||||
}
|
||||
FlushMergeableNotifications();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
IMEContentObserver::EditAction()
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::EditAction()", this));
|
||||
|
||||
mEndOfAddedTextCache.Clear();
|
||||
mStartOfRemovingTextRangeCache.Clear();
|
||||
FlushMergeableNotifications();
|
||||
|
@ -798,6 +976,9 @@ IMEContentObserver::EditAction()
|
|||
NS_IMETHODIMP
|
||||
IMEContentObserver::BeforeEditAction()
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::BeforeEditAction()", this));
|
||||
|
||||
mEndOfAddedTextCache.Clear();
|
||||
mStartOfRemovingTextRangeCache.Clear();
|
||||
return NS_OK;
|
||||
|
@ -806,6 +987,9 @@ IMEContentObserver::BeforeEditAction()
|
|||
NS_IMETHODIMP
|
||||
IMEContentObserver::CancelEditAction()
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::CancelEditAction()", this));
|
||||
|
||||
mEndOfAddedTextCache.Clear();
|
||||
mStartOfRemovingTextRangeCache.Clear();
|
||||
FlushMergeableNotifications();
|
||||
|
@ -815,6 +999,9 @@ IMEContentObserver::CancelEditAction()
|
|||
void
|
||||
IMEContentObserver::PostFocusSetNotification()
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::PostFocusSetNotification()", this));
|
||||
|
||||
mIsFocusEventPending = true;
|
||||
}
|
||||
|
||||
|
@ -822,9 +1009,19 @@ void
|
|||
IMEContentObserver::PostTextChangeNotification(
|
||||
const TextChangeDataBase& aTextChangeData)
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::PostTextChangeNotification("
|
||||
"aTextChangeData=%s)",
|
||||
this, TextChangeDataToString(aTextChangeData).get()));
|
||||
|
||||
mTextChangeData += aTextChangeData;
|
||||
MOZ_ASSERT(mTextChangeData.IsValid(),
|
||||
"mTextChangeData must have text change data");
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::PostTextChangeNotification(), "
|
||||
"mTextChangeData=%s)",
|
||||
this, TextChangeDataToString(mTextChangeData).get()));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -832,6 +1029,11 @@ IMEContentObserver::PostSelectionChangeNotification(
|
|||
bool aCausedByComposition,
|
||||
bool aCausedBySelectionEvent)
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::PostSelectionChangeNotification("
|
||||
"aCausedByComposition=%s, aCausedBySelectionEvent=%s)",
|
||||
this, ToChar(aCausedByComposition), ToChar(aCausedBySelectionEvent)));
|
||||
|
||||
if (!mIsSelectionChangeEventPending) {
|
||||
mSelectionChangeCausedOnlyByComposition = aCausedByComposition;
|
||||
} else {
|
||||
|
@ -847,6 +1049,53 @@ IMEContentObserver::PostSelectionChangeNotification(
|
|||
mIsSelectionChangeEventPending = true;
|
||||
}
|
||||
|
||||
void
|
||||
IMEContentObserver::MaybeNotifyIMEOfFocusSet()
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfFocusSet()", this));
|
||||
|
||||
PostFocusSetNotification();
|
||||
FlushMergeableNotifications();
|
||||
}
|
||||
|
||||
void
|
||||
IMEContentObserver::MaybeNotifyIMEOfTextChange(
|
||||
const TextChangeDataBase& aTextChangeData)
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfTextChange("
|
||||
"aTextChangeData=%s)",
|
||||
this, TextChangeDataToString(aTextChangeData).get()));
|
||||
|
||||
PostTextChangeNotification(aTextChangeData);
|
||||
FlushMergeableNotifications();
|
||||
}
|
||||
|
||||
void
|
||||
IMEContentObserver::MaybeNotifyIMEOfSelectionChange(
|
||||
bool aCausedByComposition,
|
||||
bool aCausedBySelectionEvent)
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfSelectionChange("
|
||||
"aCausedByComposition=%s, aCausedBySelectionEvent=%s)",
|
||||
this, ToChar(aCausedByComposition), ToChar(aCausedBySelectionEvent)));
|
||||
|
||||
PostSelectionChangeNotification(aCausedByComposition,
|
||||
aCausedBySelectionEvent);
|
||||
FlushMergeableNotifications();
|
||||
}
|
||||
|
||||
void
|
||||
IMEContentObserver::MaybeNotifyIMEOfPositionChange()
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::MaybeNotifyIMEOfPositionChange()", this));
|
||||
PostPositionChangeNotification();
|
||||
FlushMergeableNotifications();
|
||||
}
|
||||
|
||||
bool
|
||||
IMEContentObserver::UpdateSelectionCache()
|
||||
{
|
||||
|
@ -867,18 +1116,28 @@ IMEContentObserver::UpdateSelectionCache()
|
|||
return false;
|
||||
}
|
||||
|
||||
mFocusedWidget = selection.mReply.mFocusedWidget;
|
||||
mSelectionData.mOffset = selection.mReply.mOffset;
|
||||
*mSelectionData.mString = selection.mReply.mString;
|
||||
mSelectionData.SetWritingMode(selection.GetWritingMode());
|
||||
mSelectionData.mReversed = selection.mReply.mReversed;
|
||||
mSelectionData.mCausedByComposition = false;
|
||||
mSelectionData.mCausedBySelectionEvent = false;
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::UpdateSelectionCache(), "
|
||||
"mSelectionData=%s",
|
||||
this, SelectionChangeDataToString(mSelectionData).get()));
|
||||
|
||||
return mSelectionData.IsValid();
|
||||
}
|
||||
|
||||
void
|
||||
IMEContentObserver::PostPositionChangeNotification()
|
||||
{
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::PostPositionChangeNotification()", this));
|
||||
|
||||
mIsPositionChangeEventPending = true;
|
||||
}
|
||||
|
||||
|
@ -938,6 +1197,9 @@ IMEContentObserver::FlushMergeableNotifications()
|
|||
{
|
||||
if (!IsSafeToNotifyIME()) {
|
||||
// So, if this is already called, this should do nothing.
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
|
||||
"FAILED, due to unsafe to notify IME", this));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -948,6 +1210,9 @@ IMEContentObserver::FlushMergeableNotifications()
|
|||
|
||||
if (mIsFlushingPendingNotifications) {
|
||||
// So, if this is already called, this should do nothing.
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
|
||||
"FAILED, due to already flushing pending notifications", this));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -958,6 +1223,9 @@ IMEContentObserver::FlushMergeableNotifications()
|
|||
// another change.
|
||||
|
||||
if (mIsFocusEventPending) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
|
||||
"creating FocusSetEvent...", this));
|
||||
mIsFocusEventPending = false;
|
||||
nsContentUtils::AddScriptRunner(new FocusSetEvent(this));
|
||||
// This is the first notification to IME. So, we don't need to notify any
|
||||
|
@ -967,6 +1235,9 @@ IMEContentObserver::FlushMergeableNotifications()
|
|||
}
|
||||
|
||||
if (mTextChangeData.IsValid()) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
|
||||
"creating TextChangeEvent...", this));
|
||||
nsContentUtils::AddScriptRunner(new TextChangeEvent(this, mTextChangeData));
|
||||
}
|
||||
|
||||
|
@ -974,6 +1245,9 @@ IMEContentObserver::FlushMergeableNotifications()
|
|||
// notification should not be sent before a text change notification because
|
||||
// PuppetWidget shouldn't query new text content every selection change.
|
||||
if (mIsSelectionChangeEventPending) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
|
||||
"creating SelectionChangeEvent...", this));
|
||||
mIsSelectionChangeEventPending = false;
|
||||
nsContentUtils::AddScriptRunner(
|
||||
new SelectionChangeEvent(this, mSelectionChangeCausedOnlyByComposition,
|
||||
|
@ -981,6 +1255,9 @@ IMEContentObserver::FlushMergeableNotifications()
|
|||
}
|
||||
|
||||
if (mIsPositionChangeEventPending) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
|
||||
"creating PositionChangeEvent...", this));
|
||||
mIsPositionChangeEventPending = false;
|
||||
nsContentUtils::AddScriptRunner(new PositionChangeEvent(this));
|
||||
}
|
||||
|
@ -989,10 +1266,17 @@ IMEContentObserver::FlushMergeableNotifications()
|
|||
if (mTextChangeData.IsValid() ||
|
||||
mIsSelectionChangeEventPending ||
|
||||
mIsPositionChangeEventPending) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
|
||||
"posting AsyncMergeableNotificationsFlusher to current thread", this));
|
||||
nsRefPtr<AsyncMergeableNotificationsFlusher> asyncFlusher =
|
||||
new AsyncMergeableNotificationsFlusher(this);
|
||||
NS_DispatchToCurrentThread(asyncFlusher);
|
||||
}
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::FlushMergeableNotifications(), "
|
||||
"finished", this));
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
|
@ -1052,11 +1336,17 @@ IMEContentObserver::FocusSetEvent::Run()
|
|||
if (!CanNotifyIME()) {
|
||||
// If IMEContentObserver has already gone, we don't need to notify IME of
|
||||
// focus.
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::FocusSetEvent::Run(), FAILED, due to "
|
||||
"impossible to notify IME of focus", this));
|
||||
mIMEContentObserver->ClearPendingNotifications();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!IsSafeToNotifyIME()) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::FocusSetEvent::Run(), retrying to "
|
||||
"send NOTIFY_IME_OF_FOCUS...", this));
|
||||
mIMEContentObserver->PostFocusSetNotification();
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1064,8 +1354,17 @@ IMEContentObserver::FocusSetEvent::Run()
|
|||
mIMEContentObserver->mIMEHasFocus = true;
|
||||
// Initialize selection cache with the first selection data.
|
||||
mIMEContentObserver->UpdateSelectionCache();
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Info,
|
||||
("IMECO: 0x%p IMEContentObserver::FocusSetEvent::Run(), "
|
||||
"sending NOTIFY_IME_OF_FOCUS...", this));
|
||||
|
||||
IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS),
|
||||
mIMEContentObserver->mWidget);
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::FocusSetEvent::Run(), "
|
||||
"sent NOTIFY_IME_OF_FOCUS", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1077,10 +1376,16 @@ NS_IMETHODIMP
|
|||
IMEContentObserver::SelectionChangeEvent::Run()
|
||||
{
|
||||
if (!CanNotifyIME()) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), FAILED, "
|
||||
"due to impossible to notify IME of selection change", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!IsSafeToNotifyIME()) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), "
|
||||
"retrying to send NOTIFY_IME_OF_SELECTION_CHANGE...", this));
|
||||
mIMEContentObserver->PostSelectionChangeNotification(
|
||||
mCausedByComposition, mCausedBySelectionEvent);
|
||||
return NS_OK;
|
||||
|
@ -1088,6 +1393,9 @@ IMEContentObserver::SelectionChangeEvent::Run()
|
|||
|
||||
SelectionChangeData lastSelChangeData = mIMEContentObserver->mSelectionData;
|
||||
if (NS_WARN_IF(!mIMEContentObserver->UpdateSelectionCache())) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Error,
|
||||
("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), FAILED, "
|
||||
"due to UpdateSelectionCache() failure", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1101,6 +1409,9 @@ IMEContentObserver::SelectionChangeEvent::Run()
|
|||
|
||||
// The state may be changed since querying content causes flushing layout.
|
||||
if (!CanNotifyIME()) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), FAILED, "
|
||||
"due to flushing layout having changed something", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1112,13 +1423,26 @@ IMEContentObserver::SelectionChangeEvent::Run()
|
|||
lastSelChangeData.String() == newSelChangeData.String() &&
|
||||
lastSelChangeData.GetWritingMode() == newSelChangeData.GetWritingMode() &&
|
||||
lastSelChangeData.mReversed == newSelChangeData.mReversed) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), not "
|
||||
"notifying IME of NOTIFY_IME_OF_SELECTION_CHANGE due to not changed "
|
||||
"actually", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Info,
|
||||
("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), "
|
||||
"sending NOTIFY_IME_OF_SELECTION_CHANGE... newSelChangeData=%s",
|
||||
this, SelectionChangeDataToString(newSelChangeData).get()));
|
||||
|
||||
IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
|
||||
notification.SetData(mIMEContentObserver->mSelectionData,
|
||||
mCausedByComposition, mCausedBySelectionEvent);
|
||||
IMEStateManager::NotifyIME(notification, mIMEContentObserver->mWidget);
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::SelectionChangeEvent::Run(), "
|
||||
"sent NOTIFY_IME_OF_SELECTION_CHANGE", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1130,17 +1454,32 @@ NS_IMETHODIMP
|
|||
IMEContentObserver::TextChangeEvent::Run()
|
||||
{
|
||||
if (!CanNotifyIME()) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::TextChangeEvent::Run(), FAILED, "
|
||||
"due to impossible to notify IME of text change", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!IsSafeToNotifyIME()) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::TextChangeEvent::Run(), retrying to "
|
||||
"send NOTIFY_IME_OF_TEXT_CHANGE...", this));
|
||||
mIMEContentObserver->PostTextChangeNotification(mTextChangeData);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Info,
|
||||
("IMECO: 0x%p IMEContentObserver::TextChangeEvent::Run(), "
|
||||
"sending NOTIFY_IME_OF_TEXT_CHANGE... mTextChangeData=%s",
|
||||
this, TextChangeDataToString(mTextChangeData).get()));
|
||||
|
||||
IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
|
||||
notification.SetData(mTextChangeData);
|
||||
IMEStateManager::NotifyIME(notification, mIMEContentObserver->mWidget);
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::TextChangeEvent::Run(), "
|
||||
"sent NOTIFY_IME_OF_TEXT_CHANGE", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1152,16 +1491,30 @@ NS_IMETHODIMP
|
|||
IMEContentObserver::PositionChangeEvent::Run()
|
||||
{
|
||||
if (!CanNotifyIME()) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::PositionChangeEvent::Run(), FAILED, "
|
||||
"due to impossible to notify IME of position change", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!IsSafeToNotifyIME()) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::PositionChangeEvent::Run(), "
|
||||
"retrying to send NOTIFY_IME_OF_POSITION_CHANGE...", this));
|
||||
mIMEContentObserver->PostPositionChangeNotification();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Info,
|
||||
("IMECO: 0x%p IMEContentObserver::PositionChangeEvent::Run(), "
|
||||
"sending NOTIFY_IME_OF_POSITION_CHANGE...", this));
|
||||
|
||||
IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE),
|
||||
mIMEContentObserver->mWidget);
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::PositionChangeEvent::Run(), "
|
||||
"sent NOTIFY_IME_OF_POSITION_CHANGE", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1173,10 +1526,22 @@ NS_IMETHODIMP
|
|||
IMEContentObserver::AsyncMergeableNotificationsFlusher::Run()
|
||||
{
|
||||
if (!CanNotifyIME()) {
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::AsyncMergeableNotificationsFlusher::"
|
||||
"Run(), FAILED, due to impossible to flush pending notifications",
|
||||
this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Info,
|
||||
("IMECO: 0x%p IMEContentObserver::AsyncMergeableNotificationsFlusher::"
|
||||
"Run(), calling FlushMergeableNotifications()...", this));
|
||||
|
||||
mIMEContentObserver->FlushMergeableNotifications();
|
||||
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Debug,
|
||||
("IMECO: 0x%p IMEContentObserver::AsyncMergeableNotificationsFlusher::"
|
||||
"Run(), called FlushMergeableNotifications()", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ public:
|
|||
bool OnMouseButtonEvent(nsPresContext* aPresContext,
|
||||
WidgetMouseEvent* aMouseEvent);
|
||||
|
||||
nsresult HandleQueryContentEvent(WidgetQueryContentEvent* aEvent);
|
||||
|
||||
void Init(nsIWidget* aWidget, nsPresContext* aPresContext,
|
||||
nsIContent* aContent, nsIEditor* aEditor);
|
||||
void Destroy();
|
||||
|
@ -94,14 +96,8 @@ public:
|
|||
}
|
||||
nsIWidget* GetWidget() const { return mWidget; }
|
||||
nsIEditor* GetEditor() const { return mEditor; }
|
||||
void SuppressNotifyingIME() { mSuppressNotifications++; }
|
||||
void UnsuppressNotifyingIME()
|
||||
{
|
||||
if (!mSuppressNotifications || --mSuppressNotifications) {
|
||||
return;
|
||||
}
|
||||
FlushMergeableNotifications();
|
||||
}
|
||||
void SuppressNotifyingIME();
|
||||
void UnsuppressNotifyingIME();
|
||||
nsPresContext* GetPresContext() const;
|
||||
nsresult GetSelectionAndRoot(nsISelection** aSelection,
|
||||
nsIContent** aRoot) const;
|
||||
|
@ -122,32 +118,15 @@ private:
|
|||
bool IsSafeToNotifyIME() const;
|
||||
|
||||
void PostFocusSetNotification();
|
||||
void MaybeNotifyIMEOfFocusSet()
|
||||
{
|
||||
PostFocusSetNotification();
|
||||
FlushMergeableNotifications();
|
||||
}
|
||||
void MaybeNotifyIMEOfFocusSet();
|
||||
void PostTextChangeNotification(const TextChangeDataBase& aTextChangeData);
|
||||
void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData)
|
||||
{
|
||||
PostTextChangeNotification(aTextChangeData);
|
||||
FlushMergeableNotifications();
|
||||
}
|
||||
void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData);
|
||||
void PostSelectionChangeNotification(bool aCausedByComposition,
|
||||
bool aCausedBySelectionEvent);
|
||||
void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition,
|
||||
bool aCausedBySelectionEvent)
|
||||
{
|
||||
PostSelectionChangeNotification(aCausedByComposition,
|
||||
aCausedBySelectionEvent);
|
||||
FlushMergeableNotifications();
|
||||
}
|
||||
bool aCausedBySelectionEvent);
|
||||
void PostPositionChangeNotification();
|
||||
void MaybeNotifyIMEOfPositionChange()
|
||||
{
|
||||
PostPositionChangeNotification();
|
||||
FlushMergeableNotifications();
|
||||
}
|
||||
void MaybeNotifyIMEOfPositionChange();
|
||||
|
||||
void NotifyContentAdded(nsINode* aContainer, int32_t aStart, int32_t aEnd);
|
||||
void ObserveEditableNode();
|
||||
|
@ -178,6 +157,10 @@ private:
|
|||
bool UpdateSelectionCache();
|
||||
|
||||
nsCOMPtr<nsIWidget> mWidget;
|
||||
// mFocusedWidget has the editor observed by the instance. E.g., if the
|
||||
// focused editor is in XUL panel, this should be the widget of the panel.
|
||||
// On the other hand, mWidget is its parent which handles IME.
|
||||
nsCOMPtr<nsIWidget> mFocusedWidget;
|
||||
nsCOMPtr<nsISelection> mSelection;
|
||||
nsCOMPtr<nsIContent> mRootContent;
|
||||
nsCOMPtr<nsINode> mEditableNode;
|
||||
|
|
|
@ -114,6 +114,7 @@ TextComposition::CloneAndDispatchAs(
|
|||
compositionEvent.time = aCompositionEvent->time;
|
||||
compositionEvent.timeStamp = aCompositionEvent->timeStamp;
|
||||
compositionEvent.mData = aCompositionEvent->mData;
|
||||
compositionEvent.mOriginalMessage = aCompositionEvent->mMessage;
|
||||
compositionEvent.mFlags.mIsSynthesizedForTests =
|
||||
aCompositionEvent->mFlags.mIsSynthesizedForTests;
|
||||
|
||||
|
|
|
@ -789,14 +789,14 @@ class WorkerPermissionChallenge;
|
|||
|
||||
// This class calles WorkerPermissionChallenge::OperationCompleted() in the
|
||||
// worker thread.
|
||||
class WorkerPermissionOperationCompleted final : public WorkerRunnable
|
||||
class WorkerPermissionOperationCompleted final : public WorkerControlRunnable
|
||||
{
|
||||
nsRefPtr<WorkerPermissionChallenge> mChallenge;
|
||||
|
||||
public:
|
||||
WorkerPermissionOperationCompleted(WorkerPrivate* aWorkerPrivate,
|
||||
WorkerPermissionChallenge* aChallenge)
|
||||
: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
|
||||
: WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
|
||||
, mChallenge(aChallenge)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
@ -902,11 +902,7 @@ public:
|
|||
nsRefPtr<WorkerPermissionOperationCompleted> runnable =
|
||||
new WorkerPermissionOperationCompleted(mWorkerPrivate, this);
|
||||
|
||||
if (!runnable->Dispatch(nullptr)) {
|
||||
NS_WARNING("Failed to dispatch a runnable to the worker thread.");
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_TRUE(runnable->Dispatch(nullptr));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1384,7 +1380,7 @@ BackgroundFactoryRequestChild::RecvPermissionChallenge(
|
|||
JSContext* cx = workerPrivate->GetJSContext();
|
||||
MOZ_ASSERT(cx);
|
||||
|
||||
if (!workerPrivate->AddFeature(cx, challenge)) {
|
||||
if (NS_WARN_IF(!workerPrivate->AddFeature(cx, challenge))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1406,7 +1402,9 @@ BackgroundFactoryRequestChild::RecvPermissionChallenge(
|
|||
nsCOMPtr<Element> ownerElement =
|
||||
do_QueryInterface(window->GetChromeEventHandler());
|
||||
if (NS_WARN_IF(!ownerElement)) {
|
||||
return false;
|
||||
// If this fails, the page was navigated. Fail the permission check by
|
||||
// forcing an immediate retry.
|
||||
return SendPermissionRetry();
|
||||
}
|
||||
|
||||
nsRefPtr<PermissionRequestMainProcessHelper> helper =
|
||||
|
|
|
@ -103,11 +103,8 @@ public:
|
|||
virtual bool IsMediaSeekable() = 0;
|
||||
|
||||
virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags, MediaDecoderEventVisibility aEventVisibility) = 0;
|
||||
virtual void QueueMetadata(const media::TimeUnit& aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) = 0;
|
||||
virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo, MediaDecoderEventVisibility aEventVisibility) = 0;
|
||||
|
||||
virtual void RemoveMediaTracks() = 0;
|
||||
|
||||
// May be called by the reader to notify this decoder that the metadata from
|
||||
// the media file has been read. Call on the decode thread only.
|
||||
virtual void OnReadMetadataCompleted() = 0;
|
||||
|
@ -221,45 +218,6 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class MetadataUpdatedEventRunner : public nsRunnable, private MetadataContainer
|
||||
{
|
||||
public:
|
||||
MetadataUpdatedEventRunner(AbstractMediaDecoder* aDecoder,
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags,
|
||||
MediaDecoderEventVisibility aEventVisibility = MediaDecoderEventVisibility::Observable)
|
||||
: MetadataContainer(aDecoder, aInfo, aTags, aEventVisibility)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
nsAutoPtr<MediaInfo> info(new MediaInfo());
|
||||
*info = *mInfo;
|
||||
mDecoder->MetadataLoaded(info, mTags, mEventVisibility);
|
||||
mDecoder->FirstFrameLoaded(mInfo, mEventVisibility);
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
class RemoveMediaTracksEventRunner : public nsRunnable
|
||||
{
|
||||
public:
|
||||
explicit RemoveMediaTracksEventRunner(AbstractMediaDecoder* aDecoder)
|
||||
: mDecoder(aDecoder)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
mDecoder->RemoveMediaTracks();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<AbstractMediaDecoder> mDecoder;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
|
@ -401,10 +401,16 @@ MP3TrackDemuxer::FindNextFrame() {
|
|||
// This is not a valid MPEG audio stream or we've reached EOS, give up.
|
||||
break;
|
||||
}
|
||||
MOZ_ASSERT(mOffset + read > mOffset);
|
||||
NS_ENSURE_TRUE(mOffset + read > mOffset, MediaByteRange(0, 0));
|
||||
mOffset += read;
|
||||
bufferEnd = buffer + read;
|
||||
frameBeg = mParser.Parse(buffer, bufferEnd);
|
||||
const FrameParserResult parseResults = mParser.Parse(buffer, bufferEnd);
|
||||
frameBeg = parseResults.mBufferPos;
|
||||
|
||||
// If mBytesToSkip is > 0, this skips the rest of an ID3 tag which stretches
|
||||
// beyond the current buffer.
|
||||
NS_ENSURE_TRUE(mOffset + parseResults.mBytesToSkip >= mOffset, MediaByteRange(0, 0));
|
||||
mOffset += parseResults.mBytesToSkip;
|
||||
}
|
||||
|
||||
if (frameBeg == bufferEnd || !mParser.CurrentFrame().Length()) {
|
||||
|
@ -605,10 +611,10 @@ FrameParser::VBRInfo() const {
|
|||
return mVBRHeader;
|
||||
}
|
||||
|
||||
const uint8_t*
|
||||
FrameParserResult
|
||||
FrameParser::Parse(const uint8_t* aBeg, const uint8_t* aEnd) {
|
||||
if (!aBeg || !aEnd || aBeg >= aEnd) {
|
||||
return aEnd;
|
||||
return { aEnd, 0 };
|
||||
}
|
||||
|
||||
if (!mID3Parser.Header().Size() && !mFirstFrame.Length()) {
|
||||
|
@ -617,8 +623,16 @@ FrameParser::Parse(const uint8_t* aBeg, const uint8_t* aEnd) {
|
|||
// TODO: should we try to read ID3 tags at end of file/mid-stream, too?
|
||||
const uint8_t* id3Beg = mID3Parser.Parse(aBeg, aEnd);
|
||||
if (id3Beg != aEnd) {
|
||||
// ID3 headers found, skip past them.
|
||||
aBeg = id3Beg + ID3Parser::ID3Header::SIZE + mID3Parser.Header().Size();
|
||||
// ID3 tag found, skip past it.
|
||||
const uint32_t tagSize = ID3Parser::ID3Header::SIZE + mID3Parser.Header().Size() +
|
||||
mID3Parser.Header().FooterSize();
|
||||
const uint32_t remainingBuffer = aEnd - id3Beg;
|
||||
if (tagSize > remainingBuffer) {
|
||||
// Skipping across the ID3 tag would take us past the end of the buffer, therefore we
|
||||
// return immediately and let the calling function handle skipping the rest of the tag.
|
||||
return { aEnd, tagSize - remainingBuffer };
|
||||
}
|
||||
aBeg = id3Beg + tagSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -633,9 +647,9 @@ FrameParser::Parse(const uint8_t* aBeg, const uint8_t* aEnd) {
|
|||
}
|
||||
// Move to the frame header begin to allow for whole-frame parsing.
|
||||
aBeg -= FrameHeader::SIZE;
|
||||
return aBeg;
|
||||
return { aBeg, 0 };
|
||||
}
|
||||
return aEnd;
|
||||
return { aEnd, 0 };
|
||||
}
|
||||
|
||||
// FrameParser::Header
|
||||
|
@ -790,7 +804,7 @@ FrameParser::FrameHeader::ParseNext(uint8_t c) {
|
|||
|
||||
bool
|
||||
FrameParser::FrameHeader::IsValid(int aPos) const {
|
||||
if (IsValid()) {
|
||||
if (aPos >= SIZE) {
|
||||
return true;
|
||||
}
|
||||
if (aPos == frame_header::SYNC1) {
|
||||
|
@ -802,7 +816,8 @@ FrameParser::FrameHeader::IsValid(int aPos) const {
|
|||
RawLayer() != 0;
|
||||
}
|
||||
if (aPos == frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE) {
|
||||
return RawBitrate() != 0xF;
|
||||
return RawBitrate() != 0xF && RawBitrate() != 0 &&
|
||||
RawSampleRate() != 3;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1017,6 +1032,14 @@ ID3Parser::ID3Header::Size() const {
|
|||
return mSize;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
ID3Parser::ID3Header::FooterSize() const {
|
||||
if (Flags() & (1 << 4)) {
|
||||
return SIZE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
ID3Parser::ID3Header::ParseNext(uint8_t c) {
|
||||
if (!Update(c)) {
|
||||
|
@ -1030,7 +1053,7 @@ ID3Parser::ID3Header::ParseNext(uint8_t c) {
|
|||
|
||||
bool
|
||||
ID3Parser::ID3Header::IsValid(int aPos) const {
|
||||
if (IsValid()) {
|
||||
if (aPos >= SIZE) {
|
||||
return true;
|
||||
}
|
||||
const uint8_t c = mRaw[aPos];
|
||||
|
|
|
@ -61,9 +61,12 @@ public:
|
|||
// The ID3 flags field.
|
||||
uint8_t Flags() const;
|
||||
|
||||
// The derived size based on the provides size fields.
|
||||
// The derived size based on the provided size fields.
|
||||
uint32_t Size() const;
|
||||
|
||||
// Returns the size of an ID3v2.4 footer if present and zero otherwise.
|
||||
uint8_t FooterSize() const;
|
||||
|
||||
// Returns whether the parsed data is a valid ID3 header up to the given
|
||||
// byte position.
|
||||
bool IsValid(int aPos) const;
|
||||
|
@ -108,6 +111,11 @@ private:
|
|||
ID3Header mHeader;
|
||||
};
|
||||
|
||||
struct FrameParserResult {
|
||||
const uint8_t* mBufferPos;
|
||||
const uint32_t mBytesToSkip;
|
||||
};
|
||||
|
||||
// MPEG audio frame parser.
|
||||
// The MPEG frame header has the following format (one bit per character):
|
||||
// 11111111 111VVLLC BBBBSSPR MMEETOHH
|
||||
|
@ -282,9 +290,11 @@ public:
|
|||
// - resets ID3Header if no valid header was parsed yet
|
||||
void EndFrameSession();
|
||||
|
||||
// Parses given buffer [aBeg, aEnd) for a valid frame header.
|
||||
// Returns begin of frame header if a frame header was found or aEnd otherwise.
|
||||
const uint8_t* Parse(const uint8_t* aBeg, const uint8_t* aEnd);
|
||||
// Parses given buffer [aBeg, aEnd) for a valid frame header and returns a FrameParserResult.
|
||||
// FrameParserResult.mBufferPos points to begin of frame header if a frame header was found
|
||||
// or to aEnd otherwise. FrameParserResult.mBytesToSkip indicates whether additional bytes need to
|
||||
// be skipped in order to jump across an ID3 tag that stretches beyond the given buffer.
|
||||
FrameParserResult Parse(const uint8_t* aBeg, const uint8_t* aEnd);
|
||||
|
||||
// Parses given buffer [aBeg, aEnd) for a valid VBR header.
|
||||
// Returns whether a valid VBR header was found.
|
||||
|
|
|
@ -463,6 +463,7 @@ void MediaDecoder::Shutdown()
|
|||
// the asynchronous shutdown in nsDestroyStateMachine won't deadlock.
|
||||
if (mDecoderStateMachine) {
|
||||
mDecoderStateMachine->DispatchShutdown();
|
||||
mTimedMetadataListener.Disconnect();
|
||||
}
|
||||
|
||||
// Force any outstanding seek and byterange requests to complete
|
||||
|
@ -545,6 +546,8 @@ void MediaDecoder::SetStateMachineParameters()
|
|||
if (mMinimizePreroll) {
|
||||
mDecoderStateMachine->DispatchMinimizePrerollUntilPlaybackStarts();
|
||||
}
|
||||
mTimedMetadataListener = mDecoderStateMachine->TimedMetadataEvent().Connect(
|
||||
AbstractThread::MainThread(), this, &MediaDecoder::OnMetadataUpdate);
|
||||
}
|
||||
|
||||
void MediaDecoder::SetMinimizePrerollUntilPlaybackStarts()
|
||||
|
@ -635,13 +638,15 @@ already_AddRefed<nsIPrincipal> MediaDecoder::GetCurrentPrincipal()
|
|||
return mResource ? mResource->GetCurrentPrincipal() : nullptr;
|
||||
}
|
||||
|
||||
void MediaDecoder::QueueMetadata(const TimeUnit& aPublishTime,
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags)
|
||||
void MediaDecoder::OnMetadataUpdate(TimedMetadata&& aMetadata)
|
||||
{
|
||||
MOZ_ASSERT(OnDecodeTaskQueue());
|
||||
GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
mDecoderStateMachine->QueueMetadata(aPublishTime, aInfo, aTags);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
RemoveMediaTracks();
|
||||
MetadataLoaded(nsAutoPtr<MediaInfo>(new MediaInfo(*aMetadata.mInfo)),
|
||||
Move(aMetadata.mTags),
|
||||
MediaDecoderEventVisibility::Observable);
|
||||
FirstFrameLoaded(Move(aMetadata.mInfo),
|
||||
MediaDecoderEventVisibility::Observable);
|
||||
}
|
||||
|
||||
void MediaDecoder::MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
|
||||
|
|
|
@ -184,6 +184,10 @@ destroying the MediaDecoder object.
|
|||
#if !defined(MediaDecoder_h_)
|
||||
#define MediaDecoder_h_
|
||||
|
||||
#ifdef MOZ_EME
|
||||
#include "mozilla/CDMProxy.h"
|
||||
#endif
|
||||
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
#include "mozilla/StateMirroring.h"
|
||||
|
@ -191,20 +195,19 @@ destroying the MediaDecoder object.
|
|||
|
||||
#include "mozilla/dom/AudioChannelBinding.h"
|
||||
|
||||
#include "nsISupports.h"
|
||||
#include "necko-config.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsITimer.h"
|
||||
#include "MediaResource.h"
|
||||
#include "MediaDecoderOwner.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "DecodedStream.h"
|
||||
#include "necko-config.h"
|
||||
#ifdef MOZ_EME
|
||||
#include "mozilla/CDMProxy.h"
|
||||
#endif
|
||||
#include "MediaDecoderOwner.h"
|
||||
#include "MediaEventSource.h"
|
||||
#include "MediaMetadataManager.h"
|
||||
#include "MediaResource.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
#include "TimeUnits.h"
|
||||
|
||||
class nsIStreamListener;
|
||||
|
@ -579,13 +582,6 @@ public:
|
|||
void SetAudioChannel(dom::AudioChannel aChannel) { mAudioChannel = aChannel; }
|
||||
dom::AudioChannel GetAudioChannel() { return mAudioChannel; }
|
||||
|
||||
// Send a new set of metadata to the state machine, to be dispatched to the
|
||||
// main thread to be presented when the |currentTime| of the media is greater
|
||||
// or equal to aPublishTime.
|
||||
void QueueMetadata(const media::TimeUnit& aPublishTime,
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags) override;
|
||||
|
||||
/******
|
||||
* The following methods must only be called on the main
|
||||
* thread.
|
||||
|
@ -618,7 +614,7 @@ public:
|
|||
|
||||
// Removes all audio tracks and video tracks that are previously added into
|
||||
// the track list. Call on the main thread only.
|
||||
virtual void RemoveMediaTracks() override;
|
||||
void RemoveMediaTracks();
|
||||
|
||||
// Called when the video has completed playing.
|
||||
// Call on the main thread only.
|
||||
|
@ -989,6 +985,8 @@ protected:
|
|||
|
||||
const char* PlayStateStr();
|
||||
|
||||
void OnMetadataUpdate(TimedMetadata&& aMetadata);
|
||||
|
||||
// This should only ever be accessed from the main thread.
|
||||
// It is set in Init and cleared in Shutdown when the element goes away.
|
||||
// The decoder does not add a reference the element.
|
||||
|
@ -1059,6 +1057,9 @@ protected:
|
|||
// Timer to schedule updating dormant state.
|
||||
nsCOMPtr<nsITimer> mDormantTimer;
|
||||
|
||||
// A listener to receive metadata updates from MDSM.
|
||||
MediaEventListener mTimedMetadataListener;
|
||||
|
||||
protected:
|
||||
// Whether the state machine is shut down.
|
||||
Mirror<bool> mStateMachineIsShutdown;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "AbstractMediaDecoder.h"
|
||||
#include "MediaInfo.h"
|
||||
#include "MediaData.h"
|
||||
#include "MediaMetadataManager.h"
|
||||
#include "MediaQueue.h"
|
||||
#include "MediaTimer.h"
|
||||
#include "AudioCompactor.h"
|
||||
|
@ -327,6 +328,10 @@ public:
|
|||
|
||||
virtual void DisableHardwareAcceleration() {}
|
||||
|
||||
TimedMetadataEventSource& TimedMetadataEvent() {
|
||||
return mTimedMetadataEvent;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~MediaDecoderReader();
|
||||
|
||||
|
@ -418,6 +423,9 @@ protected:
|
|||
bool mHitAudioDecodeError;
|
||||
bool mShutdown;
|
||||
|
||||
// Used to send TimedMetadata to the listener.
|
||||
TimedMetadataEventProducer mTimedMetadataEvent;
|
||||
|
||||
private:
|
||||
// Promises used only for the base-class (sync->async adapter) implementation
|
||||
// of Request{Audio,Video}Data.
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "MediaTimer.h"
|
||||
#include "mediasink/DecodedAudioDataSink.h"
|
||||
#include "mediasink/AudioSinkWrapper.h"
|
||||
#include "mediasink/DecodedStream.h"
|
||||
#include "nsTArray.h"
|
||||
#include "MediaDecoder.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
|
@ -39,7 +40,6 @@
|
|||
#include "gfx2DGlue.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "DOMMediaStream.h"
|
||||
#include "DecodedStream.h"
|
||||
#include "mozilla/Logging.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -221,7 +221,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
|
|||
mSentLoadedMetadataEvent(false),
|
||||
mSentFirstFrameLoadedEvent(false),
|
||||
mSentPlaybackEndedEvent(false),
|
||||
mDecodedStream(new DecodedStream(mTaskQueue, mAudioQueue, mVideoQueue)),
|
||||
mStreamSink(new DecodedStream(mTaskQueue, mAudioQueue, mVideoQueue)),
|
||||
mResource(aDecoder->GetResource()),
|
||||
mBuffered(mTaskQueue, TimeIntervals(),
|
||||
"MediaDecoderStateMachine::mBuffered (Mirror)"),
|
||||
|
@ -289,6 +289,8 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
|
|||
mVideoQueueListener = VideoQueue().PopEvent().Connect(
|
||||
mTaskQueue, this, &MediaDecoderStateMachine::OnVideoPopped);
|
||||
|
||||
mMetadataManager.Connect(mReader->TimedMetadataEvent(), OwnerThread());
|
||||
|
||||
nsRefPtr<MediaDecoderStateMachine> self = this;
|
||||
auto audioSinkCreator = [self] () {
|
||||
MOZ_ASSERT(self->OnTaskQueue());
|
||||
|
@ -1099,7 +1101,7 @@ void MediaDecoderStateMachine::UpdatePlaybackPosition(int64_t aTime)
|
|||
UpdatePlaybackPositionInternal(aTime);
|
||||
|
||||
bool fragmentEnded = mFragmentEndTime >= 0 && GetMediaTime() >= mFragmentEndTime;
|
||||
mMetadataManager.DispatchMetadataIfNeeded(mDecoder, TimeUnit::FromMicroseconds(aTime));
|
||||
mMetadataManager.DispatchMetadataIfNeeded(TimeUnit::FromMicroseconds(aTime));
|
||||
|
||||
if (fragmentEnded) {
|
||||
StopPlayback();
|
||||
|
@ -1151,7 +1153,7 @@ void MediaDecoderStateMachine::VolumeChanged()
|
|||
MOZ_ASSERT(OnTaskQueue());
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mAudioSink->SetVolume(mVolume);
|
||||
mDecodedStream->SetVolume(mVolume);
|
||||
mStreamSink->SetVolume(mVolume);
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::RecomputeDuration()
|
||||
|
@ -1405,7 +1407,7 @@ void MediaDecoderStateMachine::SameOriginMediaChanged()
|
|||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDecodedStream->SetSameOrigin(mSameOriginMedia);
|
||||
mStreamSink->SetSameOrigin(mSameOriginMedia);
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::BufferedRangeUpdated()
|
||||
|
@ -1759,15 +1761,17 @@ MediaDecoderStateMachine::StartAudioSink()
|
|||
return;
|
||||
}
|
||||
|
||||
if (HasAudio() && !mAudioSink->IsStarted()) {
|
||||
if (!mAudioSink->IsStarted()) {
|
||||
mAudioCompleted = false;
|
||||
mAudioSink->Start(GetMediaTime(), mInfo);
|
||||
|
||||
mAudioSinkPromise.Begin(
|
||||
mAudioSink->OnEnded(TrackInfo::kAudioTrack)->Then(
|
||||
auto promise = mAudioSink->OnEnded(TrackInfo::kAudioTrack);
|
||||
if (promise) {
|
||||
mAudioSinkPromise.Begin(promise->Then(
|
||||
OwnerThread(), __func__, this,
|
||||
&MediaDecoderStateMachine::OnAudioSinkComplete,
|
||||
&MediaDecoderStateMachine::OnAudioSinkError));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1776,8 +1780,11 @@ MediaDecoderStateMachine::StopDecodedStream()
|
|||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
mDecodedStream->StopPlayback();
|
||||
mDecodedStreamPromise.DisconnectIfExists();
|
||||
|
||||
if (mStreamSink->IsStarted()) {
|
||||
mStreamSink->Stop();
|
||||
mDecodedStreamPromise.DisconnectIfExists();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1788,9 +1795,10 @@ MediaDecoderStateMachine::StartDecodedStream()
|
|||
|
||||
// Tell DecodedStream to start playback with specified start time and media
|
||||
// info. This is consistent with how we create AudioSink in StartAudioThread().
|
||||
if (mAudioCaptured && !mDecodedStreamPromise.Exists()) {
|
||||
if (mAudioCaptured && !mStreamSink->IsStarted()) {
|
||||
mStreamSink->Start(GetMediaTime(), mInfo);
|
||||
mDecodedStreamPromise.Begin(
|
||||
mDecodedStream->StartPlayback(GetMediaTime(), mInfo)->Then(
|
||||
mStreamSink->OnEnded(TrackInfo::kAudioTrack)->Then(
|
||||
OwnerThread(), __func__, this,
|
||||
&MediaDecoderStateMachine::OnDecodedStreamFinish,
|
||||
&MediaDecoderStateMachine::OnDecodedStreamError));
|
||||
|
@ -2182,6 +2190,15 @@ private:
|
|||
nsRefPtr<MediaDecoderStateMachine> mStateMachine;
|
||||
};
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::DispatchShutdown()
|
||||
{
|
||||
mStreamSink->BeginShutdown();
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::Shutdown);
|
||||
OwnerThread()->Dispatch(runnable.forget());
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::FinishShutdown()
|
||||
{
|
||||
|
@ -2195,6 +2212,7 @@ MediaDecoderStateMachine::FinishShutdown()
|
|||
// Prevent dangling pointers by disconnecting the listeners.
|
||||
mAudioQueueListener.Disconnect();
|
||||
mVideoQueueListener.Disconnect();
|
||||
mMetadataManager.Disconnect();
|
||||
|
||||
// Disconnect canonicals and mirrors before shutting down our task queue.
|
||||
mBuffered.DisconnectIfConnected();
|
||||
|
@ -2361,7 +2379,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
|||
// end of the media, and so that we update the readyState.
|
||||
if (VideoQueue().GetSize() > 1 ||
|
||||
(HasAudio() && !mAudioCompleted) ||
|
||||
(mAudioCaptured && !mDecodedStream->IsFinished()))
|
||||
(mAudioCaptured && !mStreamSink->IsFinished()))
|
||||
{
|
||||
// Start playback if necessary to play the remaining media.
|
||||
MaybeStartPlayback();
|
||||
|
@ -2553,54 +2571,6 @@ void MediaDecoderStateMachine::RenderVideoFrames(int32_t aMaxFrames,
|
|||
container->SetCurrentFrames(frames[0]->As<VideoData>()->mDisplay, images);
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::ResyncAudioClock()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
if (IsPlaying()) {
|
||||
SetPlayStartTime(TimeStamp::Now());
|
||||
mPlayDuration = GetAudioClock();
|
||||
}
|
||||
}
|
||||
|
||||
int64_t
|
||||
MediaDecoderStateMachine::GetAudioClock() const
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
// We must hold the decoder monitor while using the audio stream off the
|
||||
// audio sink to ensure that it doesn't get destroyed on the audio sink
|
||||
// while we're using it.
|
||||
AssertCurrentThreadInMonitor();
|
||||
MOZ_ASSERT(HasAudio() && !mAudioCompleted && IsPlaying());
|
||||
// Since this function is called while we are playing and AudioSink is
|
||||
// started once playback starts, IsStarted() is guaranteed to be true.
|
||||
MOZ_ASSERT(mAudioSink->IsStarted());
|
||||
return mAudioSink->GetPosition();
|
||||
}
|
||||
|
||||
int64_t MediaDecoderStateMachine::GetStreamClock() const
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
return mDecodedStream->GetPosition();
|
||||
}
|
||||
|
||||
int64_t MediaDecoderStateMachine::GetVideoStreamPosition(TimeStamp aTimeStamp) const
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
|
||||
if (!IsPlaying()) {
|
||||
return mPlayDuration;
|
||||
}
|
||||
|
||||
// Time elapsed since we started playing.
|
||||
int64_t delta = DurationToUsecs(aTimeStamp - mPlayStartTime);
|
||||
// Take playback rate into account.
|
||||
delta *= mPlaybackRate;
|
||||
return mPlayDuration + delta;
|
||||
}
|
||||
|
||||
int64_t MediaDecoderStateMachine::GetClock(TimeStamp* aTimeStamp) const
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
@ -2616,13 +2586,9 @@ int64_t MediaDecoderStateMachine::GetClock(TimeStamp* aTimeStamp) const
|
|||
clock_time = mPlayDuration;
|
||||
} else {
|
||||
if (mAudioCaptured) {
|
||||
clock_time = GetStreamClock();
|
||||
} else if (HasAudio() && !mAudioCompleted) {
|
||||
clock_time = GetAudioClock();
|
||||
clock_time = mStreamSink->GetPosition(&t);
|
||||
} else {
|
||||
t = TimeStamp::Now();
|
||||
// Audio is disabled on this system. Sync to the system clock.
|
||||
clock_time = GetVideoStreamPosition(t);
|
||||
clock_time = mAudioSink->GetPosition(&t);
|
||||
}
|
||||
NS_ASSERTION(GetMediaTime() <= clock_time, "Clock should go forwards.");
|
||||
}
|
||||
|
@ -2915,10 +2881,7 @@ void MediaDecoderStateMachine::SetPlayStartTime(const TimeStamp& aTimeStamp)
|
|||
mPlayStartTime = aTimeStamp;
|
||||
|
||||
mAudioSink->SetPlaying(!mPlayStartTime.IsNull());
|
||||
// Have DecodedStream remember the playing state so it doesn't need to
|
||||
// ask MDSM about IsPlaying(). Note we have to do this even before capture
|
||||
// happens since capture could happen in the middle of playback.
|
||||
mDecodedStream->SetPlaying(!mPlayStartTime.IsNull());
|
||||
mStreamSink->SetPlaying(!mPlayStartTime.IsNull());
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder()
|
||||
|
@ -2995,17 +2958,6 @@ MediaDecoderStateMachine::LogicalPlaybackRateChanged()
|
|||
return;
|
||||
}
|
||||
|
||||
// AudioStream will handle playback rate change when we have audio.
|
||||
// Do nothing while we are not playing. Change in playback rate will
|
||||
// take effect next time we start playing again.
|
||||
if (!HasAudio() && IsPlaying()) {
|
||||
// Remember how much time we've spent in playing the media
|
||||
// for playback rate will change from now on.
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
mPlayDuration = GetVideoStreamPosition(now);
|
||||
SetPlayStartTime(now);
|
||||
}
|
||||
|
||||
mPlaybackRate = mLogicalPlaybackRate;
|
||||
mAudioSink->SetPlaybackRate(mPlaybackRate);
|
||||
|
||||
|
@ -3025,19 +2977,6 @@ bool MediaDecoderStateMachine::IsShutdown()
|
|||
return mIsShutdown;
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::QueueMetadata(const TimeUnit& aPublishTime,
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags)
|
||||
{
|
||||
MOZ_ASSERT(OnDecodeTaskQueue());
|
||||
AssertCurrentThreadInMonitor();
|
||||
TimedMetadata* metadata = new TimedMetadata;
|
||||
metadata->mPublishTime = aPublishTime;
|
||||
metadata->mInfo = aInfo.forget();
|
||||
metadata->mTags = aTags.forget();
|
||||
mMetadataManager.QueueMetadata(metadata);
|
||||
}
|
||||
|
||||
int64_t
|
||||
MediaDecoderStateMachine::AudioEndTime() const
|
||||
{
|
||||
|
@ -3046,7 +2985,7 @@ MediaDecoderStateMachine::AudioEndTime() const
|
|||
if (mAudioSink->IsStarted()) {
|
||||
return mAudioSink->GetEndTime(TrackInfo::kAudioTrack);
|
||||
} else if (mAudioCaptured) {
|
||||
return mDecodedStream->AudioEndTime();
|
||||
return mStreamSink->GetEndTime(TrackInfo::kAudioTrack);
|
||||
}
|
||||
MOZ_ASSERT(!HasAudio());
|
||||
return -1;
|
||||
|
@ -3059,7 +2998,6 @@ void MediaDecoderStateMachine::OnAudioSinkComplete()
|
|||
MOZ_ASSERT(!mAudioCaptured, "Should be disconnected when capturing audio.");
|
||||
|
||||
mAudioSinkPromise.Complete();
|
||||
ResyncAudioClock();
|
||||
mAudioCompleted = true;
|
||||
}
|
||||
|
||||
|
@ -3070,7 +3008,6 @@ void MediaDecoderStateMachine::OnAudioSinkError()
|
|||
MOZ_ASSERT(!mAudioCaptured, "Should be disconnected when capturing audio.");
|
||||
|
||||
mAudioSinkPromise.Complete();
|
||||
ResyncAudioClock();
|
||||
mAudioCompleted = true;
|
||||
|
||||
// Make the best effort to continue playback when there is video.
|
||||
|
@ -3165,7 +3102,7 @@ void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
|
|||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
DECODER_LOG("AddOutputStream aStream=%p!", aStream);
|
||||
mDecodedStream->AddOutput(aStream, aFinishWhenEnded);
|
||||
mStreamSink->AddOutput(aStream, aFinishWhenEnded);
|
||||
DispatchAudioCaptured();
|
||||
}
|
||||
|
||||
|
@ -3173,8 +3110,8 @@ void MediaDecoderStateMachine::RemoveOutputStream(MediaStream* aStream)
|
|||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
DECODER_LOG("RemoveOutputStream=%p!", aStream);
|
||||
mDecodedStream->RemoveOutput(aStream);
|
||||
if (!mDecodedStream->HasConsumers()) {
|
||||
mStreamSink->RemoveOutput(aStream);
|
||||
if (!mStreamSink->HasConsumers()) {
|
||||
DispatchAudioUncaptured();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,6 @@ hardware (via AudioStream).
|
|||
#include "MediaEventSource.h"
|
||||
#include "MediaMetadataManager.h"
|
||||
#include "MediaTimer.h"
|
||||
#include "DecodedStream.h"
|
||||
#include "ImageContainer.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -104,6 +103,7 @@ class MediaSink;
|
|||
}
|
||||
|
||||
class AudioSegment;
|
||||
class DecodedStream;
|
||||
class TaskQueue;
|
||||
|
||||
extern PRLogModuleInfo* gMediaDecoderLog;
|
||||
|
@ -157,6 +157,10 @@ public:
|
|||
// Set/Unset dormant state.
|
||||
void SetDormant(bool aDormant);
|
||||
|
||||
TimedMetadataEventSource& TimedMetadataEvent() {
|
||||
return mMetadataManager.TimedMetadataEvent();
|
||||
}
|
||||
|
||||
private:
|
||||
// Initialization that needs to happen on the task queue. This is the first
|
||||
// task that gets run on the task queue, and is dispatched from the MDSM
|
||||
|
@ -169,13 +173,7 @@ private:
|
|||
void Shutdown();
|
||||
public:
|
||||
|
||||
void DispatchShutdown()
|
||||
{
|
||||
mDecodedStream->Shutdown();
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::Shutdown);
|
||||
OwnerThread()->Dispatch(runnable.forget());
|
||||
}
|
||||
void DispatchShutdown();
|
||||
|
||||
void FinishShutdown();
|
||||
|
||||
|
@ -333,10 +331,6 @@ public:
|
|||
// shutting down. The decoder monitor must be held while calling this.
|
||||
bool IsShutdown();
|
||||
|
||||
void QueueMetadata(const media::TimeUnit& aPublishTime,
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags);
|
||||
|
||||
// Returns true if we're currently playing. The decoder monitor must
|
||||
// be held.
|
||||
bool IsPlaying() const;
|
||||
|
@ -457,22 +451,6 @@ protected:
|
|||
// parties.
|
||||
void UpdateNextFrameStatus();
|
||||
|
||||
// Called when AudioSink reaches the end. |mPlayStartTime| and
|
||||
// |mPlayDuration| are updated to provide a good base for calculating video
|
||||
// stream time.
|
||||
void ResyncAudioClock();
|
||||
|
||||
// Returns the audio clock, if we have audio, or -1 if we don't.
|
||||
// Called on the state machine thread.
|
||||
int64_t GetAudioClock() const;
|
||||
|
||||
int64_t GetStreamClock() const;
|
||||
|
||||
// Get the video stream position, taking the |playbackRate| change into
|
||||
// account. This is a position in the media, not the duration of the playback
|
||||
// so far. Returns the position for the given time aTimeStamp.
|
||||
int64_t GetVideoStreamPosition(TimeStamp aTimeStamp) const;
|
||||
|
||||
// Return the current time, either the audio clock if available (if the media
|
||||
// has audio, and the playback is possible), or a clock for the video.
|
||||
// Called on the state machine thread.
|
||||
|
@ -1287,7 +1265,7 @@ private:
|
|||
// Only written on the main thread while holding the monitor. Therefore it
|
||||
// can be read on any thread while holding the monitor, or on the main thread
|
||||
// without holding the monitor.
|
||||
nsRefPtr<DecodedStream> mDecodedStream;
|
||||
nsRefPtr<DecodedStream> mStreamSink;
|
||||
|
||||
// Media data resource from the decoder.
|
||||
nsRefPtr<MediaResource> mResource;
|
||||
|
|
|
@ -1378,6 +1378,8 @@ MediaManager::IsInMediaThread()
|
|||
// NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
|
||||
// thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
|
||||
// from MediaManager thread.
|
||||
|
||||
// Guaranteed never to return nullptr.
|
||||
/* static */ MediaManager*
|
||||
MediaManager::Get() {
|
||||
if (!sSingleton) {
|
||||
|
@ -2066,7 +2068,14 @@ MediaManager::EnumerateDevices(nsPIDOMWindow* aWindow,
|
|||
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
|
||||
uint64_t windowId = aWindow->WindowID();
|
||||
|
||||
AddWindowID(windowId);
|
||||
StreamListeners* listeners = AddWindowID(windowId);
|
||||
|
||||
// Create a disabled listener to act as a placeholder
|
||||
nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
|
||||
new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowId);
|
||||
|
||||
// No need for locking because we always do this in the main thread.
|
||||
listeners->AppendElement(listener);
|
||||
|
||||
bool fake = Preferences::GetBool("media.navigator.streams.fake");
|
||||
|
||||
|
@ -2074,11 +2083,15 @@ MediaManager::EnumerateDevices(nsPIDOMWindow* aWindow,
|
|||
dom::MediaSourceEnum::Camera,
|
||||
dom::MediaSourceEnum::Microphone,
|
||||
fake);
|
||||
p->Then([onSuccess](SourceSet*& aDevices) mutable {
|
||||
p->Then([onSuccess, windowId, listener](SourceSet*& aDevices) mutable {
|
||||
ScopedDeletePtr<SourceSet> devices(aDevices); // grab result
|
||||
nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
|
||||
mgr->RemoveFromWindowList(windowId, listener);
|
||||
nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
|
||||
onSuccess->OnSuccess(array);
|
||||
}, [onFailure](MediaStreamError& reason) mutable {
|
||||
}, [onFailure, windowId, listener](MediaStreamError& reason) mutable {
|
||||
nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
|
||||
mgr->RemoveFromWindowList(windowId, listener);
|
||||
onFailure->OnError(&reason);
|
||||
});
|
||||
return NS_OK;
|
||||
|
|
|
@ -7,19 +7,41 @@
|
|||
#if !defined(MediaMetadataManager_h__)
|
||||
#define MediaMetadataManager_h__
|
||||
|
||||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
|
||||
#include "nsAutoPtr.h"
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "MediaEventSource.h"
|
||||
#include "TimeUnits.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class TimedMetadata;
|
||||
typedef MediaEventProducer<TimedMetadata, ListenerMode::Exclusive>
|
||||
TimedMetadataEventProducer;
|
||||
typedef MediaEventSource<TimedMetadata, ListenerMode::Exclusive>
|
||||
TimedMetadataEventSource;
|
||||
|
||||
// A struct that contains the metadata of a media, and the time at which those
|
||||
// metadata should start to be reported.
|
||||
class TimedMetadata : public LinkedListElement<TimedMetadata> {
|
||||
public:
|
||||
TimedMetadata(const media::TimeUnit& aPublishTime,
|
||||
nsAutoPtr<MetadataTags>&& aTags,
|
||||
nsAutoPtr<MediaInfo>&& aInfo)
|
||||
: mPublishTime(aPublishTime)
|
||||
, mTags(Move(aTags))
|
||||
, mInfo(Move(aInfo)) {}
|
||||
|
||||
// Define our move constructor because we don't want to move the members of
|
||||
// LinkedListElement to change the list.
|
||||
TimedMetadata(TimedMetadata&& aOther)
|
||||
: mPublishTime(aOther.mPublishTime)
|
||||
, mTags(Move(aOther.mTags))
|
||||
, mInfo(Move(aOther.mInfo)) {}
|
||||
|
||||
// The time, in microseconds, at which those metadata should be available.
|
||||
media::TimeUnit mPublishTime;
|
||||
// The metadata. The ownership is transfered to the element when dispatching to
|
||||
|
@ -33,8 +55,7 @@ public:
|
|||
|
||||
// This class encapsulate the logic to give the metadata from the reader to
|
||||
// the content, at the right time.
|
||||
class MediaMetadataManager
|
||||
{
|
||||
class MediaMetadataManager {
|
||||
public:
|
||||
~MediaMetadataManager() {
|
||||
TimedMetadata* element;
|
||||
|
@ -43,30 +64,41 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void QueueMetadata(TimedMetadata* aMetadata) {
|
||||
mMetadataQueue.insertBack(aMetadata);
|
||||
// Connect to an event source to receive TimedMetadata events.
|
||||
void Connect(TimedMetadataEventSource& aEvent, AbstractThread* aThread) {
|
||||
mListener = aEvent.Connect(
|
||||
aThread, this, &MediaMetadataManager::OnMetadataQueued);
|
||||
}
|
||||
|
||||
void DispatchMetadataIfNeeded(AbstractMediaDecoder* aDecoder, const media::TimeUnit& aCurrentTime) {
|
||||
// Stop receiving TimedMetadata events.
|
||||
void Disconnect() {
|
||||
mListener.Disconnect();
|
||||
}
|
||||
|
||||
// Return an event source through which we will send TimedMetadata events
|
||||
// when playback position reaches the publish time.
|
||||
TimedMetadataEventSource& TimedMetadataEvent() {
|
||||
return mTimedMetadataEvent;
|
||||
}
|
||||
|
||||
void DispatchMetadataIfNeeded(const media::TimeUnit& aCurrentTime) {
|
||||
TimedMetadata* metadata = mMetadataQueue.getFirst();
|
||||
while (metadata && aCurrentTime >= metadata->mPublishTime) {
|
||||
// Remove all media tracks from the list first.
|
||||
nsCOMPtr<nsIRunnable> removeTracksEvent =
|
||||
new RemoveMediaTracksEventRunner(aDecoder);
|
||||
NS_DispatchToMainThread(removeTracksEvent);
|
||||
|
||||
nsCOMPtr<nsIRunnable> metadataUpdatedEvent =
|
||||
new MetadataUpdatedEventRunner(aDecoder,
|
||||
metadata->mInfo,
|
||||
metadata->mTags);
|
||||
NS_DispatchToMainThread(metadataUpdatedEvent);
|
||||
// Our listener will figure out what to do with TimedMetadata.
|
||||
mTimedMetadataEvent.Notify(Move(*metadata));
|
||||
delete mMetadataQueue.popFirst();
|
||||
metadata = mMetadataQueue.getFirst();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void OnMetadataQueued(TimedMetadata&& aMetadata) {
|
||||
mMetadataQueue.insertBack(new TimedMetadata(Move(aMetadata)));
|
||||
}
|
||||
|
||||
LinkedList<TimedMetadata> mMetadataQueue;
|
||||
MediaEventListener mListener;
|
||||
TimedMetadataEventProducer mTimedMetadataEvent;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -541,7 +541,7 @@ private:
|
|||
|
||||
// Bind this Track Union Stream with Source Media.
|
||||
mInputPort = mTrackUnionStream->AllocateInputPort(mRecorder->GetSourceMediaStream(),
|
||||
MediaInputPort::FLAG_BLOCK_OUTPUT);
|
||||
0);
|
||||
|
||||
DOMMediaStream* domStream = mRecorder->Stream();
|
||||
if (domStream) {
|
||||
|
@ -792,7 +792,7 @@ MediaRecorder::MediaRecorder(AudioNode& aSrcAudioNode,
|
|||
AudioNodeStream* ns = aSrcAudioNode.GetStream();
|
||||
if (ns) {
|
||||
mInputPort = mPipeStream->AllocateInputPort(aSrcAudioNode.GetStream(),
|
||||
MediaInputPort::FLAG_BLOCK_INPUT,
|
||||
0,
|
||||
0,
|
||||
aSrcOutput);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,11 @@
|
|||
// For HTTP seeking, if number of bytes needing to be
|
||||
// seeked forward is less than this value then a read is
|
||||
// done rather than a byte range request.
|
||||
static const int64_t SEEK_VS_READ_THRESHOLD = 32*1024;
|
||||
//
|
||||
// If we assume a 100Mbit connection, and assume reissuing an HTTP seek causes
|
||||
// a delay of 200ms, then in that 200ms we could have simply read ahead 2MB. So
|
||||
// setting SEEK_VS_READ_THRESHOLD to 1MB sounds reasonable.
|
||||
static const int64_t SEEK_VS_READ_THRESHOLD = 1 * 1024 * 1024;
|
||||
|
||||
static const uint32_t HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE = 416;
|
||||
|
||||
|
|
|
@ -82,13 +82,12 @@ MediaStreamGraphImpl::FinishStream(MediaStream* aStream)
|
|||
SetStreamOrderDirty();
|
||||
}
|
||||
|
||||
static const GraphTime START_TIME_DELAYED = -1;
|
||||
|
||||
void
|
||||
MediaStreamGraphImpl::AddStreamGraphThread(MediaStream* aStream)
|
||||
{
|
||||
aStream->mBufferStartTime = mProcessedTime;
|
||||
// Check if we're adding a stream to a suspended context, in which case, we
|
||||
// add it to mSuspendedStreams, and delay setting mBufferStartTime
|
||||
// add it to mSuspendedStreams
|
||||
bool contextSuspended = false;
|
||||
if (aStream->AsAudioNodeStream()) {
|
||||
for (uint32_t i = 0; i < mSuspendedStreams.Length(); i++) {
|
||||
|
@ -99,11 +98,9 @@ MediaStreamGraphImpl::AddStreamGraphThread(MediaStream* aStream)
|
|||
}
|
||||
|
||||
if (contextSuspended) {
|
||||
aStream->mBufferStartTime = START_TIME_DELAYED;
|
||||
mSuspendedStreams.AppendElement(aStream);
|
||||
STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to the graph, in the suspended stream array", aStream));
|
||||
} else {
|
||||
aStream->mBufferStartTime = mProcessedTime;
|
||||
mStreams.AppendElement(aStream);
|
||||
STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to the graph", aStream));
|
||||
}
|
||||
|
@ -359,61 +356,52 @@ void
|
|||
MediaStreamGraphImpl::UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime,
|
||||
GraphTime aNextCurrentTime)
|
||||
{
|
||||
nsTArray<MediaStream*>* runningAndSuspendedPair[2];
|
||||
runningAndSuspendedPair[0] = &mStreams;
|
||||
runningAndSuspendedPair[1] = &mSuspendedStreams;
|
||||
|
||||
for (uint32_t array = 0; array < 2; array++) {
|
||||
for (uint32_t i = 0; i < runningAndSuspendedPair[array]->Length(); ++i) {
|
||||
MediaStream* stream = (*runningAndSuspendedPair[array])[i];
|
||||
|
||||
// Calculate blocked time and fire Blocked/Unblocked events
|
||||
GraphTime blockedTime = 0;
|
||||
GraphTime t = aPrevCurrentTime;
|
||||
// include |nextCurrentTime| to ensure NotifyBlockingChanged() is called
|
||||
// before NotifyEvent(this, EVENT_FINISHED) when |nextCurrentTime ==
|
||||
// stream end time|
|
||||
while (t <= aNextCurrentTime) {
|
||||
GraphTime end;
|
||||
bool blocked = stream->mBlocked.GetAt(t, &end);
|
||||
if (blocked) {
|
||||
blockedTime += std::min(end, aNextCurrentTime) - t;
|
||||
}
|
||||
if (blocked != stream->mNotifiedBlocked) {
|
||||
for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
|
||||
MediaStreamListener* l = stream->mListeners[j];
|
||||
l->NotifyBlockingChanged(this, blocked
|
||||
? MediaStreamListener::BLOCKED
|
||||
: MediaStreamListener::UNBLOCKED);
|
||||
}
|
||||
stream->mNotifiedBlocked = blocked;
|
||||
}
|
||||
t = end;
|
||||
for (MediaStream* stream : AllStreams()) {
|
||||
// Calculate blocked time and fire Blocked/Unblocked events
|
||||
GraphTime blockedTime = 0;
|
||||
GraphTime t = aPrevCurrentTime;
|
||||
// include |nextCurrentTime| to ensure NotifyBlockingChanged() is called
|
||||
// before NotifyEvent(this, EVENT_FINISHED) when |nextCurrentTime ==
|
||||
// stream end time|
|
||||
while (t <= aNextCurrentTime) {
|
||||
GraphTime end;
|
||||
bool blocked = stream->mBlocked.GetAt(t, &end);
|
||||
if (blocked) {
|
||||
blockedTime += std::min(end, aNextCurrentTime) - t;
|
||||
}
|
||||
|
||||
stream->AdvanceTimeVaryingValuesToCurrentTime(aNextCurrentTime,
|
||||
blockedTime);
|
||||
// Advance mBlocked last so that AdvanceTimeVaryingValuesToCurrentTime
|
||||
// can rely on the value of mBlocked.
|
||||
stream->mBlocked.AdvanceCurrentTime(aNextCurrentTime);
|
||||
|
||||
if (runningAndSuspendedPair[array] == &mStreams) {
|
||||
bool streamHasOutput = blockedTime < aNextCurrentTime - aPrevCurrentTime;
|
||||
NS_ASSERTION(!streamHasOutput || !stream->mNotifiedFinished,
|
||||
"Shouldn't have already notified of finish *and* have output!");
|
||||
|
||||
if (streamHasOutput) {
|
||||
StreamNotifyOutput(stream);
|
||||
}
|
||||
|
||||
if (stream->mFinished && !stream->mNotifiedFinished) {
|
||||
StreamReadyToFinish(stream);
|
||||
if (blocked != stream->mNotifiedBlocked) {
|
||||
for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
|
||||
MediaStreamListener* l = stream->mListeners[j];
|
||||
l->NotifyBlockingChanged(this, blocked
|
||||
? MediaStreamListener::BLOCKED
|
||||
: MediaStreamListener::UNBLOCKED);
|
||||
}
|
||||
stream->mNotifiedBlocked = blocked;
|
||||
}
|
||||
STREAM_LOG(LogLevel::Verbose,
|
||||
("MediaStream %p bufferStartTime=%f blockedTime=%f", stream,
|
||||
MediaTimeToSeconds(stream->mBufferStartTime),
|
||||
MediaTimeToSeconds(blockedTime)));
|
||||
t = end;
|
||||
}
|
||||
|
||||
stream->AdvanceTimeVaryingValuesToCurrentTime(aNextCurrentTime,
|
||||
blockedTime);
|
||||
// Advance mBlocked last so that AdvanceTimeVaryingValuesToCurrentTime
|
||||
// can rely on the value of mBlocked.
|
||||
stream->mBlocked.AdvanceCurrentTime(aNextCurrentTime);
|
||||
|
||||
STREAM_LOG(LogLevel::Verbose,
|
||||
("MediaStream %p bufferStartTime=%f blockedTime=%f", stream,
|
||||
MediaTimeToSeconds(stream->mBufferStartTime),
|
||||
MediaTimeToSeconds(blockedTime)));
|
||||
|
||||
bool streamHasOutput = blockedTime < aNextCurrentTime - aPrevCurrentTime;
|
||||
NS_ASSERTION(!streamHasOutput || !stream->mNotifiedFinished,
|
||||
"Shouldn't have already notified of finish *and* have output!");
|
||||
|
||||
if (streamHasOutput) {
|
||||
StreamNotifyOutput(stream);
|
||||
}
|
||||
|
||||
if (stream->mFinished && !stream->mNotifiedFinished) {
|
||||
StreamReadyToFinish(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -742,26 +730,19 @@ MediaStreamGraphImpl::RecomputeBlocking(GraphTime aEndBlockingDecisions)
|
|||
{
|
||||
STREAM_LOG(LogLevel::Verbose, ("Media graph %p computing blocking for time %f",
|
||||
this, MediaTimeToSeconds(mStateComputedTime)));
|
||||
nsTArray<MediaStream*>* runningAndSuspendedPair[2];
|
||||
runningAndSuspendedPair[0] = &mStreams;
|
||||
runningAndSuspendedPair[1] = &mSuspendedStreams;
|
||||
for (MediaStream* stream : AllStreams()) {
|
||||
if (!stream->mInBlockingSet) {
|
||||
// Compute a partition of the streams containing 'stream' such that we
|
||||
// can
|
||||
// compute the blocking status of each subset independently.
|
||||
nsAutoTArray<MediaStream*, 10> streamSet;
|
||||
AddBlockingRelatedStreamsToSet(&streamSet, stream);
|
||||
|
||||
for (uint32_t array = 0; array < 2; array++) {
|
||||
for (uint32_t i = 0; i < (*runningAndSuspendedPair[array]).Length(); ++i) {
|
||||
MediaStream* stream = (*runningAndSuspendedPair[array])[i];
|
||||
if (!stream->mInBlockingSet) {
|
||||
// Compute a partition of the streams containing 'stream' such that we
|
||||
// can
|
||||
// compute the blocking status of each subset independently.
|
||||
nsAutoTArray<MediaStream*, 10> streamSet;
|
||||
AddBlockingRelatedStreamsToSet(&streamSet, stream);
|
||||
|
||||
GraphTime end;
|
||||
for (GraphTime t = mStateComputedTime;
|
||||
t < aEndBlockingDecisions; t = end) {
|
||||
end = GRAPH_TIME_MAX;
|
||||
RecomputeBlockingAt(streamSet, t, aEndBlockingDecisions, &end);
|
||||
}
|
||||
GraphTime end;
|
||||
for (GraphTime t = mStateComputedTime;
|
||||
t < aEndBlockingDecisions; t = end) {
|
||||
end = GRAPH_TIME_MAX;
|
||||
RecomputeBlockingAt(streamSet, t, aEndBlockingDecisions, &end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -865,6 +846,12 @@ MediaStreamGraphImpl::RecomputeBlockingAt(const nsTArray<MediaStream*>& aStreams
|
|||
continue;
|
||||
}
|
||||
|
||||
if (StreamSuspended(stream)) {
|
||||
STREAM_LOG(LogLevel::Verbose, ("MediaStream %p is blocked due to being suspended", stream));
|
||||
MarkStreamBlocking(stream);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool underrun = WillUnderrun(stream, aTime, aEndBlockingDecisions, aEnd);
|
||||
if (underrun) {
|
||||
// We'll block indefinitely
|
||||
|
@ -1263,9 +1250,9 @@ MediaStreamGraphImpl::PrepareUpdatesToMainThreadState(bool aFinalUpdate)
|
|||
// We don't want to frequently update the main thread about timing update
|
||||
// when we are not running in realtime.
|
||||
if (aFinalUpdate || ShouldUpdateMainThread()) {
|
||||
mStreamUpdates.SetCapacity(mStreamUpdates.Length() + mStreams.Length());
|
||||
for (uint32_t i = 0; i < mStreams.Length(); ++i) {
|
||||
MediaStream* stream = mStreams[i];
|
||||
mStreamUpdates.SetCapacity(mStreamUpdates.Length() + mStreams.Length() +
|
||||
mSuspendedStreams.Length());
|
||||
for (MediaStream* stream : AllStreams()) {
|
||||
if (!stream->MainThreadNeedsUpdates()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -1329,9 +1316,8 @@ MediaStreamGraphImpl::ProduceDataForStreamsBlockByBlock(uint32_t aStreamIndex,
|
|||
bool
|
||||
MediaStreamGraphImpl::AllFinishedStreamsNotified()
|
||||
{
|
||||
for (uint32_t i = 0; i < mStreams.Length(); ++i) {
|
||||
MediaStream* s = mStreams[i];
|
||||
if (s->mFinished && !s->mNotifiedFinished) {
|
||||
for (MediaStream* stream : AllStreams()) {
|
||||
if (stream->mFinished && !stream->mNotifiedFinished) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1476,8 +1462,8 @@ MediaStreamGraphImpl::OneIteration(GraphTime aStateEnd)
|
|||
if (mNeedsMemoryReport) {
|
||||
mNeedsMemoryReport = false;
|
||||
|
||||
for (uint32_t i = 0; i < mStreams.Length(); ++i) {
|
||||
AudioNodeStream* stream = mStreams[i]->AsAudioNodeStream();
|
||||
for (MediaStream* s : AllStreams()) {
|
||||
AudioNodeStream* stream = s->AsAudioNodeStream();
|
||||
if (stream) {
|
||||
AudioNodeSizes usage;
|
||||
stream->SizeOfAudioNodesIncludingThis(MallocSizeOf, usage);
|
||||
|
@ -1596,8 +1582,8 @@ public:
|
|||
// delete it.
|
||||
NS_ASSERTION(mGraph->mForceShutDown || !mGraph->mRealtime,
|
||||
"Not in forced shutdown?");
|
||||
for (uint32_t i = 0; i < mGraph->mStreams.Length(); ++i) {
|
||||
DOMMediaStream* s = mGraph->mStreams[i]->GetWrapper();
|
||||
for (MediaStream* stream : mGraph->AllStreams()) {
|
||||
DOMMediaStream* s = stream->GetWrapper();
|
||||
if (s) {
|
||||
s->NotifyMediaStreamGraphShutdown();
|
||||
}
|
||||
|
@ -3192,19 +3178,12 @@ void
|
|||
MediaStreamGraphImpl::ResetVisitedStreamState()
|
||||
{
|
||||
// Reset the visited/consumed/blocked state of the streams.
|
||||
nsTArray<MediaStream*>* runningAndSuspendedPair[2];
|
||||
runningAndSuspendedPair[0] = &mStreams;
|
||||
runningAndSuspendedPair[1] = &mSuspendedStreams;
|
||||
|
||||
for (uint32_t array = 0; array < 2; array++) {
|
||||
for (uint32_t i = 0; i < runningAndSuspendedPair[array]->Length(); ++i) {
|
||||
ProcessedMediaStream* ps =
|
||||
(*runningAndSuspendedPair[array])[i]->AsProcessedStream();
|
||||
if (ps) {
|
||||
ps->mCycleMarker = NOT_VISITED;
|
||||
ps->mIsConsumed = false;
|
||||
ps->mInBlockingSet = false;
|
||||
}
|
||||
for (MediaStream* stream : AllStreams()) {
|
||||
ProcessedMediaStream* ps = stream->AsProcessedStream();
|
||||
if (ps) {
|
||||
ps->mCycleMarker = NOT_VISITED;
|
||||
ps->mIsConsumed = false;
|
||||
ps->mInBlockingSet = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3250,13 +3229,6 @@ MediaStreamGraphImpl::MoveStreams(AudioContextOperation aAudioContextOperation,
|
|||
to.AppendElement(stream);
|
||||
}
|
||||
|
||||
// If streams got added during a period where an AudioContext was suspended,
|
||||
// set their buffer start time to the appropriate value now:
|
||||
if (aAudioContextOperation == AudioContextOperation::Resume &&
|
||||
stream->mBufferStartTime == START_TIME_DELAYED) {
|
||||
stream->mBufferStartTime = mProcessedTime;
|
||||
}
|
||||
|
||||
stream->remove();
|
||||
}
|
||||
STREAM_LOG(LogLevel::Debug, ("Moving streams between suspended and running"
|
||||
|
|
|
@ -528,6 +528,56 @@ public:
|
|||
already_AddRefed<MediaInputPort>
|
||||
ConnectToCaptureStream(uint64_t aWindowId, MediaStream* aMediaStream);
|
||||
|
||||
class StreamSet {
|
||||
public:
|
||||
class iterator {
|
||||
public:
|
||||
explicit iterator(MediaStreamGraphImpl& aGraph)
|
||||
: mGraph(&aGraph), mArrayNum(-1), mArrayIndex(0)
|
||||
{
|
||||
++(*this);
|
||||
}
|
||||
iterator() : mGraph(nullptr), mArrayNum(2), mArrayIndex(0) {}
|
||||
MediaStream* operator*()
|
||||
{
|
||||
return Array()->ElementAt(mArrayIndex);
|
||||
}
|
||||
iterator operator++()
|
||||
{
|
||||
++mArrayIndex;
|
||||
while (mArrayNum < 2 &&
|
||||
(mArrayNum < 0 || mArrayIndex >= Array()->Length())) {
|
||||
++mArrayNum;
|
||||
mArrayIndex = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
bool operator==(const iterator& aOther) const
|
||||
{
|
||||
return mArrayNum == aOther.mArrayNum && mArrayIndex == aOther.mArrayIndex;
|
||||
}
|
||||
bool operator!=(const iterator& aOther) const
|
||||
{
|
||||
return !(*this == aOther);
|
||||
}
|
||||
private:
|
||||
nsTArray<MediaStream*>* Array()
|
||||
{
|
||||
return mArrayNum == 0 ? &mGraph->mStreams : &mGraph->mSuspendedStreams;
|
||||
}
|
||||
MediaStreamGraphImpl* mGraph;
|
||||
int mArrayNum;
|
||||
uint32_t mArrayIndex;
|
||||
};
|
||||
|
||||
explicit StreamSet(MediaStreamGraphImpl& aGraph) : mGraph(aGraph) {}
|
||||
iterator begin() { return iterator(mGraph); }
|
||||
iterator end() { return iterator(); }
|
||||
private:
|
||||
MediaStreamGraphImpl& mGraph;
|
||||
};
|
||||
StreamSet AllStreams() { return StreamSet(*this); }
|
||||
|
||||
// Data members
|
||||
//
|
||||
/**
|
||||
|
|
|
@ -69,7 +69,7 @@ bool XiphExtradataToHeaders(nsTArray<unsigned char*>& aHeaders,
|
|||
// Since we know aAvailable >= total + headerLen, this can't overflow.
|
||||
total += headerLen;
|
||||
}
|
||||
aHeaderLens.AppendElement(aAvailable);
|
||||
aHeaderLens.AppendElement(aAvailable - total);
|
||||
for (int i = 0; i < nHeaders; i++) {
|
||||
aHeaders.AppendElement(aData);
|
||||
aData += aHeaderLens[i];
|
||||
|
|
|
@ -34,9 +34,9 @@ AudioSinkWrapper::SetPlaybackParams(const PlaybackParams& aParams)
|
|||
{
|
||||
AssertOwnerThread();
|
||||
if (mAudioSink) {
|
||||
mAudioSink->SetVolume(aParams.volume);
|
||||
mAudioSink->SetPlaybackRate(aParams.playbackRate);
|
||||
mAudioSink->SetPreservesPitch(aParams.preservesPitch);
|
||||
mAudioSink->SetVolume(aParams.mVolume);
|
||||
mAudioSink->SetPlaybackRate(aParams.mPlaybackRate);
|
||||
mAudioSink->SetPreservesPitch(aParams.mPreservesPitch);
|
||||
}
|
||||
mParams = aParams;
|
||||
}
|
||||
|
@ -64,11 +64,41 @@ AudioSinkWrapper::GetEndTime(TrackType aType) const
|
|||
}
|
||||
|
||||
int64_t
|
||||
AudioSinkWrapper::GetPosition() const
|
||||
AudioSinkWrapper::GetVideoPosition(TimeStamp aNow) const
|
||||
{
|
||||
AssertOwnerThread();
|
||||
MOZ_ASSERT(!mPlayStartTime.IsNull());
|
||||
// Time elapsed since we started playing.
|
||||
int64_t delta = (aNow - mPlayStartTime).ToMicroseconds();
|
||||
// Take playback rate into account.
|
||||
return mPlayDuration + delta * mParams.mPlaybackRate;
|
||||
}
|
||||
|
||||
int64_t
|
||||
AudioSinkWrapper::GetPosition(TimeStamp* aTimeStamp) const
|
||||
{
|
||||
AssertOwnerThread();
|
||||
MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");
|
||||
return mAudioSink->GetPosition();
|
||||
|
||||
int64_t pos = -1;
|
||||
TimeStamp t = TimeStamp::Now();
|
||||
|
||||
if (!mAudioEnded) {
|
||||
// Rely on the audio sink to report playback position when it is not ended.
|
||||
pos = mAudioSink->GetPosition();
|
||||
} else if (!mPlayStartTime.IsNull()) {
|
||||
// Calculate playback position using system clock if we are still playing.
|
||||
pos = GetVideoPosition(t);
|
||||
} else {
|
||||
// Return how long we've played if we are not playing.
|
||||
pos = mPlayDuration;
|
||||
}
|
||||
|
||||
if (aTimeStamp) {
|
||||
*aTimeStamp = t;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -82,7 +112,7 @@ void
|
|||
AudioSinkWrapper::SetVolume(double aVolume)
|
||||
{
|
||||
AssertOwnerThread();
|
||||
mParams.volume = aVolume;
|
||||
mParams.mVolume = aVolume;
|
||||
if (mAudioSink) {
|
||||
mAudioSink->SetVolume(aVolume);
|
||||
}
|
||||
|
@ -92,17 +122,26 @@ void
|
|||
AudioSinkWrapper::SetPlaybackRate(double aPlaybackRate)
|
||||
{
|
||||
AssertOwnerThread();
|
||||
mParams.playbackRate = aPlaybackRate;
|
||||
if (mAudioSink) {
|
||||
mParams.mPlaybackRate = aPlaybackRate;
|
||||
if (!mAudioEnded) {
|
||||
// Pass the playback rate to the audio sink. The underlying AudioStream
|
||||
// will handle playback rate changes and report correct audio position.
|
||||
mAudioSink->SetPlaybackRate(aPlaybackRate);
|
||||
} else if (!mPlayStartTime.IsNull()) {
|
||||
// Adjust playback duration and start time when we are still playing.
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
mPlayDuration = GetVideoPosition(now);
|
||||
mPlayStartTime = now;
|
||||
}
|
||||
// Do nothing when not playing. Changes in playback rate will be taken into
|
||||
// account by GetVideoPosition().
|
||||
}
|
||||
|
||||
void
|
||||
AudioSinkWrapper::SetPreservesPitch(bool aPreservesPitch)
|
||||
{
|
||||
AssertOwnerThread();
|
||||
mParams.preservesPitch = aPreservesPitch;
|
||||
mParams.mPreservesPitch = aPreservesPitch;
|
||||
if (mAudioSink) {
|
||||
mAudioSink->SetPreservesPitch(aPreservesPitch);
|
||||
}
|
||||
|
@ -121,6 +160,17 @@ AudioSinkWrapper::SetPlaying(bool aPlaying)
|
|||
if (mAudioSink) {
|
||||
mAudioSink->SetPlaying(aPlaying);
|
||||
}
|
||||
|
||||
if (aPlaying) {
|
||||
MOZ_ASSERT(mPlayStartTime.IsNull());
|
||||
mPlayStartTime = TimeStamp::Now();
|
||||
} else {
|
||||
// Remember how long we've played.
|
||||
mPlayDuration = GetPosition();
|
||||
// mPlayStartTime must be updated later since GetPosition()
|
||||
// depends on the value of mPlayStartTime.
|
||||
mPlayStartTime = TimeStamp();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -130,10 +180,22 @@ AudioSinkWrapper::Start(int64_t aStartTime, const MediaInfo& aInfo)
|
|||
MOZ_ASSERT(!mIsStarted, "playback already started.");
|
||||
|
||||
mIsStarted = true;
|
||||
mPlayDuration = aStartTime;
|
||||
mPlayStartTime = TimeStamp::Now();
|
||||
|
||||
mAudioSink = mCreator->Create();
|
||||
mEndPromise = mAudioSink->Init();
|
||||
SetPlaybackParams(mParams);
|
||||
// no audio is equivalent to audio ended before video starts.
|
||||
mAudioEnded = !aInfo.HasAudio();
|
||||
|
||||
if (aInfo.HasAudio()) {
|
||||
mAudioSink = mCreator->Create();
|
||||
mEndPromise = mAudioSink->Init();
|
||||
SetPlaybackParams(mParams);
|
||||
|
||||
mAudioSinkPromise.Begin(mEndPromise->Then(
|
||||
mOwnerThread.get(), __func__, this,
|
||||
&AudioSinkWrapper::OnAudioEnded,
|
||||
&AudioSinkWrapper::OnAudioEnded));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -143,9 +205,14 @@ AudioSinkWrapper::Stop()
|
|||
MOZ_ASSERT(mIsStarted, "playback not started.");
|
||||
|
||||
mIsStarted = false;
|
||||
mAudioSink->Shutdown();
|
||||
mAudioSink = nullptr;
|
||||
mEndPromise = nullptr;
|
||||
mAudioEnded = true;
|
||||
|
||||
if (mAudioSink) {
|
||||
mAudioSinkPromise.DisconnectIfExists();
|
||||
mAudioSink->Shutdown();
|
||||
mAudioSink = nullptr;
|
||||
mEndPromise = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -155,6 +222,18 @@ AudioSinkWrapper::IsStarted() const
|
|||
return mIsStarted;
|
||||
}
|
||||
|
||||
void
|
||||
AudioSinkWrapper::OnAudioEnded()
|
||||
{
|
||||
AssertOwnerThread();
|
||||
mAudioSinkPromise.Complete();
|
||||
mPlayDuration = GetPosition();
|
||||
if (!mPlayStartTime.IsNull()) {
|
||||
mPlayStartTime = TimeStamp::Now();
|
||||
}
|
||||
mAudioEnded = true;
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/dom/AudioChannelBinding.h"
|
||||
#include "mozilla/nsRefPtr.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#include "MediaSink.h"
|
||||
|
@ -50,6 +51,9 @@ public:
|
|||
: mOwnerThread(aOwnerThread)
|
||||
, mCreator(new CreatorImpl<Function>(aFunc))
|
||||
, mIsStarted(false)
|
||||
// Give an insane value to facilitate debug if used before playback starts.
|
||||
, mPlayDuration(INT64_MAX)
|
||||
, mAudioEnded(true)
|
||||
{}
|
||||
|
||||
const PlaybackParams& GetPlaybackParams() const override;
|
||||
|
@ -57,7 +61,7 @@ public:
|
|||
|
||||
nsRefPtr<GenericPromise> OnEnded(TrackType aType) override;
|
||||
int64_t GetEndTime(TrackType aType) const override;
|
||||
int64_t GetPosition() const override;
|
||||
int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const override;
|
||||
bool HasUnplayedFrames(TrackType aType) const override;
|
||||
|
||||
void SetVolume(double aVolume) override;
|
||||
|
@ -78,6 +82,10 @@ private:
|
|||
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
||||
}
|
||||
|
||||
int64_t GetVideoPosition(TimeStamp aNow) const;
|
||||
|
||||
void OnAudioEnded();
|
||||
|
||||
const nsRefPtr<AbstractThread> mOwnerThread;
|
||||
UniquePtr<Creator> mCreator;
|
||||
nsRefPtr<AudioSink> mAudioSink;
|
||||
|
@ -85,6 +93,12 @@ private:
|
|||
|
||||
bool mIsStarted;
|
||||
PlaybackParams mParams;
|
||||
|
||||
TimeStamp mPlayStartTime;
|
||||
int64_t mPlayDuration;
|
||||
|
||||
bool mAudioEnded;
|
||||
MozPromiseRequestHolder<GenericPromise> mAudioSinkPromise;
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
* 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/CheckedInt.h"
|
||||
#include "mozilla/gfx/Point.h"
|
||||
|
||||
#include "AudioSegment.h"
|
||||
#include "DecodedStream.h"
|
||||
#include "MediaData.h"
|
||||
|
@ -250,10 +253,7 @@ OutputStreamData::Connect(MediaStream* aStream)
|
|||
MOZ_ASSERT(!mPort, "Already connected?");
|
||||
MOZ_ASSERT(!mStream->IsDestroyed(), "Can't connect a destroyed stream.");
|
||||
|
||||
// The output stream must stay in sync with the input stream, so if
|
||||
// either stream is blocked, we block the other.
|
||||
mPort = mStream->AllocateInputPort(aStream,
|
||||
MediaInputPort::FLAG_BLOCK_INPUT | MediaInputPort::FLAG_BLOCK_OUTPUT);
|
||||
mPort = mStream->AllocateInputPort(aStream, 0);
|
||||
// Unblock the output stream now. The input stream is responsible for
|
||||
// controlling blocking from now on.
|
||||
mStream->ChangeExplicitBlockerCount(-1);
|
||||
|
@ -358,7 +358,6 @@ DecodedStream::DecodedStream(AbstractThread* aOwnerThread,
|
|||
: mOwnerThread(aOwnerThread)
|
||||
, mShuttingDown(false)
|
||||
, mPlaying(false)
|
||||
, mVolume(1.0)
|
||||
, mSameOrigin(false)
|
||||
, mAudioQueue(aAudioQueue)
|
||||
, mVideoQueue(aVideoQueue)
|
||||
|
@ -370,21 +369,52 @@ DecodedStream::~DecodedStream()
|
|||
MOZ_ASSERT(mStartTime.isNothing(), "playback should've ended.");
|
||||
}
|
||||
|
||||
const media::MediaSink::PlaybackParams&
|
||||
DecodedStream::GetPlaybackParams() const
|
||||
{
|
||||
AssertOwnerThread();
|
||||
return mParams;
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStream::Shutdown()
|
||||
DecodedStream::SetPlaybackParams(const PlaybackParams& aParams)
|
||||
{
|
||||
AssertOwnerThread();
|
||||
mParams = aParams;
|
||||
}
|
||||
|
||||
nsRefPtr<GenericPromise>
|
||||
DecodedStream::OnEnded(TrackType aType)
|
||||
{
|
||||
AssertOwnerThread();
|
||||
MOZ_ASSERT(mStartTime.isSome());
|
||||
|
||||
if (aType == TrackInfo::kAudioTrack) {
|
||||
// TODO: we should return a promise which is resolved when the audio track
|
||||
// is finished. For now this promise is resolved when the whole stream is
|
||||
// finished.
|
||||
return mFinishPromise;
|
||||
}
|
||||
// TODO: handle video track.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStream::BeginShutdown()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mShuttingDown = true;
|
||||
}
|
||||
|
||||
nsRefPtr<GenericPromise>
|
||||
DecodedStream::StartPlayback(int64_t aStartTime, const MediaInfo& aInfo)
|
||||
void
|
||||
DecodedStream::Start(int64_t aStartTime, const MediaInfo& aInfo)
|
||||
{
|
||||
AssertOwnerThread();
|
||||
MOZ_ASSERT(mStartTime.isNothing(), "playback already started.");
|
||||
|
||||
mStartTime.emplace(aStartTime);
|
||||
mInfo = aInfo;
|
||||
mPlaying = true;
|
||||
ConnectListener();
|
||||
|
||||
class R : public nsRunnable {
|
||||
|
@ -408,30 +438,33 @@ DecodedStream::StartPlayback(int64_t aStartTime, const MediaInfo& aInfo)
|
|||
};
|
||||
|
||||
MozPromiseHolder<GenericPromise> promise;
|
||||
nsRefPtr<GenericPromise> rv = promise.Ensure(__func__);
|
||||
mFinishPromise = promise.Ensure(__func__);
|
||||
nsCOMPtr<nsIRunnable> r = new R(this, &DecodedStream::CreateData, Move(promise));
|
||||
AbstractThread::MainThread()->Dispatch(r.forget());
|
||||
|
||||
return rv.forget();
|
||||
}
|
||||
|
||||
void DecodedStream::StopPlayback()
|
||||
void
|
||||
DecodedStream::Stop()
|
||||
{
|
||||
AssertOwnerThread();
|
||||
|
||||
// Playback didn't even start at all.
|
||||
if (mStartTime.isNothing()) {
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mStartTime.isSome(), "playback not started.");
|
||||
|
||||
mStartTime.reset();
|
||||
DisconnectListener();
|
||||
mFinishPromise = nullptr;
|
||||
|
||||
// Clear mData immediately when this playback session ends so we won't
|
||||
// send data to the wrong stream in SendData() in next playback session.
|
||||
DestroyData(Move(mData));
|
||||
}
|
||||
|
||||
bool
|
||||
DecodedStream::IsStarted() const
|
||||
{
|
||||
AssertOwnerThread();
|
||||
return mStartTime.isSome();
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStream::DestroyData(UniquePtr<DecodedStreamData> aData)
|
||||
{
|
||||
|
@ -531,6 +564,12 @@ void
|
|||
DecodedStream::SetPlaying(bool aPlaying)
|
||||
{
|
||||
AssertOwnerThread();
|
||||
|
||||
// Resume/pause matters only when playback started.
|
||||
if (mStartTime.isNothing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPlaying = aPlaying;
|
||||
if (mData) {
|
||||
mData->SetPlaying(aPlaying);
|
||||
|
@ -541,7 +580,21 @@ void
|
|||
DecodedStream::SetVolume(double aVolume)
|
||||
{
|
||||
AssertOwnerThread();
|
||||
mVolume = aVolume;
|
||||
mParams.mVolume = aVolume;
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStream::SetPlaybackRate(double aPlaybackRate)
|
||||
{
|
||||
AssertOwnerThread();
|
||||
mParams.mPlaybackRate = aPlaybackRate;
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStream::SetPreservesPitch(bool aPreservesPitch)
|
||||
{
|
||||
AssertOwnerThread();
|
||||
mParams.mPreservesPitch = aPreservesPitch;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -815,7 +868,7 @@ DecodedStream::SendData()
|
|||
}
|
||||
|
||||
InitTracks();
|
||||
SendAudio(mVolume, mSameOrigin);
|
||||
SendAudio(mParams.mVolume, mSameOrigin);
|
||||
SendVideo(mSameOrigin);
|
||||
AdvanceTracks();
|
||||
|
||||
|
@ -829,26 +882,31 @@ DecodedStream::SendData()
|
|||
}
|
||||
|
||||
int64_t
|
||||
DecodedStream::AudioEndTime() const
|
||||
DecodedStream::GetEndTime(TrackType aType) const
|
||||
{
|
||||
AssertOwnerThread();
|
||||
if (mStartTime.isSome() && mInfo.HasAudio() && mData) {
|
||||
if (aType == TrackInfo::kAudioTrack && mInfo.HasAudio() && mData) {
|
||||
CheckedInt64 t = mStartTime.ref() +
|
||||
FramesToUsecs(mData->mAudioFramesWritten, mInfo.mAudio.mRate);
|
||||
if (t.isValid()) {
|
||||
return t.value();
|
||||
}
|
||||
} else if (aType == TrackInfo::kVideoTrack && mData) {
|
||||
return mData->mNextVideoTime;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64_t
|
||||
DecodedStream::GetPosition() const
|
||||
DecodedStream::GetPosition(TimeStamp* aTimeStamp) const
|
||||
{
|
||||
AssertOwnerThread();
|
||||
// This is only called after MDSM starts playback. So mStartTime is
|
||||
// guaranteed to be something.
|
||||
MOZ_ASSERT(mStartTime.isSome());
|
||||
if (aTimeStamp) {
|
||||
*aTimeStamp = TimeStamp::Now();
|
||||
}
|
||||
return mStartTime.ref() + (mData ? mData->GetPosition() : 0);
|
||||
}
|
||||
|
|
@ -10,15 +10,13 @@
|
|||
#include "nsTArray.h"
|
||||
#include "MediaEventSource.h"
|
||||
#include "MediaInfo.h"
|
||||
#include "MediaSink.h"
|
||||
|
||||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/nsRefPtr.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/gfx/Point.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -31,7 +29,7 @@ class MediaStreamGraph;
|
|||
class OutputStreamListener;
|
||||
class OutputStreamManager;
|
||||
class ProcessedMediaStream;
|
||||
class ReentrantMonitor;
|
||||
class TimeStamp;
|
||||
|
||||
template <class T> class MediaQueue;
|
||||
|
||||
|
@ -99,34 +97,41 @@ private:
|
|||
nsTArray<OutputStreamData> mStreams;
|
||||
};
|
||||
|
||||
class DecodedStream {
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodedStream);
|
||||
class DecodedStream : public media::MediaSink {
|
||||
using media::MediaSink::PlaybackParams;
|
||||
|
||||
public:
|
||||
DecodedStream(AbstractThread* aOwnerThread,
|
||||
MediaQueue<MediaData>& aAudioQueue,
|
||||
MediaQueue<MediaData>& aVideoQueue);
|
||||
|
||||
void Shutdown();
|
||||
// MediaSink functions.
|
||||
const PlaybackParams& GetPlaybackParams() const override;
|
||||
void SetPlaybackParams(const PlaybackParams& aParams) override;
|
||||
|
||||
// Mimic MDSM::StartAudioThread.
|
||||
// Must be called before any calls to SendData().
|
||||
//
|
||||
// Return a promise which will be resolved when the stream is finished
|
||||
// or rejected if any error.
|
||||
nsRefPtr<GenericPromise> StartPlayback(int64_t aStartTime,
|
||||
const MediaInfo& aInfo);
|
||||
// Mimic MDSM::StopAudioThread.
|
||||
void StopPlayback();
|
||||
nsRefPtr<GenericPromise> OnEnded(TrackType aType) override;
|
||||
int64_t GetEndTime(TrackType aType) const override;
|
||||
int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const override;
|
||||
bool HasUnplayedFrames(TrackType aType) const override
|
||||
{
|
||||
// TODO: implement this.
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetVolume(double aVolume) override;
|
||||
void SetPlaybackRate(double aPlaybackRate) override;
|
||||
void SetPreservesPitch(bool aPreservesPitch) override;
|
||||
void SetPlaying(bool aPlaying) override;
|
||||
|
||||
void Start(int64_t aStartTime, const MediaInfo& aInfo) override;
|
||||
void Stop() override;
|
||||
bool IsStarted() const override;
|
||||
|
||||
// TODO: fix these functions that don't fit into the interface of MediaSink.
|
||||
void BeginShutdown();
|
||||
void AddOutput(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
|
||||
void RemoveOutput(MediaStream* aStream);
|
||||
|
||||
void SetPlaying(bool aPlaying);
|
||||
void SetVolume(double aVolume);
|
||||
void SetSameOrigin(bool aSameOrigin);
|
||||
|
||||
int64_t AudioEndTime() const;
|
||||
int64_t GetPosition() const;
|
||||
bool IsFinished() const;
|
||||
bool HasConsumers() const;
|
||||
|
||||
|
@ -164,10 +169,11 @@ private:
|
|||
* Worker thread only members.
|
||||
*/
|
||||
UniquePtr<DecodedStreamData> mData;
|
||||
nsRefPtr<GenericPromise> mFinishPromise;
|
||||
|
||||
bool mPlaying;
|
||||
double mVolume;
|
||||
bool mSameOrigin;
|
||||
PlaybackParams mParams;
|
||||
|
||||
Maybe<int64_t> mStartTime;
|
||||
MediaInfo mInfo;
|
|
@ -13,6 +13,9 @@
|
|||
#include "MediaInfo.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class TimeStamp;
|
||||
|
||||
namespace media {
|
||||
|
||||
/**
|
||||
|
@ -37,10 +40,10 @@ public:
|
|||
|
||||
struct PlaybackParams {
|
||||
PlaybackParams()
|
||||
: volume(1.0) , playbackRate(1.0) , preservesPitch(true) {}
|
||||
double volume;
|
||||
double playbackRate;
|
||||
bool preservesPitch;
|
||||
: mVolume(1.0) , mPlaybackRate(1.0) , mPreservesPitch(true) {}
|
||||
double mVolume;
|
||||
double mPlaybackRate;
|
||||
bool mPreservesPitch;
|
||||
};
|
||||
|
||||
// Return the playback parameters of this sink.
|
||||
|
@ -64,8 +67,10 @@ public:
|
|||
// Return playback position of the media.
|
||||
// Since A/V sync is always maintained by this sink, there is no need to
|
||||
// specify whether we want to get audio or video position.
|
||||
// aTimeStamp returns the timeStamp corresponding to the returned position
|
||||
// which is used by the compositor to derive the render time of video frames.
|
||||
// Must be called after playback starts.
|
||||
virtual int64_t GetPosition() const = 0;
|
||||
virtual int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const = 0;
|
||||
|
||||
// Return true if there are data consumed but not played yet.
|
||||
// Can be called in any state.
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
UNIFIED_SOURCES += [
|
||||
'AudioSinkWrapper.cpp',
|
||||
'DecodedAudioDataSink.cpp',
|
||||
'DecodedStream.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
|
|
@ -95,20 +95,6 @@ SourceBufferDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
|
|||
MSE_DEBUG("UNIMPLEMENTED");
|
||||
}
|
||||
|
||||
void
|
||||
SourceBufferDecoder::QueueMetadata(const media::TimeUnit& aTime,
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags)
|
||||
{
|
||||
MSE_DEBUG("UNIMPLEMENTED");
|
||||
}
|
||||
|
||||
void
|
||||
SourceBufferDecoder::RemoveMediaTracks()
|
||||
{
|
||||
MSE_DEBUG("UNIMPLEMENTED");
|
||||
}
|
||||
|
||||
bool
|
||||
SourceBufferDecoder::HasInitializationData()
|
||||
{
|
||||
|
|
|
@ -51,8 +51,6 @@ public:
|
|||
virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded, uint32_t aDropped) final override;
|
||||
virtual void NotifyWaitingForResourcesStatusChanged() final override;
|
||||
virtual void OnReadMetadataCompleted() final override;
|
||||
virtual void QueueMetadata(const media::TimeUnit& aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) final override;
|
||||
virtual void RemoveMediaTracks() final override;
|
||||
virtual void SetMediaSeekable(bool aMediaSeekable) final override;
|
||||
virtual bool HasInitializationData() final override;
|
||||
|
||||
|
|
|
@ -107,7 +107,6 @@ EXPORTS += [
|
|||
'AudioStream.h',
|
||||
'BufferMediaResource.h',
|
||||
'CubebUtils.h',
|
||||
'DecodedStream.h',
|
||||
'DecoderTraits.h',
|
||||
'DOMMediaStream.h',
|
||||
'EncodedBufferCache.h',
|
||||
|
@ -202,7 +201,6 @@ UNIFIED_SOURCES += [
|
|||
'AudioTrackList.cpp',
|
||||
'CanvasCaptureMediaStream.cpp',
|
||||
'CubebUtils.cpp',
|
||||
'DecodedStream.cpp',
|
||||
'DOMMediaStream.cpp',
|
||||
'EncodedBufferCache.cpp',
|
||||
'FileBlockCache.cpp',
|
||||
|
|
|
@ -814,10 +814,11 @@ bool OggReader::ReadOggChain()
|
|||
if (chained) {
|
||||
SetChained(true);
|
||||
{
|
||||
nsAutoPtr<MediaInfo> info(new MediaInfo(mInfo));
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
auto t = mDecodedAudioFrames * USECS_PER_S / mInfo.mAudio.mRate;
|
||||
mDecoder->QueueMetadata(media::TimeUnit::FromMicroseconds(t), info, tags);
|
||||
mTimedMetadataEvent.Notify(
|
||||
TimedMetadata(media::TimeUnit::FromMicroseconds(t),
|
||||
Move(tags),
|
||||
nsAutoPtr<MediaInfo>(new MediaInfo(mInfo))));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -857,11 +857,6 @@ AudioContext::Suspend(ErrorResult& aRv)
|
|||
|
||||
Destination()->Suspend();
|
||||
|
||||
MediaStream* ds = DestinationStream();
|
||||
if (ds) {
|
||||
ds->BlockStreamIfNeeded();
|
||||
}
|
||||
|
||||
mPromiseGripArray.AppendElement(promise);
|
||||
Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
|
||||
AudioContextOperation::Suspend, promise);
|
||||
|
@ -897,11 +892,6 @@ AudioContext::Resume(ErrorResult& aRv)
|
|||
|
||||
Destination()->Resume();
|
||||
|
||||
MediaStream* ds = DestinationStream();
|
||||
if (ds) {
|
||||
ds->UnblockStreamIfNeeded();
|
||||
}
|
||||
|
||||
mPromiseGripArray.AppendElement(promise);
|
||||
Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
|
||||
AudioContextOperation::Resume, promise);
|
||||
|
@ -943,10 +933,6 @@ AudioContext::Close(ErrorResult& aRv)
|
|||
if (ds) {
|
||||
Graph()->ApplyAudioContextOperation(ds->AsAudioNodeStream(),
|
||||
AudioContextOperation::Close, promise);
|
||||
|
||||
if (ds) {
|
||||
ds->BlockStreamIfNeeded();
|
||||
}
|
||||
}
|
||||
return promise.forget();
|
||||
}
|
||||
|
|
|
@ -225,7 +225,7 @@ AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
|
|||
MOZ_ASSERT(aInput <= UINT16_MAX, "Unexpected large input port number");
|
||||
MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number");
|
||||
input->mStreamPort = destinationStream->
|
||||
AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_INPUT,
|
||||
AllocateInputPort(mStream, 0,
|
||||
static_cast<uint16_t>(aInput),
|
||||
static_cast<uint16_t>(aOutput));
|
||||
}
|
||||
|
@ -268,8 +268,7 @@ AudioNode::Connect(AudioParam& aDestination, uint32_t aOutput,
|
|||
// Setup our stream as an input to the AudioParam's stream
|
||||
MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number");
|
||||
input->mStreamPort =
|
||||
ps->AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_INPUT,
|
||||
0, static_cast<uint16_t>(aOutput));
|
||||
ps->AllocateInputPort(mStream, 0, 0, static_cast<uint16_t>(aOutput));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -114,8 +114,7 @@ AudioParam::Stream()
|
|||
// Setup the AudioParam's stream as an input to the owner AudioNode's stream
|
||||
AudioNodeStream* nodeStream = mNode->GetStream();
|
||||
if (nodeStream) {
|
||||
mNodeStreamPort =
|
||||
nodeStream->AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_INPUT);
|
||||
mNodeStreamPort = nodeStream->AllocateInputPort(mStream, 0);
|
||||
}
|
||||
|
||||
// Let the MSG's copy of AudioParamTimeline know about the change in the stream
|
||||
|
|
|
@ -129,18 +129,6 @@ BufferDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo, MediaDecoderEventVis
|
|||
// ignore
|
||||
}
|
||||
|
||||
void
|
||||
BufferDecoder::QueueMetadata(const media::TimeUnit& aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
void
|
||||
BufferDecoder::RemoveMediaTracks()
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
void
|
||||
BufferDecoder::OnReadMetadataCompleted()
|
||||
{
|
||||
|
|
|
@ -58,12 +58,9 @@ public:
|
|||
virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags,
|
||||
MediaDecoderEventVisibility aEventVisibility) final override;
|
||||
virtual void QueueMetadata(const media::TimeUnit& aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) final override;
|
||||
virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
|
||||
MediaDecoderEventVisibility aEventVisibility) final override;
|
||||
|
||||
virtual void RemoveMediaTracks() final override;
|
||||
|
||||
virtual void OnReadMetadataCompleted() final override;
|
||||
|
||||
virtual MediaDecoderOwner* GetOwner() final override;
|
||||
|
|
|
@ -41,8 +41,7 @@ MediaStreamAudioSourceNode::MediaStreamAudioSourceNode(AudioContext* aContext,
|
|||
AudioNodeEngine* engine = new MediaStreamAudioSourceNodeEngine(this);
|
||||
mStream = AudioNodeExternalInputStream::Create(aContext->Graph(), engine);
|
||||
ProcessedMediaStream* outputStream = static_cast<ProcessedMediaStream*>(mStream.get());
|
||||
mInputPort = outputStream->AllocateInputPort(aMediaStream->GetStream(),
|
||||
MediaInputPort::FLAG_BLOCK_INPUT);
|
||||
mInputPort = outputStream->AllocateInputPort(aMediaStream->GetStream(), 0);
|
||||
mInputStream->AddConsumerToKeepAlive(static_cast<nsIDOMEventTarget*>(this));
|
||||
|
||||
PrincipalChanged(mInputStream); // trigger enabling/disabling of the connector
|
||||
|
|
|
@ -162,6 +162,49 @@ MediaEngineCameraVideoSource::LogConstraints(
|
|||
c.mFrameRate.mIdeal.WasPassed()? c.mFrameRate.mIdeal.Value() : 0));
|
||||
}
|
||||
|
||||
void
|
||||
MediaEngineCameraVideoSource::LogCapability(const char* aHeader,
|
||||
const webrtc::CaptureCapability &aCapability, uint32_t aDistance)
|
||||
{
|
||||
// RawVideoType and VideoCodecType media/webrtc/trunk/webrtc/common_types.h
|
||||
static const char* const types[] = {
|
||||
"I420",
|
||||
"YV12",
|
||||
"YUY2",
|
||||
"UYVY",
|
||||
"IYUV",
|
||||
"ARGB",
|
||||
"RGB24",
|
||||
"RGB565",
|
||||
"ARGB4444",
|
||||
"ARGB1555",
|
||||
"MJPEG",
|
||||
"NV12",
|
||||
"NV21",
|
||||
"BGRA",
|
||||
"Unknown type"
|
||||
};
|
||||
|
||||
static const char* const codec[] = {
|
||||
"VP8",
|
||||
"VP9",
|
||||
"H264",
|
||||
"I420",
|
||||
"RED",
|
||||
"ULPFEC",
|
||||
"Generic codec",
|
||||
"Unknown codec"
|
||||
};
|
||||
|
||||
LOG(("%s: %4u x %4u x %2u maxFps, %s, %s. Distance = %lu",
|
||||
aHeader, aCapability.width, aCapability.height, aCapability.maxFPS,
|
||||
types[std::min(std::max(uint32_t(0), uint32_t(aCapability.rawType)),
|
||||
uint32_t(sizeof(types) / sizeof(*types) - 1))],
|
||||
codec[std::min(std::max(uint32_t(0), uint32_t(aCapability.codecType)),
|
||||
uint32_t(sizeof(codec) / sizeof(*codec) - 1))],
|
||||
aDistance));
|
||||
}
|
||||
|
||||
bool
|
||||
MediaEngineCameraVideoSource::ChooseCapability(
|
||||
const MediaTrackConstraints &aConstraints,
|
||||
|
@ -195,6 +238,7 @@ MediaEngineCameraVideoSource::ChooseCapability(
|
|||
webrtc::CaptureCapability cap;
|
||||
GetCapability(candidate.mIndex, cap);
|
||||
candidate.mDistance = GetFitnessDistance(cap, aConstraints, false, aDeviceId);
|
||||
LogCapability("Capability", cap, candidate.mDistance);
|
||||
if (candidate.mDistance == UINT32_MAX) {
|
||||
candidateSet.RemoveElementAt(i);
|
||||
} else {
|
||||
|
@ -234,6 +278,7 @@ MediaEngineCameraVideoSource::ChooseCapability(
|
|||
|
||||
// Any remaining multiples all have the same distance. A common case of this
|
||||
// occurs when no ideal is specified. Lean toward defaults.
|
||||
uint32_t sameDistance = candidateSet[0].mDistance;
|
||||
{
|
||||
MediaTrackConstraintSet prefs;
|
||||
prefs.mWidth.SetAsLong() = aPrefs.GetWidth();
|
||||
|
@ -268,9 +313,7 @@ MediaEngineCameraVideoSource::ChooseCapability(
|
|||
GetCapability(candidateSet[0].mIndex, mCapability);
|
||||
}
|
||||
|
||||
LOG(("chose cap %dx%d @%dfps codec %d raw %d",
|
||||
mCapability.width, mCapability.height, mCapability.maxFPS,
|
||||
mCapability.codecType, mCapability.rawType));
|
||||
LogCapability("Chosen capability", mCapability, sameDistance);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,9 @@ protected:
|
|||
static void TrimLessFitCandidates(CapabilitySet& set);
|
||||
static void LogConstraints(const dom::MediaTrackConstraintSet& aConstraints,
|
||||
bool aAdvanced);
|
||||
static void LogCapability(const char* aHeader,
|
||||
const webrtc::CaptureCapability &aCapability,
|
||||
uint32_t aDistance);
|
||||
virtual size_t NumCapabilities();
|
||||
virtual void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut);
|
||||
bool ChooseCapability(const dom::MediaTrackConstraints &aConstraints,
|
||||
|
|
|
@ -140,15 +140,19 @@ nsSpeechTask::~nsSpeechTask()
|
|||
}
|
||||
|
||||
void
|
||||
nsSpeechTask::Init(ProcessedMediaStream* aStream)
|
||||
nsSpeechTask::InitDirectAudio()
|
||||
{
|
||||
if (aStream) {
|
||||
mStream = aStream->Graph()->CreateSourceStream(nullptr);
|
||||
mPort = aStream->AllocateInputPort(mStream, 0);
|
||||
mIndirectAudio = false;
|
||||
} else {
|
||||
mIndirectAudio = true;
|
||||
}
|
||||
mStream = MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
|
||||
AudioChannel::Normal)->
|
||||
CreateSourceStream(nullptr);
|
||||
mIndirectAudio = false;
|
||||
mInited = true;
|
||||
}
|
||||
|
||||
void
|
||||
nsSpeechTask::InitIndirectAudio()
|
||||
{
|
||||
mIndirectAudio = true;
|
||||
mInited = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,8 @@ public:
|
|||
|
||||
void SetSpeechSynthesis(SpeechSynthesis* aSpeechSynthesis);
|
||||
|
||||
void Init(ProcessedMediaStream* aStream);
|
||||
void InitDirectAudio();
|
||||
void InitIndirectAudio();
|
||||
|
||||
void SetChosenVoiceURI(const nsAString& aUri);
|
||||
|
||||
|
|
|
@ -177,14 +177,6 @@ nsSynthVoiceRegistry::~nsSynthVoiceRegistry()
|
|||
// mSpeechSynthChild's lifecycle is managed by the Content protocol.
|
||||
mSpeechSynthChild = nullptr;
|
||||
|
||||
if (mStream) {
|
||||
if (!mStream->IsDestroyed()) {
|
||||
mStream->Destroy();
|
||||
}
|
||||
|
||||
mStream = nullptr;
|
||||
}
|
||||
|
||||
mUriVoiceMap.Clear();
|
||||
}
|
||||
|
||||
|
@ -775,14 +767,9 @@ nsSynthVoiceRegistry::SpeakImpl(VoiceData* aVoice,
|
|||
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to get speech service type");
|
||||
|
||||
if (serviceType == nsISpeechService::SERVICETYPE_INDIRECT_AUDIO) {
|
||||
aTask->Init(nullptr);
|
||||
aTask->InitIndirectAudio();
|
||||
} else {
|
||||
if (!mStream) {
|
||||
mStream =
|
||||
MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
|
||||
AudioChannel::Normal)->CreateTrackUnionStream(nullptr);
|
||||
}
|
||||
aTask->Init(mStream);
|
||||
aTask->InitDirectAudio();
|
||||
}
|
||||
|
||||
aVoice->mService->Speak(aText, aVoice->mUri, aVolume, aRate, aPitch, aTask);
|
||||
|
|
|
@ -95,8 +95,6 @@ private:
|
|||
|
||||
SpeechSynthesisChild* mSpeechSynthChild;
|
||||
|
||||
nsRefPtr<ProcessedMediaStream> mStream;
|
||||
|
||||
bool mUseGlobalQueue;
|
||||
|
||||
nsTArray<nsRefPtr<GlobalQueueItem>> mGlobalQueue;
|
||||
|
|
|
@ -511,10 +511,10 @@ SVGSVGElement::SetCurrentScaleTranslate(float s, float x, float y)
|
|||
if (presShell && IsRoot()) {
|
||||
nsEventStatus status = nsEventStatus_eIgnore;
|
||||
if (mPreviousScale != mCurrentScale) {
|
||||
InternalSVGZoomEvent svgZoomEvent(true, NS_SVG_ZOOM);
|
||||
InternalSVGZoomEvent svgZoomEvent(true, eSVGZoom);
|
||||
presShell->HandleDOMEventWithTarget(this, &svgZoomEvent, &status);
|
||||
} else {
|
||||
WidgetEvent svgScrollEvent(true, NS_SVG_SCROLL);
|
||||
WidgetEvent svgScrollEvent(true, eSVGScroll);
|
||||
presShell->HandleDOMEventWithTarget(this, &svgScrollEvent, &status);
|
||||
}
|
||||
InvalidateTransformNotifyFrame();
|
||||
|
@ -590,7 +590,7 @@ SVGSVGElement::IsAttributeMapped(const nsIAtom* name) const
|
|||
nsresult
|
||||
SVGSVGElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
|
||||
{
|
||||
if (aVisitor.mEvent->mMessage == NS_SVG_LOAD) {
|
||||
if (aVisitor.mEvent->mMessage == eSVGLoad) {
|
||||
if (mTimedDocumentRoot) {
|
||||
mTimedDocumentRoot->Begin();
|
||||
// Set 'resample needed' flag, so that if any script calls a DOM method
|
||||
|
|
|
@ -31,7 +31,7 @@ SVGZoomEvent::SVGZoomEvent(EventTarget* aOwner,
|
|||
nsPresContext* aPresContext,
|
||||
InternalSVGZoomEvent* aEvent)
|
||||
: UIEvent(aOwner, aPresContext,
|
||||
aEvent ? aEvent : new InternalSVGZoomEvent(false, NS_SVG_ZOOM))
|
||||
aEvent ? aEvent : new InternalSVGZoomEvent(false, eSVGZoom))
|
||||
, mPreviousScale(0)
|
||||
, mNewScale(0)
|
||||
{
|
||||
|
|
|
@ -1857,6 +1857,9 @@ nsEditor::NotifyEditorObservers(NotificationForEditorObservers aNotification)
|
|||
FireInputEvent();
|
||||
break;
|
||||
case eNotifyEditorObserversOfBefore:
|
||||
if (NS_WARN_IF(mIsInEditAction)) {
|
||||
break;
|
||||
}
|
||||
mIsInEditAction = true;
|
||||
for (auto& observer : observers) {
|
||||
observer->BeforeEditAction();
|
||||
|
|
|
@ -885,10 +885,11 @@ nsPlaintextEditor::UpdateIMEComposition(nsIDOMEvent* aDOMTextEvent)
|
|||
}
|
||||
|
||||
// If still composing, we should fire input event via observer.
|
||||
// Note that if committed, we don't need to notify it since it will be
|
||||
// notified at followed compositionend event.
|
||||
// Note that if the composition will be committed by the following
|
||||
// compositionend event, we don't need to notify editor observes of this
|
||||
// change.
|
||||
// NOTE: We must notify after the auto batch will be gone.
|
||||
if (IsIMEComposing()) {
|
||||
if (!compositionChangeEvent->IsFollowedByCompositionEnd()) {
|
||||
NotifyEditorObservers(eNotifyEditorObserversOfEnd);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ lib_LTLIBRARIES = libharfbuzz.la
|
|||
|
||||
HBCFLAGS =
|
||||
HBLIBS =
|
||||
HBNONPCLIBS =
|
||||
HBDEPS =
|
||||
HBSOURCES = \
|
||||
hb-atomic-private.hh \
|
||||
hb-blob.cc \
|
||||
|
@ -39,6 +41,7 @@ HBSOURCES = \
|
|||
hb-open-file-private.hh \
|
||||
hb-open-type-private.hh \
|
||||
hb-ot-cmap-table.hh \
|
||||
hb-ot-glyf-table.hh \
|
||||
hb-ot-head-table.hh \
|
||||
hb-ot-hhea-table.hh \
|
||||
hb-ot-hmtx-table.hh \
|
||||
|
@ -93,6 +96,7 @@ HBSOURCES += \
|
|||
hb-ot-shape.cc \
|
||||
hb-ot-shape-complex-arabic.cc \
|
||||
hb-ot-shape-complex-arabic-fallback.hh \
|
||||
hb-ot-shape-complex-arabic-private.hh \
|
||||
hb-ot-shape-complex-arabic-table.hh \
|
||||
hb-ot-shape-complex-arabic-win1256.hh \
|
||||
hb-ot-shape-complex-default.cc \
|
||||
|
@ -132,12 +136,13 @@ endif
|
|||
|
||||
if HAVE_PTHREAD
|
||||
HBCFLAGS += $(PTHREAD_CFLAGS)
|
||||
HBLIBS += $(PTHREAD_LIBS)
|
||||
HBNONPCLIBS += $(PTHREAD_LIBS)
|
||||
endif
|
||||
|
||||
if HAVE_GLIB
|
||||
HBCFLAGS += $(GLIB_CFLAGS)
|
||||
HBLIBS += $(GLIB_LIBS)
|
||||
HBDEPS += $(GLIB_DEPS)
|
||||
HBSOURCES += hb-glib.cc
|
||||
HBHEADERS += hb-glib.h
|
||||
endif
|
||||
|
@ -145,6 +150,12 @@ endif
|
|||
if HAVE_FREETYPE
|
||||
HBCFLAGS += $(FREETYPE_CFLAGS)
|
||||
HBLIBS += $(FREETYPE_LIBS)
|
||||
# XXX
|
||||
# The following creates a recursive dependency on FreeType if FreeType is
|
||||
# built with HarfBuzz support enabled. Newer pkg-config handles that just
|
||||
# fine but pkg-config 0.26 as shipped in Ubuntu 14.04 crashes. Remove
|
||||
# in a year or two, or otherwise work around it...
|
||||
#HBDEPS += $(FREETYPE_DEPS)
|
||||
HBSOURCES += hb-ft.cc
|
||||
HBHEADERS += hb-ft.h
|
||||
endif
|
||||
|
@ -152,20 +163,21 @@ endif
|
|||
if HAVE_GRAPHITE2
|
||||
HBCFLAGS += $(GRAPHITE2_CFLAGS)
|
||||
HBLIBS += $(GRAPHITE2_LIBS)
|
||||
HBDEPS += $(GRAPHITE2_DEPS)
|
||||
HBSOURCES += hb-graphite2.cc
|
||||
HBHEADERS += hb-graphite2.h
|
||||
endif
|
||||
|
||||
if HAVE_UNISCRIBE
|
||||
HBCFLAGS += $(UNISCRIBE_CFLAGS)
|
||||
HBLIBS += $(UNISCRIBE_LIBS)
|
||||
HBNONPCLIBS += $(UNISCRIBE_LIBS)
|
||||
HBSOURCES += hb-uniscribe.cc
|
||||
HBHEADERS += hb-uniscribe.h
|
||||
endif
|
||||
|
||||
if HAVE_CORETEXT
|
||||
HBCFLAGS += $(CORETEXT_CFLAGS)
|
||||
HBLIBS += $(CORETEXT_LIBS)
|
||||
HBNONPCLIBS += $(CORETEXT_LIBS)
|
||||
HBSOURCES += hb-coretext.cc
|
||||
HBHEADERS += hb-coretext.h
|
||||
endif
|
||||
|
@ -181,6 +193,8 @@ DIST_SUBDIRS += hb-ucdn
|
|||
|
||||
# Put the library together
|
||||
|
||||
HBLIBS += $(HBNONPCLIBS)
|
||||
|
||||
if OS_WIN32
|
||||
export_symbols = -export-symbols harfbuzz.def
|
||||
harfbuzz_def_dependency = harfbuzz.def
|
||||
|
@ -255,6 +269,8 @@ EXTRA_DIST += \
|
|||
-e 's@%exec_prefix%@$(exec_prefix)@g' \
|
||||
-e 's@%libdir%@$(libdir)@g' \
|
||||
-e 's@%includedir%@$(includedir)@g' \
|
||||
-e 's@%libs_private%@$(HBNONPCLIBS)@g' \
|
||||
-e 's@%requires_private%@$(HBDEPS)@g' \
|
||||
-e 's@%VERSION%@$(VERSION)@g' \
|
||||
"$<" > "$@" \
|
||||
|| ($(RM) "$@"; false)
|
||||
|
@ -269,7 +285,7 @@ harfbuzz.def: $(HBHEADERS) $(HBNODISTHEADERS)
|
|||
$(EGREP) '^hb_.* \(' | \
|
||||
sed -e 's/ (.*//' | \
|
||||
LANG=C sort; \
|
||||
echo LIBRARY libharfbuzz-$(HB_VERSION_MAJOR).dll; \
|
||||
echo LIBRARY libharfbuzz-0.dll; \
|
||||
) >"$@"
|
||||
@ ! grep -q hb_ERROR "$@" \
|
||||
|| ($(RM) "$@"; false)
|
||||
|
@ -370,7 +386,7 @@ TESTS_ENVIRONMENT = \
|
|||
if HAVE_INTROSPECTION
|
||||
|
||||
-include $(INTROSPECTION_MAKEFILE)
|
||||
INTROSPECTION_GIRS = HarfBuzz-$(HB_VERSION_MAJOR).0.gir # What does the 0 mean anyway?!
|
||||
INTROSPECTION_GIRS = HarfBuzz-0.0.gir # What does the 0 mean anyway?!
|
||||
INTROSPECTION_SCANNER_ARGS = -I$(srcdir) -n hb --identifier-prefix=hb_ --warn-all
|
||||
INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir)
|
||||
INTROSPECTION_SCANNER_ENV = CC="$(CC)"
|
||||
|
|
|
@ -8,4 +8,6 @@ Description: HarfBuzz text shaping library
|
|||
Version: %VERSION%
|
||||
|
||||
Libs: -L${libdir} -lharfbuzz
|
||||
Libs.private: %libs_private%
|
||||
Requires.private: %requires_private%
|
||||
Cflags: -I${includedir}/harfbuzz
|
||||
|
|
|
@ -201,6 +201,8 @@ struct hb_buffer_t {
|
|||
HB_INTERNAL scratch_buffer_t *get_scratch_buffer (unsigned int *size);
|
||||
|
||||
inline void clear_context (unsigned int side) { context_len[side] = 0; }
|
||||
|
||||
HB_INTERNAL void sort (unsigned int start, unsigned int end, int(*compar)(const hb_glyph_info_t *, const hb_glyph_info_t *));
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -99,7 +99,8 @@ _hb_buffer_serialize_glyphs_json (hb_buffer_t *buffer,
|
|||
hb_buffer_serialize_flags_t flags)
|
||||
{
|
||||
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
|
||||
hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
|
||||
hb_glyph_position_t *pos = (flags & HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS) ?
|
||||
NULL : hb_buffer_get_glyph_positions (buffer, NULL);
|
||||
|
||||
*buf_consumed = 0;
|
||||
for (unsigned int i = start; i < end; i++)
|
||||
|
@ -144,6 +145,16 @@ _hb_buffer_serialize_glyphs_json (hb_buffer_t *buffer,
|
|||
pos[i].x_advance, pos[i].y_advance);
|
||||
}
|
||||
|
||||
if (flags & HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS)
|
||||
{
|
||||
hb_glyph_extents_t extents;
|
||||
hb_font_get_glyph_extents(font, info[i].codepoint, &extents);
|
||||
p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"xb\":%d,\"yb\":%d",
|
||||
extents.x_bearing, extents.y_bearing));
|
||||
p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"w\":%d,\"h\":%d",
|
||||
extents.width, extents.height));
|
||||
}
|
||||
|
||||
*p++ = '}';
|
||||
|
||||
unsigned int l = p - b;
|
||||
|
@ -172,7 +183,8 @@ _hb_buffer_serialize_glyphs_text (hb_buffer_t *buffer,
|
|||
hb_buffer_serialize_flags_t flags)
|
||||
{
|
||||
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
|
||||
hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
|
||||
hb_glyph_position_t *pos = (flags & HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS) ?
|
||||
NULL : hb_buffer_get_glyph_positions (buffer, NULL);
|
||||
|
||||
*buf_consumed = 0;
|
||||
for (unsigned int i = start; i < end; i++)
|
||||
|
@ -208,6 +220,13 @@ _hb_buffer_serialize_glyphs_text (hb_buffer_t *buffer,
|
|||
p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",%d", pos[i].y_advance));
|
||||
}
|
||||
|
||||
if (flags & HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS)
|
||||
{
|
||||
hb_glyph_extents_t extents;
|
||||
hb_font_get_glyph_extents(font, info[i].codepoint, &extents);
|
||||
p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), "<%d,%d,%d,%d>", extents.x_bearing, extents.y_bearing, extents.width, extents.height));
|
||||
}
|
||||
|
||||
unsigned int l = p - b;
|
||||
if (buf_size > l)
|
||||
{
|
||||
|
|
|
@ -1636,7 +1636,7 @@ normalize_glyphs_cluster (hb_buffer_t *buffer,
|
|||
pos[end - 1].x_advance = total_x_advance;
|
||||
pos[end - 1].y_advance = total_y_advance;
|
||||
|
||||
hb_bubble_sort (buffer->info + start, end - start - 1, compare_info_codepoint, buffer->pos + start);
|
||||
hb_stable_sort (buffer->info + start, end - start - 1, compare_info_codepoint, buffer->pos + start);
|
||||
} else {
|
||||
/* Transfer all cluster advance to the first glyph. */
|
||||
pos[start].x_advance += total_x_advance;
|
||||
|
@ -1645,7 +1645,7 @@ normalize_glyphs_cluster (hb_buffer_t *buffer,
|
|||
pos[i].x_offset -= total_x_advance;
|
||||
pos[i].y_offset -= total_y_advance;
|
||||
}
|
||||
hb_bubble_sort (buffer->info + start + 1, end - start - 1, compare_info_codepoint, buffer->pos + start + 1);
|
||||
hb_stable_sort (buffer->info + start + 1, end - start - 1, compare_info_codepoint, buffer->pos + start + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1678,3 +1678,24 @@ hb_buffer_normalize_glyphs (hb_buffer_t *buffer)
|
|||
}
|
||||
normalize_glyphs_cluster (buffer, start, end, backward);
|
||||
}
|
||||
|
||||
void
|
||||
hb_buffer_t::sort (unsigned int start, unsigned int end, int(*compar)(const hb_glyph_info_t *, const hb_glyph_info_t *))
|
||||
{
|
||||
assert (!have_positions);
|
||||
for (unsigned int i = start + 1; i < end; i++)
|
||||
{
|
||||
unsigned int j = i;
|
||||
while (j > start && compar (&info[j - 1], &info[i]) > 0)
|
||||
j--;
|
||||
if (i == j)
|
||||
continue;
|
||||
/* Move item i to occupy place for item j, shift what's in between. */
|
||||
merge_clusters (j, i + 1);
|
||||
{
|
||||
hb_glyph_info_t t = info[i];
|
||||
memmove (&info[j + 1], &info[j], (i - j) * sizeof (hb_glyph_info_t));
|
||||
info[j] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -323,7 +323,8 @@ typedef enum { /*< flags >*/
|
|||
HB_BUFFER_SERIALIZE_FLAG_DEFAULT = 0x00000000u,
|
||||
HB_BUFFER_SERIALIZE_FLAG_NO_CLUSTERS = 0x00000001u,
|
||||
HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS = 0x00000002u,
|
||||
HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES = 0x00000004u
|
||||
HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES = 0x00000004u,
|
||||
HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS = 0x00000008u
|
||||
} hb_buffer_serialize_flags_t;
|
||||
|
||||
typedef enum {
|
||||
|
|
|
@ -156,6 +156,7 @@ _hb_coretext_shaper_font_data_create (hb_font_t *font)
|
|||
hb_coretext_shaper_face_data_t *face_data = HB_SHAPER_DATA_GET (face);
|
||||
|
||||
/* Choose a CoreText font size and calculate multipliers to convert to HarfBuzz space. */
|
||||
/* TODO: use upem instead of 36? */
|
||||
CGFloat font_size = 36.; /* Default... */
|
||||
/* No idea if the following is even a good idea. */
|
||||
if (font->y_ppem)
|
||||
|
@ -915,8 +916,8 @@ retry:
|
|||
info->cluster = log_clusters[j];
|
||||
|
||||
info->mask = advance;
|
||||
info->var1.u32 = x_offset;
|
||||
info->var2.u32 = y_offset;
|
||||
info->var1.i32 = x_offset;
|
||||
info->var2.i32 = y_offset;
|
||||
|
||||
info++;
|
||||
buffer->len++;
|
||||
|
@ -1002,8 +1003,8 @@ retry:
|
|||
else /* last glyph */
|
||||
advance = run_advance - (positions[j].x - positions[0].x);
|
||||
info->mask = advance * x_mult;
|
||||
info->var1.u32 = x_offset;
|
||||
info->var2.u32 = positions[j].y * y_mult;
|
||||
info->var1.i32 = x_offset;
|
||||
info->var2.i32 = positions[j].y * y_mult;
|
||||
info++;
|
||||
}
|
||||
}
|
||||
|
@ -1018,8 +1019,8 @@ retry:
|
|||
else /* last glyph */
|
||||
advance = run_advance - (positions[j].y - positions[0].y);
|
||||
info->mask = advance * y_mult;
|
||||
info->var1.u32 = positions[j].x * x_mult;
|
||||
info->var2.u32 = y_offset;
|
||||
info->var1.i32 = positions[j].x * x_mult;
|
||||
info->var2.i32 = y_offset;
|
||||
info++;
|
||||
}
|
||||
}
|
||||
|
@ -1034,10 +1035,20 @@ retry:
|
|||
buffer->len += num_glyphs;
|
||||
}
|
||||
|
||||
/* Make sure all runs had the expected direction. */
|
||||
bool backward = HB_DIRECTION_IS_BACKWARD (buffer->props.direction);
|
||||
assert (bool (status_and & kCTRunStatusRightToLeft) == backward);
|
||||
assert (bool (status_or & kCTRunStatusRightToLeft) == backward);
|
||||
/* Mac OS 10.6 doesn't have kCTTypesetterOptionForcedEmbeddingLevel,
|
||||
* or if it does, it doesn't resepct it. So we get runs with wrong
|
||||
* directions. As such, disable the assert... It wouldn't crash, but
|
||||
* cursoring will be off...
|
||||
*
|
||||
* http://crbug.com/419769
|
||||
*/
|
||||
if (0)
|
||||
{
|
||||
/* Make sure all runs had the expected direction. */
|
||||
bool backward = HB_DIRECTION_IS_BACKWARD (buffer->props.direction);
|
||||
assert (bool (status_and & kCTRunStatusRightToLeft) == backward);
|
||||
assert (bool (status_or & kCTRunStatusRightToLeft) == backward);
|
||||
}
|
||||
|
||||
buffer->clear_positions ();
|
||||
|
||||
|
@ -1048,16 +1059,16 @@ retry:
|
|||
for (unsigned int i = 0; i < count; i++)
|
||||
{
|
||||
pos->x_advance = info->mask;
|
||||
pos->x_offset = info->var1.u32;
|
||||
pos->y_offset = info->var2.u32;
|
||||
pos->x_offset = info->var1.i32;
|
||||
pos->y_offset = info->var2.i32;
|
||||
info++, pos++;
|
||||
}
|
||||
else
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
{
|
||||
pos->y_advance = info->mask;
|
||||
pos->x_offset = info->var1.u32;
|
||||
pos->y_offset = info->var2.u32;
|
||||
pos->x_offset = info->var1.i32;
|
||||
pos->y_offset = info->var2.i32;
|
||||
info++, pos++;
|
||||
}
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче