Merge mozilla-central to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-09-08 16:22:03 +02:00
Родитель 9f9a7e6942 06682809ee
Коммит 1355b0a297
233 изменённых файлов: 5362 добавлений и 2463 удалений

Просмотреть файл

@ -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++;
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше