Merge mozilla-central to fx-team

This commit is contained in:
Carsten "Tomcat" Book 2014-12-22 14:06:42 +01:00
commit edefedeca8
181 changed files with 3519 additions and 1667 deletions

View File

@ -3,12 +3,8 @@
// Send these sites a custom user-agent. Bugs to remove each override after
// evangelism are included.
{
// bug 826335, globo.com
"globo.com": "\\(Mobile#(Android; Mobile",
// bug 826347, msn.com
"msn.com": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
// bug 826348, linkedin.com
"linkedin.com": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
// bug 826353, itau.com.br
"itau.com.br": "\\(Mobile#(Android; Mobile",
// bug 826510, r7.com

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ca6e91e09ef3ab417a0f6b6d6668d43597d85700"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c5b03a9b40a37947d44a71eccd9017e76632f796"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
@ -129,7 +129,7 @@
<!-- Stock Android things -->
<project name="platform/external/icu4c" path="external/icu4c" revision="2bb01561780583cc37bc667f0ea79f48a122d8a2"/>
<!-- dolphin specific things -->
<project name="device/sprd" path="device/sprd" revision="b007fe5fb2c214bd71133378248113e6e74ce4d9"/>
<project name="device/sprd" path="device/sprd" revision="14267a1ca51dcefe56d031245c6252c9d7402ce5"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="4e58336019b5cbcfd134caf55b142236cf986618"/>
<project name="platform/frameworks/av" path="frameworks/av" revision="4387fe988e5a1001f29ce05fcfda03ed2d32137b"/>
<project name="platform/hardware/akm" path="hardware/akm" revision="6d3be412647b0eab0adff8a2768736cf4eb68039"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ca6e91e09ef3ab417a0f6b6d6668d43597d85700"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5b03a9b40a37947d44a71eccd9017e76632f796"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ca6e91e09ef3ab417a0f6b6d6668d43597d85700"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c5b03a9b40a37947d44a71eccd9017e76632f796"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f11d3c6500659e3232fbe6fe7ea0204c40ab7fdd"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ca6e91e09ef3ab417a0f6b6d6668d43597d85700"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c5b03a9b40a37947d44a71eccd9017e76632f796"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ca6e91e09ef3ab417a0f6b6d6668d43597d85700"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5b03a9b40a37947d44a71eccd9017e76632f796"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ca6e91e09ef3ab417a0f6b6d6668d43597d85700"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c5b03a9b40a37947d44a71eccd9017e76632f796"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ca6e91e09ef3ab417a0f6b6d6668d43597d85700"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c5b03a9b40a37947d44a71eccd9017e76632f796"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f11d3c6500659e3232fbe6fe7ea0204c40ab7fdd"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "3d66911e8f085901b6302ec2b40bc89bc5322db4",
"revision": "24f27472ae6ddb6c819227cb3b7d398a6925ef86",
"repo_path": "integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ca6e91e09ef3ab417a0f6b6d6668d43597d85700"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5b03a9b40a37947d44a71eccd9017e76632f796"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ca6e91e09ef3ab417a0f6b6d6668d43597d85700"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5b03a9b40a37947d44a71eccd9017e76632f796"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ca6e91e09ef3ab417a0f6b6d6668d43597d85700"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c5b03a9b40a37947d44a71eccd9017e76632f796"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f11d3c6500659e3232fbe6fe7ea0204c40ab7fdd"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ca6e91e09ef3ab417a0f6b6d6668d43597d85700"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5b03a9b40a37947d44a71eccd9017e76632f796"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="8aee09c106f479f36c57b2a29af72d455e359211"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -281,9 +281,9 @@ let MozLoopPushHandler = {
}
let pushServerURLFetchError = () => {
console.warn("MozLoopPushHandler - Could not retrieve push server URL from Loop server; using default");
this.pushServerUri = Services.prefs.getCharPref("services.push.serverURL");
performOpen();
console.warn("MozLoopPushHandler - Could not retrieve push server URL from Loop server, will retry");
this._retryOperation(() => this._openSocket());
return;
}
if (!this.pushServerUri) {
@ -303,6 +303,7 @@ let MozLoopPushHandler = {
}
if (pushServerConfig.pushServerURI) {
this.pushServerUri = pushServerConfig.pushServerURI;
this._retryEnd();
performOpen();
} else {
console.warn("MozLoopPushHandler - push server URL config lacks pushServerURI parameter");

View File

@ -65,6 +65,7 @@ function setup_params(request, response) {
if (request.method == "DELETE") {
setSharedState("/fxa-oauth/params", "");
setSharedState("/registration", "");
setSharedState("/fxa-oauth/token", "");
response.write("Params deleted");
return;
}
@ -166,13 +167,14 @@ function token(request, response) {
return;
}
let tokenData = {
access_token: payload.code + "_access_token",
scope: "profile",
token_type: "bearer",
};
let tokenData = JSON.stringify(
{access_token: payload.code + "_access_token",
scope: "profile",
token_type: "bearer"},
null, 2);
setSharedState("/fxa-oauth/token", tokenData);
response.setHeader("Content-Type", "application/json; charset=utf-8", false);
response.write(JSON.stringify(tokenData, null, 2));
response.write(tokenData);
}
/**
@ -194,17 +196,27 @@ function profile(request, response) {
* Mock Loop registration endpoint. Hawk Authorization headers are expected only for FxA sessions.
*/
function registration(request, response) {
let isFxARequest = function(payload) {
return (payload.simplePushURL == "https://localhost/pushUrl/fxa" ||
payload.simplePushURLs.calls == "https://localhost/pushUrl/fxa-calls" ||
payload.simplePushURLs.rooms == "https://localhost/pushUrl/fxa-rooms");
};
let body = NetUtil.readInputStreamToString(request.bodyInputStream,
request.bodyInputStream.available());
let payload = JSON.parse(body);
if ((payload.simplePushURL == "https://localhost/pushUrl/fxa" ||
payload.simplePushURLs.calls == "https://localhost/pushUrl/fxa-calls" ||
payload.simplePushURLs.rooms == "https://localhost/pushUrl/fxa-rooms") &&
(!request.hasHeader("Authorization") ||
!request.getHeader("Authorization").startsWith("Hawk"))) {
response.setStatusLine(request.httpVersion, 401, "Missing Hawk");
response.write("401 Missing Hawk Authorization header");
return;
if (isFxARequest(payload)) {
if (!request.hasHeader("Authorization") ||
!request.getHeader("Authorization").startsWith("Hawk")) {
response.setStatusLine(request.httpVersion, 401, "Missing Hawk");
response.write("401 Missing Hawk Authorization header");
return;
}
if (!getSharedState("/fxa-oauth/token")) {
response.setStatusLine(request.httpVersion, 409, "Wrong State");
response.write("409 complete OAuth before attempting registration");
return;
}
}
setSharedState("/registration", body);
}

View File

@ -3,15 +3,54 @@
{
let dummyCallback = () => {};
let mockWebSocket = new MockWebSocketChannel();
let pushServerRequestCount = 0;
add_test(function test_initalize_offline() {
Services.io.offline = true;
do_check_false(MozLoopPushHandler.initialize());
Services.io.offline = false;
MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket});
run_next_test();
});
add_test(function test_initalize() {
loopServer.registerPathHandler("/push-server-config", (request, response) => {
// The PushHandler should retry the request for the push-server-config for
// each of these cases without throwing an error.
let n = 0;
switch (++pushServerRequestCount) {
case ++n:
// Non-200 response
response.setStatusLine(null, 500, "Retry");
response.processAsync();
response.finish();
break;
case ++n:
// missing parameter
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({pushServerURI: null}));
response.processAsync();
response.finish();
break;
case ++n:
// json parse error
response.setStatusLine(null, 200, "OK");
response.processAsync();
response.finish();
break;
case ++n:
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({pushServerURI: kServerPushUrl}));
response.processAsync();
response.finish();
run_next_test();
break;
}
});
do_check_true(MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket}));
});
add_test(function test_initalize_missing_chanid() {
Assert.throws(() => {MozLoopPushHandler.register(null, dummyCallback, dummyCallback)});
run_next_test();
@ -104,11 +143,13 @@
function run_test() {
setupFakeLoopServer();
Services.prefs.setCharPref("services.push.serverURL", kServerPushUrl);
Services.prefs.setCharPref("loop.server", kLoopServerUrl);
Services.prefs.setIntPref("loop.retry_delay.start", 10); // 10 ms
Services.prefs.setIntPref("loop.retry_delay.limit", 20); // 20 ms
do_register_cleanup(function() {
Services.prefs.setCharPref("loop.server", kLoopServerUrl);
});
run_next_test();
};
}

View File

@ -42,9 +42,9 @@ function checkState(tab) {
// deserialized in the content scope. And in this case, since RegExps are
// not currently Xrayable (see bug 1014991), trying to pull |obj3| (a RegExp)
// off of an Xrayed Object won't work. So we need to waive.
runInContent(tab.linkedBrowser, function(win, state) {
return Cu.waiveXrays(state).obj3.toString();
}, aEvent.state).then(function(stateStr) {
runInContent(tab.linkedBrowser, function(win, event) {
return Cu.waiveXrays(event.state).obj3.toString();
}, aEvent).then(function(stateStr) {
is(stateStr, '/^a$/', "second popstate object.");
// Make sure that the new-elem node is present in the document. If it's

View File

@ -233,7 +233,7 @@ function finishTests() {
// Sends a click event on the passed DOM node in an async manner
function click(node) {
node.scrollIntoView()
node.scrollIntoView();
executeSoon(() => EventUtils.synthesizeMouseAtCenter(node, {}, gPanelWindow));
}
@ -483,8 +483,35 @@ function selectTreeItem(ids) {
// Expand tree as some/all items could be collapsed leading to click on an
// incorrect tree item
gUI.tree.expandAll();
click(gPanelWindow.document.querySelector("[data-id='" + JSON.stringify(ids) +
"'] > .tree-widget-item"));
let target = gPanelWindow.document.querySelector(
"[data-id='" + JSON.stringify(ids) + "'] > .tree-widget-item");
// Look for an animating list
//
// If the list is still expanding we won't be able to click the requested
// item. We detect if a list (or one of its ancestor lists) is animating
// by checking if max-height is set. If the animation effect in
// widgets.inc.css changes this will likely break but until the Web
// Animations API is available (specifically, bug 1074630 and bug 1112422)
// there is no reliable way to detect if animations are running, particularly
// in the face of interactions that can cancel an animation without triggering
// an animationend event.
let animatingList = target.nextElementSibling;
while (animatingList) {
if (window.getComputedStyle(animatingList).maxHeight != "none") {
break;
}
animatingList = animatingList.parentNode.closest(".tree-widget-item + ul");
}
if (animatingList) {
animatingList.addEventListener("animationend", function animationend() {
animatingList.removeEventListener("animationend", animationend);
click(target);
});
} else {
click(target);
}
}
/**

View File

@ -161,7 +161,6 @@ http://tracking.example.org:80
# Bug 483437, 484111
https://www.bank1.com:443 privileged,cert=escapeattack1
https://www.bank2.com:443 privileged,cert=escapeattack2
#
# CONNECT for redirproxy results in a 302 redirect to

View File

@ -133,9 +133,7 @@ AnimationPlayer::SetSource(Animation* aSource)
void
AnimationPlayer::Tick()
{
if (mSource) {
mSource->SetParentTime(GetCurrentTime());
}
UpdateSourceContent();
}
void
@ -275,6 +273,14 @@ AnimationPlayer::DoPause()
mStartTime.SetNull();
}
void
AnimationPlayer::UpdateSourceContent()
{
if (mSource) {
mSource->SetParentTime(GetCurrentTime());
}
}
void
AnimationPlayer::FlushStyle() const
{

View File

@ -21,6 +21,12 @@
#undef CurrentTime
#endif
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
// GetTickCount().
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif
struct JSContext;
class nsCSSPropertySet;
class nsIDocument;
@ -131,6 +137,7 @@ protected:
void DoPlay();
void DoPause();
void UpdateSourceContent();
void FlushStyle() const;
void PostUpdate();
// Remove this player from the pending player tracker and resets mIsPending

View File

@ -38,34 +38,104 @@ AnimationTimeline::GetCurrentTimeAsDouble() const
return AnimationUtils::TimeDurationToDouble(GetCurrentTime());
}
void
AnimationTimeline::FastForward(const TimeStamp& aTimeStamp)
{
// If we have already been fast-forwarded to an equally or more
// recent time, ignore this call.
if (!mFastForwardTime.IsNull() && aTimeStamp <= mFastForwardTime) {
return;
}
// If the refresh driver is under test control then its values have little
// connection to TimeStamp values and it doesn't make sense to fast-forward
// the timeline to a TimeStamp value.
//
// Furthermore, when the refresh driver is under test control,
// nsDOMWindowUtils::AdvanceTimeAndRefresh automatically starts any
// pending animation players so we don't need to fast-forward the timeline
// anyway.
nsRefreshDriver* refreshDriver = GetRefreshDriver();
if (refreshDriver && refreshDriver->IsTestControllingRefreshesEnabled()) {
return;
}
// Bug 1113413: If the refresh driver has just been restored from test
// control it's possible that aTimeStamp could be before the most recent
// refresh.
if (refreshDriver &&
aTimeStamp < refreshDriver->MostRecentRefresh()) {
mFastForwardTime = refreshDriver->MostRecentRefresh();
return;
}
// FIXME: For all animations attached to this timeline, we should mark
// their target elements as needing restyling. Otherwise, tasks that run
// in between now and the next refresh driver tick might see inconsistencies
// between the timing of an animation and the computed style of its target.
mFastForwardTime = aTimeStamp;
}
TimeStamp
AnimationTimeline::GetCurrentTimeStamp() const
{
// Always return the same object to benefit from return-value optimization.
TimeStamp result = mLastCurrentTime;
nsRefreshDriver* refreshDriver = GetRefreshDriver();
TimeStamp refreshTime = refreshDriver
? refreshDriver->MostRecentRefresh()
: TimeStamp();
// If we've never been sampled, initialize the current time to the timeline's
// zero time since that is the time we'll use if we don't have a refresh
// driver.
// Always return the same object to benefit from return-value optimization.
TimeStamp result = !refreshTime.IsNull()
? refreshTime
: mLastRefreshDriverTime;
// If we don't have a refresh driver and we've never had one use the
// timeline's zero time.
if (result.IsNull()) {
nsRefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
if (!timing) {
return result;
if (timing) {
result = timing->GetNavigationStartTimeStamp();
// Also, let this time represent the current refresh time. This way
// we'll save it as the last refresh time and skip looking up
// navigation timing each time.
refreshTime = result;
}
result = timing->GetNavigationStartTimeStamp();
}
nsRefreshDriver* refreshDriver = GetRefreshDriver();
if (!refreshDriver) {
return result;
// The timeline may have been fast-forwarded to account for animations
// that begin playing between ticks of the refresh driver. If so, we should
// use the fast-forward time unless we've already gone past that time.
//
// (If the refresh driver were ever to go backwards then we would need to
// ignore the fast-forward time in that case to prevent the timeline getting
// "stuck" until the refresh driver caught up. However, the only time the
// refresh driver goes backwards is when it is restored from test control
// and FastForward makes sure we don't set the fast foward time when we
// are under test control.)
MOZ_ASSERT(refreshTime.IsNull() || mLastRefreshDriverTime.IsNull() ||
refreshTime >= mLastRefreshDriverTime ||
mFastForwardTime.IsNull(),
"The refresh driver time should not go backwards when the"
" fast-forward time is set");
// We need to check if mFastForwardTime is ahead of the refresh driver
// time. This is because mFastForwardTime can still be set after the next
// refresh driver tick since we don't clear mFastForwardTime on a call to
// Tick() as we aren't currently guaranteed to get only one call to Tick()
// per refresh-driver tick.
if (result.IsNull() ||
(!mFastForwardTime.IsNull() && mFastForwardTime > result)) {
result = mFastForwardTime;
} else {
// Make sure we continue to ignore the fast-forward time.
mFastForwardTime = TimeStamp();
}
if (!refreshTime.IsNull()) {
mLastRefreshDriverTime = refreshTime;
}
result = refreshDriver->MostRecentRefresh();
// FIXME: We would like to assert that:
// mLastCurrentTime.IsNull() || result >= mLastCurrentTime
// but due to bug 1043078 this will not be the case when the refresh driver
// is restored from test control.
mLastCurrentTime = result;
return result;
}

View File

@ -50,16 +50,36 @@ public:
Nullable<TimeDuration> ToTimelineTime(const TimeStamp& aTimeStamp) const;
TimeStamp ToTimeStamp(const TimeDuration& aTimelineTime) const;
// Force the timeline to advance to |aTimeStamp|.
//
// Normally the timeline uses the refresh driver time but when we have
// animations that are timed from when their first frame is rendered we need
// to bring the timeline forward to that moment. If we don't, calling
// IsRunning() will incorrectly return false (because GetCurrentTime() will
// return a negative time) until the next refresh driver tick causes the
// timeline to catch up.
//
// |aTimeStamp| must be greater or equal to the current refresh driver
// time for the document with which this timeline is associated unless the
// refresh driver is under test control, in which case this method will
// be a no-op.
void FastForward(const TimeStamp& aTimeStamp);
nsRefreshDriver* GetRefreshDriver() const;
protected:
TimeStamp GetCurrentTimeStamp() const;
nsRefreshDriver* GetRefreshDriver() const;
nsCOMPtr<nsIDocument> mDocument;
// Store the most recently returned value of current time. This is used
// in cases where we don't have a refresh driver (e.g. because we are in
// a display:none iframe).
mutable TimeStamp mLastCurrentTime;
// The most recently used refresh driver time. This is used in cases where
// we don't have a refresh driver (e.g. because we are in a display:none
// iframe).
mutable TimeStamp mLastRefreshDriverTime;
// The time to which the timeline has been forced-to in order to account for
// animations that are started in-between frames.
mutable TimeStamp mFastForwardTime;
};
} // namespace dom

View File

@ -5,11 +5,15 @@
#include "PendingPlayerTracker.h"
#include "mozilla/dom/AnimationTimeline.h"
#include "nsIFrame.h"
#include "nsIPresShell.h"
using namespace mozilla;
namespace mozilla {
NS_IMPL_CYCLE_COLLECTION(PendingPlayerTracker, mPlayPendingSet)
NS_IMPL_CYCLE_COLLECTION(PendingPlayerTracker, mPlayPendingSet, mDocument)
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PendingPlayerTracker, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PendingPlayerTracker, Release)
@ -18,6 +22,11 @@ void
PendingPlayerTracker::AddPlayPending(dom::AnimationPlayer& aPlayer)
{
mPlayPendingSet.PutEntry(&aPlayer);
// Schedule a paint. Otherwise animations that don't trigger a paint by
// themselves (e.g. CSS animations with an empty keyframes rule) won't
// start until something else paints.
EnsurePaintIsScheduled();
}
void
@ -32,4 +41,56 @@ PendingPlayerTracker::IsWaitingToPlay(dom::AnimationPlayer const& aPlayer) const
return mPlayPendingSet.Contains(const_cast<dom::AnimationPlayer*>(&aPlayer));
}
PLDHashOperator
StartPlayerAtTime(nsRefPtrHashKey<dom::AnimationPlayer>* aKey,
void* aReadyTime)
{
dom::AnimationPlayer* player = aKey->GetKey();
// For animations that are waiting until their first frame has rendered
// before starting, we record the moment when they finish painting
// as the "ready time" and make any pending layer animations start at
// that time.
//
// Here we fast-forward the player's timeline to the same "ready time" and
// then tell the player to start at the timeline's current time.
//
// Redundant calls to FastForward with the same ready time are ignored by
// AnimationTimeline.
dom::AnimationTimeline* timeline = player->Timeline();
timeline->FastForward(*static_cast<const TimeStamp*>(aReadyTime));
player->StartNow();
return PL_DHASH_NEXT;
}
void
PendingPlayerTracker::StartPendingPlayers(const TimeStamp& aReadyTime)
{
mPlayPendingSet.EnumerateEntries(StartPlayerAtTime,
const_cast<TimeStamp*>(&aReadyTime));
mPlayPendingSet.Clear();
}
void
PendingPlayerTracker::EnsurePaintIsScheduled()
{
if (!mDocument) {
return;
}
nsIPresShell* presShell = mDocument->GetShell();
if (!presShell) {
return;
}
nsIFrame* rootFrame = presShell->GetRootFrame();
if (!rootFrame) {
return;
}
rootFrame->SchedulePaint();
}
} // namespace mozilla

View File

@ -8,13 +8,20 @@
#include "mozilla/dom/AnimationPlayer.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDocument.h"
#include "nsTHashtable.h"
class nsIFrame;
namespace mozilla {
class PendingPlayerTracker MOZ_FINAL
{
public:
explicit PendingPlayerTracker(nsIDocument* aDocument)
: mDocument(aDocument)
{ }
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PendingPlayerTracker)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(PendingPlayerTracker)
@ -22,13 +29,21 @@ public:
void RemovePlayPending(dom::AnimationPlayer& aPlayer);
bool IsWaitingToPlay(dom::AnimationPlayer const& aPlayer) const;
// Causes any pending players to resume at |aReadyTime| by first
// fast-forwarding their timeline to the corresponding time.
void StartPendingPlayers(const TimeStamp& aReadyTime);
bool HasPendingPlayers() const { return mPlayPendingSet.Count() > 0; }
private:
~PendingPlayerTracker() { }
void EnsurePaintIsScheduled();
typedef nsTHashtable<nsRefPtrHashKey<dom::AnimationPlayer>>
AnimationPlayerSet;
AnimationPlayerSet mPlayPendingSet;
nsCOMPtr<nsIDocument> mDocument;
};
} // namespace mozilla

View File

@ -20,6 +20,7 @@
#include "nsFrameManager.h"
#include "nsRefreshDriver.h"
#include "mozilla/dom/Touch.h"
#include "mozilla/PendingPlayerTracker.h"
#include "nsIObjectLoadingContent.h"
#include "nsFrame.h"
#include "mozilla/layers/ShadowLayers.h"
@ -2654,6 +2655,23 @@ nsDOMWindowUtils::AdvanceTimeAndRefresh(int64_t aMilliseconds)
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
nsRefreshDriver* driver = GetPresContext()->RefreshDriver();
// Before we advance the time, we should trigger any animations that are
// waiting to start. This is because there are many tests that call this
// which expect animations to start immediately. Ideally, we should make
// all these tests do an asynchronous wait on the corresponding animation
// player's 'ready' promise before continuing. Then we could remove the
// special handling here and the code path followed when testing would
// more closely match the code path during regular operation. Filed as
// bug 1112957.
nsCOMPtr<nsIDocument> doc = GetDocument();
if (doc) {
PendingPlayerTracker* tracker = doc->GetPendingPlayerTracker();
if (tracker) {
tracker->StartPendingPlayers(driver->MostRecentRefresh());
}
}
driver->AdvanceTimeAndRefresh(aMilliseconds);
RefPtr<LayerTransactionChild> transaction = GetLayerTransaction();

View File

@ -7397,7 +7397,7 @@ PendingPlayerTracker*
nsDocument::GetOrCreatePendingPlayerTracker()
{
if (!mPendingPlayerTracker) {
mPendingPlayerTracker = new PendingPlayerTracker();
mPendingPlayerTracker = new PendingPlayerTracker(this);
}
return mPendingPlayerTracker;

View File

@ -1614,11 +1614,6 @@ nsFocusManager::Blur(nsPIDOMWindow* aWindowToClear,
return true;
}
nsRefPtr<SelectionCarets> selectionCarets = presShell->GetSelectionCarets();
if (selectionCarets) {
selectionCarets->NotifyBlur();
}
bool clearFirstBlurEvent = false;
if (!mFirstBlurEvent) {
mFirstBlurEvent = content;
@ -1688,8 +1683,14 @@ nsFocusManager::Blur(nsPIDOMWindow* aWindowToClear,
// if we are leaving the document or the window was lowered, make the caret
// invisible.
if (aIsLeavingDocument || !mActiveWindow)
if (aIsLeavingDocument || !mActiveWindow) {
SetCaretVisible(presShell, false, nullptr);
nsRefPtr<SelectionCarets> selectionCarets = presShell->GetSelectionCarets();
if (selectionCarets) {
selectionCarets->NotifyBlur();
}
}
// at this point, it is expected that this window will be still be
// focused, but the focused content will be null, as it was cleared before

View File

@ -197,24 +197,6 @@
let savedElement = null;
function recvDomTest(message) {
savedElement = message.objects.element;
// Test to ensure that we don't pass CPOWs to C++-implemented interfaces.
// See bug 1072980.
if (test_state == "remote") {
let walker = Components.classes["@mozilla.org/inspector/deep-tree-walker;1"]
.createInstance(Components.interfaces.inIDeepTreeWalker);
const SHOW_ELEMENT = Components.interfaces.nsIDOMNodeFilter.SHOW_ELEMENT;
walker.showAnonymousContent = true;
walker.showSubDocuments = false;
try {
walker.init(savedElement, SHOW_ELEMENT);
ok(false, "expected exception passing CPOW to C++");
} catch (e) {
is(e.result, Components.results.NS_ERROR_XPC_CANT_PASS_CPOW_TO_NATIVE,
"got exception when passing CPOW to C++");
}
}
}
function recvDomTestAfterGC(message) {

View File

@ -1,12 +0,0 @@
<html>
<head>
<script>
window.addEventListener("focus", function() { window.close(); }, false);
function done() {
window.focus();
}
</script>
</head>
<body onload="done();">
</body>
</html>

View File

@ -5,7 +5,6 @@ support-files =
bug299673.js
bug322588-popup.html
bug426082.html
bug457672.html
bug656379-1.html
error_event_worker.js
empty.js
@ -53,7 +52,7 @@ skip-if = buildapp == 'mulet'
skip-if = buildapp == 'mulet'
[test_bug456273.html]
[test_bug457672.html]
skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #CRASH_DUMP, RANDOM
skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #CRASH_DUMP, RANDOM
[test_bug489671.html]
[test_bug493251.html]
skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage

View File

@ -48,7 +48,8 @@ function startTest() {
// Note, focus/blur don't bubble
window.addEventListener("focus", listener, false);
window.addEventListener("blur", listener, false);
window.open("bug457672.html", "", "");
var subwin = window.open("about:blank", "", "");
subwin.addEventListener("focus", function(e) { subwin.close(); }, false);
}
addLoadEvent(startTest);

View File

@ -7287,7 +7287,8 @@ HTMLInputElement::SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker)
nsTArray<nsFilePickerFilter> filters;
nsString allExtensionsList;
bool allFiltersAreValid = true;
bool allMimeTypeFiltersAreValid = true;
bool atLeastOneFileExtensionFilter = false;
// Retrieve all filters
while (tokenizer.hasMoreTokens()) {
@ -7314,6 +7315,10 @@ HTMLInputElement::SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker)
filterMask = nsIFilePicker::filterVideo;
filterBundle->GetStringFromName(MOZ_UTF16("videoFilter"),
getter_Copies(extensionListStr));
} else if (token.First() == '.') {
extensionListStr = NS_LITERAL_STRING("*") + token;
filterName = extensionListStr + NS_LITERAL_STRING("; ");
atLeastOneFileExtensionFilter = true;
} else {
//... if no image/audio/video filter is found, check mime types filters
nsCOMPtr<nsIMIMEInfo> mimeInfo;
@ -7322,7 +7327,7 @@ HTMLInputElement::SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker)
EmptyCString(), // No extension
getter_AddRefs(mimeInfo))) ||
!mimeInfo) {
allFiltersAreValid = false;
allMimeTypeFiltersAreValid = false;
continue;
}
@ -7355,7 +7360,7 @@ HTMLInputElement::SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker)
if (!filterMask && (extensionListStr.IsEmpty() || filterName.IsEmpty())) {
// No valid filter found
allFiltersAreValid = false;
allMimeTypeFiltersAreValid = false;
continue;
}
@ -7377,6 +7382,28 @@ HTMLInputElement::SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker)
}
}
// Remove similar filters
// Iterate over a copy, as we might modify the original filters list
nsTArray<nsFilePickerFilter> filtersCopy;
filtersCopy = filters;
for (uint32_t i = 0; i < filtersCopy.Length(); ++i) {
const nsFilePickerFilter& filterToCheck = filtersCopy[i];
if (filterToCheck.mFilterMask) {
continue;
}
for (uint32_t j = 0; j < filtersCopy.Length(); ++j) {
if (i == j) {
continue;
}
if (FindInReadable(filterToCheck.mFilter, filtersCopy[j].mFilter)) {
// We already have a similar, less restrictive filter (i.e.
// filterToCheck extensionList is just a subset of another filter
// extension list): remove this one
filters.RemoveElement(filterToCheck);
}
}
}
// Add "All Supported Types" filter
if (filters.Length() > 1) {
nsXPIDLString title;
@ -7395,9 +7422,8 @@ HTMLInputElement::SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker)
}
}
// If all filters are known/valid, select the first filter as default;
// otherwise filterAll will remain the default filter
if (filters.Length() >= 1 && allFiltersAreValid) {
if (filters.Length() >= 1 &&
(allMimeTypeFiltersAreValid || atLeastOneFileExtensionFilter)) {
// |filterAll| will always use index=0 so we need to set index=1 as the
// current filter.
filePicker->SetFilterIndex(1);

View File

@ -320,12 +320,20 @@ public:
*
* @note You should not call this function if the element has no @accept.
* @note "All Files" filter is always set, no matter if there is a valid
* filter specifed or not.
* @note If there is only one valid filter that is audio or video or image,
* it will be selected as the default filter. Otherwise "All files" remains
* the default filter.
* filter specified or not.
* @note If more than one valid filter is found, the "All Supported Types"
* filter is added, which is the concatenation of all valid filters.
* @note Duplicate filters and similar filters (i.e. filters whose file
* extensions already exist in another filter) are ignored.
* @note "All Files" filter will be selected by default if unknown mime types
* have been specified and no file extension filter has been specified.
* Otherwise, specified filter or "All Supported Types" filter will be
* selected by default.
* The logic behind is that having unknown mime type means we might restrict
* user's input too much, as some filters will be missing.
* However, if author has also specified some file extension filters, it's
* likely those are fallback for the unusual mime type we haven't been able
* to resolve; so it's better to select author specified filters in that case.
*/
void SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker);

View File

@ -101,6 +101,7 @@ static PRLogModuleInfo* gMediaElementEventsLog;
#include "nsIContentSecurityPolicy.h"
#include "mozilla/Preferences.h"
#include "mozilla/FloatingPoint.h"
#include "nsIPermissionManager.h"
#include "nsContentTypeParser.h"
@ -1369,7 +1370,7 @@ HTMLMediaElement::Seek(double aTime,
ErrorResult& aRv)
{
// aTime should be non-NaN.
MOZ_ASSERT(aTime == aTime);
MOZ_ASSERT(!mozilla::IsNaN(aTime));
StopSuspendingAfterFirstFrame();
@ -1493,7 +1494,7 @@ HTMLMediaElement::Seek(double aTime,
NS_IMETHODIMP HTMLMediaElement::SetCurrentTime(double aCurrentTime)
{
// Detect for a NaN and invalid values.
if (aCurrentTime != aCurrentTime) {
if (mozilla::IsNaN(aCurrentTime)) {
LOG(PR_LOG_DEBUG, ("%p SetCurrentTime(%f) failed: bad time", this, aCurrentTime));
return NS_ERROR_FAILURE;
}

View File

@ -32,6 +32,11 @@ function ok(condition, msg) {
// test failures, so work around it on Linux until we can get a proper fix.
const workAroundFullscreenTransition = navigator.userAgent.indexOf("Linux") != -1;
if (workAroundFullscreenTransition) {
SimpleTest.requestFlakyTimeout("We need to wait an arbitrary and non-zero " +
"amount of time in case of the Linux specific workaround to avoid busy-waiting.");
}
// Adds a listener that will be called once a fullscreen transition
// is complete. When type==='enter', callback is called when we've
// received a fullscreenchange event, and the fullscreen transition is

View File

@ -11,6 +11,7 @@
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=377624">Mozilla Bug 377624</a>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=565274">Mozilla Bug 565274</a>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=701353">Mozilla Bug 701353</a>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=826176">Mozilla Bug 826176</a>
<p id="display"></p>
<div id="content">
<input id='a' type='file' accept="image/*">
@ -27,7 +28,12 @@
<input id='l' type="file" accept="image/*,image/gif,image/png">
<input id='m' type="file" accept="image/gif,image/gif">
<input id='n' type="file" accept="">
<input id='o' type="file" accept=".test">
<input id='p' type="file" accept="image/gif,.csv">
<input id='q' type="file" accept="image/gif,.gif">
<input id='z' type='file' accept="i/am,a,pathological,;,,,,test/case">
<input id='mix-ref' type="file" accept="image/jpeg">
<input id='mix' type="file" accept="image/jpeg,.jpg">
<input id='hidden' hidden type='file'>
<input id='untrusted-click' type='file'>
<input id='prevent-default' type='file'>
@ -82,7 +88,13 @@ var testData = [["a", 1, MockFilePicker.filterImages, 1],
["l", 4, imageExtensionList + "; " + "*.gif; *.png", 1],
["m", 1, "*.gif", 1],
["n", 0, undefined, 0],
["o", 1, "*.test", 1],
["p", 3, "*.gif; *.csv", 1],
["q", 1, "*.gif", 1],
["z", 0, undefined, 0],
// Note: mix and mix-ref tests extension lists are checked differently: see SimpleTest.executeSoon below
["mix-ref", undefined, undefined, undefined],
["mix", 1, undefined, 1],
["hidden", 0, undefined, 0],
["untrusted-click", 0, undefined, 0],
["prevent-default", 0, undefined, 0, true],
@ -105,6 +117,7 @@ var currentTest = 0;
var filterAllAdded;
var filters;
var filterIndex;
var mixRefExtensionList;
// disable popups to make sure that the popup blocker does not interfere
// with manually opened file pickers.
@ -192,12 +205,31 @@ function runTests() {
"File picker show method should have been called (" + testName + ")");
ok(filterAllAdded,
"filterAll is missing (" + testName + ")");
is(filters.length, testData[currentTest][1],
"appendFilters not called as often as expected (" + testName + ")");
is(filters[0], testData[currentTest][2],
"Correct filters should have been added (" + testName + ")");
is(filterIndex, testData[currentTest][3],
"File picker should show the correct filter index (" + testName + ")");
if (testName == "mix-ref") {
// Used only for reference for next test: nothing to be tested here
mixRefExtensionList = filters[0];
if (mixRefExtensionList == undefined) {
mixRefExtensionList = "";
}
} else {
if (testName == "mix") {
// Mixing mime type and file extension filters ("image/jpeg" and
// ".jpg" here) shouldn't restrict the list but only extend it, if file
// extension filter isn't a duplicate
ok(filters[0].contains(mixRefExtensionList),
"Mixing mime types and file extension filters shouldn't restrict extension list: " +
mixRefExtensionList + " | " + filters[0]);
ok(filters[0].contains("*.jpg"),
"Filter should contain '.jpg' extension", filters[0]);
} else {
is(filters[0], testData[currentTest][2],
"Correct filters should have been added (" + testName + ")");
is(filters.length, testData[currentTest][1],
"appendFilters not called as often as expected (" + testName + ")");
}
is(filterIndex, testData[currentTest][3],
"File picker should show the correct filter index (" + testName + ")");
}
if (++currentTest == testData.length) {
MockFilePicker.cleanup();

View File

@ -13,6 +13,8 @@
#include "MediaQueue.h"
#include "AudioCompactor.h"
#include "mozilla/TypedEnum.h"
namespace mozilla {
namespace dom {
@ -22,6 +24,17 @@ class TimeRanges;
class MediaDecoderReader;
class SharedDecoderManager;
struct WaitForDataRejectValue {
enum Reason {
SHUTDOWN
};
WaitForDataRejectValue(MediaData::Type aType, Reason aReason)
:mType(aType), mReason(aReason) {}
MediaData::Type mType;
Reason mReason;
};
// Encapsulates the decoding and reading of media data. Reading can either
// synchronous and done on the calling "decode" thread, or asynchronous and
// performed on a background thread, with the result being returned by
@ -40,6 +53,7 @@ public:
typedef MediaPromise<nsRefPtr<AudioData>, NotDecodedReason> AudioDataPromise;
typedef MediaPromise<nsRefPtr<VideoData>, NotDecodedReason> VideoDataPromise;
typedef MediaPromise<bool, nsresult> SeekPromise;
typedef MediaPromise<MediaData::Type, WaitForDataRejectValue> WaitForDataPromise;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReader)
@ -113,6 +127,12 @@ public:
virtual nsRefPtr<VideoDataPromise>
RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold);
// By default, the state machine polls the reader once per second when it's
// in buffering mode. Some readers support a promise-based mechanism by which
// they notify the state machine when the data arrives.
virtual bool IsWaitForDataSupported() { return false; }
virtual nsRefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) { MOZ_CRASH(); }
virtual bool HasAudio() = 0;
virtual bool HasVideo() = 0;
@ -175,10 +195,11 @@ public:
virtual int64_t ComputeStartTime(const VideoData* aVideo, const AudioData* aAudio);
// Wait this number of seconds when buffering, then leave and play
// as best as we can if the required amount of data hasn't been
// retrieved.
virtual uint32_t GetBufferingWait() { return 30; }
// The MediaDecoderStateMachine uses various heuristics that assume that
// raw media data is arriving sequentially from a network channel. This
// makes sense in the <video src="foo"> case, but not for more advanced use
// cases like MSE.
virtual bool UseBufferingHeuristics() { return true; }
// Returns the number of bytes of memory allocated by structures/frames in
// the video queue.

View File

@ -109,6 +109,13 @@ const int64_t NO_VIDEO_AMPLE_AUDIO_DIVISOR = 8;
// which is at or after the current playback position.
static const uint32_t LOW_VIDEO_FRAMES = 1;
// Threshold in usecs that used to check if we are low on decoded video.
// If the last video frame's end time |mDecodedVideoEndTime| doesn't exceed
// |clock time + LOW_VIDEO_THRESHOLD_USECS*mPlaybackRate| calculation in
// Advanceframe(), we are low on decoded video frames and trying to skip to next
// keyframe.
static const int32_t LOW_VIDEO_THRESHOLD_USECS = 16000;
// Arbitrary "frame duration" when playing only audio.
static const int AUDIO_DURATION_USECS = 40000;
@ -189,14 +196,15 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
mAudioEndTime(-1),
mDecodedAudioEndTime(-1),
mVideoFrameEndTime(-1),
mDecodedVideoEndTime(-1),
mVolume(1.0),
mPlaybackRate(1.0),
mPreservesPitch(true),
mAmpleVideoFrames(2),
mLowAudioThresholdUsecs(LOW_AUDIO_USECS),
mAmpleAudioThresholdUsecs(AMPLE_AUDIO_USECS),
mAudioRequestPending(false),
mVideoRequestPending(false),
mAudioRequestStatus(RequestStatus::Idle),
mVideoRequestStatus(RequestStatus::Idle),
mAudioCaptured(false),
mPositionChangeQueued(false),
mAudioCompleted(false),
@ -221,7 +229,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
mAmpleVideoFrames =
std::max<uint32_t>(Preferences::GetUint("media.video-queue.default-size", 10), 3);
mBufferingWait = mScheduler->IsRealTime() ? 0 : mReader->GetBufferingWait();
mBufferingWait = mScheduler->IsRealTime() ? 0 : 30;
mLowDataThresholdUsecs = mScheduler->IsRealTime() ? 0 : LOW_DATA_THRESHOLD_USECS;
mVideoPrerollFrames = mScheduler->IsRealTime() ? 0 : mAmpleVideoFrames / 2;
@ -567,6 +575,47 @@ MediaDecoderStateMachine::NeedToDecodeVideo()
(!mMinimizePreroll && !HaveEnoughDecodedVideo()));
}
bool
MediaDecoderStateMachine::NeedToSkipToNextKeyframe()
{
AssertCurrentThreadInMonitor();
MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
mState == DECODER_STATE_BUFFERING ||
mState == DECODER_STATE_SEEKING);
// We are in seeking or buffering states, don't skip frame.
if (!IsVideoDecoding() || mState == DECODER_STATE_BUFFERING ||
mState == DECODER_STATE_SEEKING) {
return false;
}
// Don't skip frame for video-only decoded stream because the clock time of
// the stream relies on the video frame.
if (mDecoder->GetDecodedStream() && !HasAudio()) {
DECODER_LOG("Video-only decoded stream, set skipToNextKeyFrame to false");
return false;
}
// We'll skip the video decode to the nearest keyframe if we're low on
// audio, or if we're low on video, provided we're not running low on
// data to decode. If we're running low on downloaded data to decode,
// we won't start keyframe skipping, as we'll be pausing playback to buffer
// soon anyway and we'll want to be able to display frames immediately
// after buffering finishes.
bool isLowOnDecodedAudio = !mIsAudioPrerolling && IsAudioDecoding() &&
(GetDecodedAudioDuration() <
mLowAudioThresholdUsecs * mPlaybackRate);
bool isLowOnDecodedVideo = !mIsVideoPrerolling &&
(mDecodedVideoEndTime - GetClock() <
LOW_VIDEO_THRESHOLD_USECS * mPlaybackRate);
if ((isLowOnDecodedAudio || isLowOnDecodedVideo) && !HasLowUndecodedData()) {
DECODER_LOG("Skipping video decode to the next keyframe");
return true;
}
return false;
}
void
MediaDecoderStateMachine::DecodeVideo()
{
@ -579,7 +628,7 @@ MediaDecoderStateMachine::DecodeVideo()
if (mState != DECODER_STATE_DECODING &&
mState != DECODER_STATE_BUFFERING &&
mState != DECODER_STATE_SEEKING) {
mVideoRequestPending = false;
mVideoRequestStatus = RequestStatus::Idle;
DispatchDecodeTasksIfNeeded();
return;
}
@ -594,27 +643,8 @@ MediaDecoderStateMachine::DecodeVideo()
mIsVideoPrerolling = false;
}
// We'll skip the video decode to the nearest keyframe if we're low on
// audio, or if we're low on video, provided we're not running low on
// data to decode. If we're running low on downloaded data to decode,
// we won't start keyframe skipping, as we'll be pausing playback to buffer
// soon anyway and we'll want to be able to display frames immediately
// after buffering finishes.
if (mState == DECODER_STATE_DECODING &&
IsVideoDecoding() &&
((!mIsAudioPrerolling && IsAudioDecoding() &&
GetDecodedAudioDuration() < mLowAudioThresholdUsecs * mPlaybackRate) ||
(!mIsVideoPrerolling && IsVideoDecoding() &&
// don't skip frame when |clock time| <= |mVideoFrameEndTime| for
// we are still in the safe range without underrunning video frames
GetClock() > mVideoFrameEndTime &&
(static_cast<uint32_t>(VideoQueue().GetSize())
< LOW_VIDEO_FRAMES * mPlaybackRate))) &&
!HasLowUndecodedData())
{
skipToNextKeyFrame = true;
DECODER_LOG("Skipping video decode to the next keyframe");
}
skipToNextKeyFrame = NeedToSkipToNextKeyframe();
currentTime = mState == DECODER_STATE_SEEKING ? 0 : GetMediaTime();
// Time the video decode, so that if it's slow, we can increase our low
@ -658,7 +688,7 @@ MediaDecoderStateMachine::DecodeAudio()
if (mState != DECODER_STATE_DECODING &&
mState != DECODER_STATE_BUFFERING &&
mState != DECODER_STATE_SEEKING) {
mAudioRequestPending = false;
mAudioRequestStatus = RequestStatus::Idle;
DispatchDecodeTasksIfNeeded();
mon.NotifyAll();
return;
@ -713,7 +743,7 @@ MediaDecoderStateMachine::OnAudioDecoded(AudioData* aAudioSample)
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
nsRefPtr<AudioData> audio(aAudioSample);
MOZ_ASSERT(audio);
mAudioRequestPending = false;
mAudioRequestStatus = RequestStatus::Idle;
mDecodedAudioEndTime = audio->GetEndTime();
SAMPLE_LOG("OnAudioDecoded [%lld,%lld] disc=%d",
@ -729,8 +759,10 @@ MediaDecoderStateMachine::OnAudioDecoded(AudioData* aAudioSample)
}
case DECODER_STATE_BUFFERING:
// If we're buffering, this may be the sample we need to stop buffering.
// Schedule the state machine and then fall through.
ScheduleStateMachine();
case DECODER_STATE_DECODING: {
// In buffering and decoding state, we simply enqueue samples.
Push(audio);
return;
}
@ -825,11 +857,7 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
MOZ_ASSERT_IF(!isAudio, aType == MediaData::VIDEO_DATA);
// This callback means that the pending request is dead.
if (isAudio) {
mAudioRequestPending = false;
} else {
mVideoRequestPending = false;
}
RequestStatusRef(aType) = RequestStatus::Idle;
// If this is a decode error, delegate to the generic error path.
if (aReason == MediaDecoderReader::DECODE_ERROR) {
@ -837,15 +865,15 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
return;
}
// If the decoder is waiting for data, we need to make sure that the requests
// are cleared, which happened above. Additionally, if we're out of decoded
// samples, we need to switch to buffering mode.
// If the decoder is waiting for data, we tell it to call us back when the
// data arrives.
if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
bool outOfSamples = isAudio ? !AudioQueue().GetSize() : !VideoQueue().GetSize();
if (outOfSamples) {
StartBuffering();
}
MOZ_ASSERT(mReader->IsWaitForDataSupported(),
"Readers that send WAITING_FOR_DATA need to implement WaitForData");
RequestStatusRef(aType) = RequestStatus::Waiting;
mReader->WaitForData(aType)->Then(DecodeTaskQueue(), __func__, this,
&MediaDecoderStateMachine::OnWaitForDataResolved,
&MediaDecoderStateMachine::OnWaitForDataRejected);
return;
}
@ -930,7 +958,8 @@ MediaDecoderStateMachine::OnVideoDecoded(VideoData* aVideoSample)
MOZ_ASSERT(OnDecodeThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
nsRefPtr<VideoData> video(aVideoSample);
mVideoRequestPending = false;
mVideoRequestStatus = RequestStatus::Idle;
mDecodedVideoEndTime = video ? video->GetEndTime() : mDecodedVideoEndTime;
SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d",
(video ? video->mTime : -1),
@ -945,6 +974,9 @@ MediaDecoderStateMachine::OnVideoDecoded(VideoData* aVideoSample)
}
case DECODER_STATE_BUFFERING:
// If we're buffering, this may be the sample we need to stop buffering.
// Schedule the state machine and then fall through.
ScheduleStateMachine();
case DECODER_STATE_DECODING: {
Push(video);
// If the requested video sample was slow to arrive, increase the
@ -1505,6 +1537,7 @@ void MediaDecoderStateMachine::ResetPlayback()
MOZ_ASSERT(!mAudioSink);
mVideoFrameEndTime = -1;
mDecodedVideoEndTime = -1;
mAudioStartTime = -1;
mAudioEndTime = -1;
mDecodedAudioEndTime = -1;
@ -1745,9 +1778,9 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
!needToDecodeVideo &&
!IsPlaying();
SAMPLE_LOG("DispatchDecodeTasksIfNeeded needAudio=%d dispAudio=%d needVideo=%d dispVid=%d needIdle=%d",
needToDecodeAudio, mAudioRequestPending,
needToDecodeVideo, mVideoRequestPending,
SAMPLE_LOG("DispatchDecodeTasksIfNeeded needAudio=%d audioStatus=%d needVideo=%d videoStatus=%d needIdle=%d",
needToDecodeAudio, mAudioRequestStatus,
needToDecodeVideo, mVideoRequestStatus,
needIdle);
if (needToDecodeAudio) {
@ -1817,8 +1850,8 @@ MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine or decode thread.");
SAMPLE_LOG("EnsureAudioDecodeTaskQueued isDecoding=%d dispatched=%d",
IsAudioDecoding(), mAudioRequestPending);
SAMPLE_LOG("EnsureAudioDecodeTaskQueued isDecoding=%d status=%d",
IsAudioDecoding(), mAudioRequestStatus);
if (mState >= DECODER_STATE_COMPLETED) {
return NS_OK;
@ -1826,12 +1859,12 @@ MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
MOZ_ASSERT(mState > DECODER_STATE_DECODING_FIRSTFRAME);
if (IsAudioDecoding() && !mAudioRequestPending && !mWaitingForDecoderSeek) {
if (IsAudioDecoding() && mAudioRequestStatus == RequestStatus::Idle && !mWaitingForDecoderSeek) {
RefPtr<nsIRunnable> task(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeAudio));
nsresult rv = DecodeTaskQueue()->Dispatch(task);
if (NS_SUCCEEDED(rv)) {
mAudioRequestPending = true;
mAudioRequestStatus = RequestStatus::Pending;
} else {
DECODER_WARN("Failed to dispatch task to decode audio");
}
@ -1859,8 +1892,8 @@ MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
{
AssertCurrentThreadInMonitor();
SAMPLE_LOG("EnsureVideoDecodeTaskQueued isDecoding=%d dispatched=%d",
IsVideoDecoding(), mVideoRequestPending);
SAMPLE_LOG("EnsureVideoDecodeTaskQueued isDecoding=%d status=%d",
IsVideoDecoding(), mVideoRequestStatus);
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine or decode thread.");
@ -1871,12 +1904,12 @@ MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
MOZ_ASSERT(mState > DECODER_STATE_DECODING_FIRSTFRAME);
if (IsVideoDecoding() && !mVideoRequestPending && !mWaitingForDecoderSeek) {
if (IsVideoDecoding() && mVideoRequestStatus == RequestStatus::Idle && !mWaitingForDecoderSeek) {
RefPtr<nsIRunnable> task(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeVideo));
nsresult rv = DecodeTaskQueue()->Dispatch(task);
if (NS_SUCCEEDED(rv)) {
mVideoRequestPending = true;
mVideoRequestStatus = RequestStatus::Pending;
} else {
DECODER_WARN("Failed to dispatch task to decode video");
}
@ -1930,6 +1963,7 @@ int64_t MediaDecoderStateMachine::AudioDecodedUsecs()
bool MediaDecoderStateMachine::HasLowDecodedData(int64_t aAudioUsecs)
{
AssertCurrentThreadInMonitor();
MOZ_ASSERT(mReader->UseBufferingHeuristics());
// We consider ourselves low on decoded data if we're low on audio,
// provided we've not decoded to the end of the audio stream, or
// if we're low on video frames, provided
@ -2626,28 +2660,41 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
TimeStamp now = TimeStamp::Now();
NS_ASSERTION(!mBufferingStart.IsNull(), "Must know buffering start time.");
// We will remain in the buffering state if we've not decoded enough
// data to begin playback, or if we've not downloaded a reasonable
// amount of data inside our buffering time.
TimeDuration elapsed = now - mBufferingStart;
bool isLiveStream = resource->GetLength() == -1;
if ((isLiveStream || !mDecoder->CanPlayThrough()) &&
elapsed < TimeDuration::FromSeconds(mBufferingWait * mPlaybackRate) &&
(mQuickBuffering ? HasLowDecodedData(QUICK_BUFFERING_LOW_DATA_USECS)
: HasLowUndecodedData(mBufferingWait * USECS_PER_S)) &&
mDecoder->IsExpectingMoreData())
{
DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s",
mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
(mQuickBuffering ? "(quick exit)" : ""));
ScheduleStateMachine(USECS_PER_S);
// With buffering heuristics we will remain in the buffering state if
// we've not decoded enough data to begin playback, or if we've not
// downloaded a reasonable amount of data inside our buffering time.
if (mReader->UseBufferingHeuristics()) {
TimeDuration elapsed = now - mBufferingStart;
bool isLiveStream = resource->GetLength() == -1;
if ((isLiveStream || !mDecoder->CanPlayThrough()) &&
elapsed < TimeDuration::FromSeconds(mBufferingWait * mPlaybackRate) &&
(mQuickBuffering ? HasLowDecodedData(QUICK_BUFFERING_LOW_DATA_USECS)
: HasLowUndecodedData(mBufferingWait * USECS_PER_S)) &&
mDecoder->IsExpectingMoreData())
{
DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s",
mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
(mQuickBuffering ? "(quick exit)" : ""));
ScheduleStateMachine(USECS_PER_S);
return NS_OK;
}
} else if (OutOfDecodedAudio() || OutOfDecodedVideo()) {
MOZ_ASSERT(mReader->IsWaitForDataSupported(),
"Don't yet have a strategy for non-heuristic + non-WaitForData");
DispatchDecodeTasksIfNeeded();
MOZ_ASSERT_IF(OutOfDecodedAudio(), mAudioRequestStatus != RequestStatus::Idle);
MOZ_ASSERT_IF(OutOfDecodedVideo(), mVideoRequestStatus != RequestStatus::Idle);
DECODER_LOG("In buffering mode, waiting to be notified: outOfAudio: %d, "
"mAudioStatus: %d, outOfVideo: %d, mVideoStatus: %d",
OutOfDecodedAudio(), mAudioRequestStatus,
OutOfDecodedVideo(), mVideoRequestStatus);
return NS_OK;
} else {
DECODER_LOG("Changed state from BUFFERING to DECODING");
DECODER_LOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
StartDecoding();
}
DECODER_LOG("Changed state from BUFFERING to DECODING");
DECODER_LOG("Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
StartDecoding();
// Notify to allow blocked decoder thread to continue
mDecoder->GetReentrantMonitor().NotifyAll();
UpdateReadyState();
@ -2747,8 +2794,8 @@ MediaDecoderStateMachine::FlushDecoding()
// These flags will be reset when the decoded data returned in OnAudioDecoded()
// and OnVideoDecoded(). Because the decode tasks are flushed, these flags need
// to be reset here.
mAudioRequestPending = false;
mVideoRequestPending = false;
mAudioRequestStatus = RequestStatus::Idle;
mVideoRequestStatus = RequestStatus::Idle;
// We must reset playback so that all references to frames queued
// in the state machine are dropped, else subsequent calls to Shutdown()
@ -2904,9 +2951,16 @@ void MediaDecoderStateMachine::AdvanceFrame()
// If we don't, switch to buffering mode.
if (mState == DECODER_STATE_DECODING &&
mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING &&
HasLowDecodedData(remainingTime + EXHAUSTED_DATA_MARGIN_USECS) &&
mDecoder->IsExpectingMoreData()) {
if (JustExitedQuickBuffering() || HasLowUndecodedData()) {
bool shouldBuffer;
if (mReader->UseBufferingHeuristics()) {
shouldBuffer = HasLowDecodedData(remainingTime + EXHAUSTED_DATA_MARGIN_USECS) &&
(JustExitedQuickBuffering() || HasLowUndecodedData());
} else {
MOZ_ASSERT(mReader->IsWaitForDataSupported());
shouldBuffer = OutOfDecodedAudio() || OutOfDecodedVideo();
}
if (shouldBuffer) {
if (currentFrame) {
VideoQueue().PushFront(currentFrame);
}

View File

@ -389,6 +389,23 @@ public:
void OnSeekCompleted();
void OnSeekFailed(nsresult aResult);
void OnWaitForDataResolved(MediaData::Type aType)
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
if (RequestStatusRef(aType) == RequestStatus::Waiting) {
RequestStatusRef(aType) = RequestStatus::Idle;
DispatchDecodeTasksIfNeeded();
}
}
void OnWaitForDataRejected(WaitForDataRejectValue aRejection)
{
MOZ_ASSERT(aRejection.mReason == WaitForDataRejectValue::SHUTDOWN);
if (RequestStatusRef(aRejection.mType) == RequestStatus::Waiting) {
RequestStatusRef(aRejection.mType) = RequestStatus::Idle;
}
}
private:
void AcquireMonitorAndInvokeDecodeError();
@ -462,8 +479,23 @@ protected:
// Returns true if we've got less than aAudioUsecs microseconds of decoded
// and playable data. The decoder monitor must be held.
//
// May not be invoked when mReader->UseBufferingHeuristics() is false.
bool HasLowDecodedData(int64_t aAudioUsecs);
bool OutOfDecodedAudio()
{
return IsAudioDecoding() && !AudioQueue().IsFinished() && AudioQueue().GetSize() == 0;
}
bool OutOfDecodedVideo()
{
// In buffering mode, we keep the last already-played frame in the queue.
int emptyVideoSize = mState == DECODER_STATE_BUFFERING ? 1 : 0;
return IsVideoDecoding() && !VideoQueue().IsFinished() && VideoQueue().GetSize() <= emptyVideoSize;
}
// Returns true if we're running low on data which is not yet decoded.
// The decoder monitor must be held.
bool HasLowUndecodedData();
@ -710,6 +742,10 @@ protected:
// DECODER_STATE_WAIT_FOR_RESOURCES.
void DoNotifyWaitingForResourcesStatusChanged();
// Return true if the video decoder's decode speed can not catch up the
// play time.
bool NeedToSkipToNextKeyframe();
// The decoder object that created this state machine. The state machine
// holds a strong reference to the decoder to ensure that the decoder stays
// alive once media element has started the decoder shutdown process, and has
@ -843,6 +879,10 @@ protected:
// in microseconds. Accessed from the state machine thread.
int64_t mVideoFrameEndTime;
// The end time of the last decoded video frame. Used to check if we are low
// on decoded video data.
int64_t mDecodedVideoEndTime;
// Volume of playback. 0.0 = muted. 1.0 = full volume. Read/Written
// from the state machine and main threads. Synchronised via decoder
// monitor.
@ -911,11 +951,22 @@ protected:
bool mIsAudioPrerolling;
bool mIsVideoPrerolling;
MOZ_BEGIN_NESTED_ENUM_CLASS(RequestStatus)
Idle,
Pending,
Waiting
MOZ_END_NESTED_ENUM_CLASS(RequestStatus)
// True when we have dispatched a task to the decode task queue to request
// decoded audio/video, and/or we are waiting for the requested sample to be
// returned by callback from the Reader.
bool mAudioRequestPending;
bool mVideoRequestPending;
RequestStatus mAudioRequestStatus;
RequestStatus mVideoRequestStatus;
RequestStatus& RequestStatusRef(MediaData::Type aType)
{
return aType == MediaData::AUDIO_DATA ? mAudioRequestStatus : mVideoRequestStatus;
}
// True if we shouldn't play our audio (but still write it to any capturing
// streams). When this is true, mStopAudioThread is always true and

View File

@ -61,7 +61,6 @@ public:
: mCreationSite(aCreationSite)
, mMutex("MediaPromise Mutex")
{
MOZ_COUNT_CTOR(MediaPromise);
PROMISE_LOG("%s creating MediaPromise (%p)", mCreationSite, this);
}
@ -99,14 +98,10 @@ protected:
public:
ResolveRunnable(ThenValueBase* aThenValue, ResolveValueType aResolveValue)
: mThenValue(aThenValue)
, mResolveValue(aResolveValue)
{
MOZ_COUNT_CTOR(ResolveRunnable);
}
, mResolveValue(aResolveValue) {}
~ResolveRunnable()
{
MOZ_COUNT_DTOR(ResolveRunnable);
MOZ_ASSERT(!mThenValue);
}
@ -130,14 +125,10 @@ protected:
public:
RejectRunnable(ThenValueBase* aThenValue, RejectValueType aRejectValue)
: mThenValue(aThenValue)
, mRejectValue(aRejectValue)
{
MOZ_COUNT_CTOR(RejectRunnable);
}
, mRejectValue(aRejectValue) {}
~RejectRunnable()
{
MOZ_COUNT_DTOR(RejectRunnable);
MOZ_ASSERT(!mThenValue);
}
@ -327,7 +318,6 @@ protected:
~MediaPromise()
{
MOZ_COUNT_DTOR(MediaPromise);
PROMISE_LOG("MediaPromise::~MediaPromise [this=%p]", this);
MOZ_ASSERT(!IsPending());
MOZ_ASSERT(mThenValues.IsEmpty());

View File

@ -157,7 +157,7 @@ MP4Reader::Init(MediaDecoderReader* aCloneDonor)
{
MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
PlatformDecoderModule::Init();
mDemuxer = new MP4Demuxer(new MP4Stream(mDecoder->GetResource()));
mDemuxer = new MP4Demuxer(new MP4Stream(mDecoder->GetResource(), &mIndexMonitor), &mIndexMonitor);
InitLayersBackendType();
@ -294,11 +294,10 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags)
{
if (!mDemuxerInitialized) {
bool ok = mDemuxer->Init();
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
{
MonitorAutoLock mon(mIndexMonitor);
bool ok = mDemuxer->Init();
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
mIndexReady = true;
}
@ -632,6 +631,7 @@ MP4Reader::ReturnOutput(MediaData* aData, TrackType aTrack)
MP4Sample*
MP4Reader::PopSample(TrackType aTrack)
{
MonitorAutoLock mon(mIndexMonitor);
switch (aTrack) {
case kAudio:
return mDemuxer->DemuxAudioSample();

View File

@ -9,7 +9,9 @@
namespace mozilla {
MP4Stream::MP4Stream(MediaResource* aResource) : mResource(aResource)
MP4Stream::MP4Stream(MediaResource* aResource, Monitor* aDemuxerMonitor)
: mResource(aResource)
, mDemuxerMonitor(aDemuxerMonitor)
{
MOZ_COUNT_CTOR(MP4Stream);
MOZ_ASSERT(aResource);
@ -24,6 +26,12 @@ bool
MP4Stream::ReadAt(int64_t aOffset, void* aBuffer, size_t aCount,
size_t* aBytesRead)
{
// The read call can acquire various monitors, including both the decoder
// monitor and gMediaCache's monitor. So we need to unlock ours to avoid
// deadlock.
mDemuxerMonitor->AssertCurrentThreadOwns();
MonitorAutoUnlock unlock(*mDemuxerMonitor);
uint32_t sum = 0;
uint32_t bytesRead = 0;
do {
@ -44,6 +52,12 @@ bool
MP4Stream::CachedReadAt(int64_t aOffset, void* aBuffer, size_t aCount,
size_t* aBytesRead)
{
// The read call can acquire various monitors, including both the decoder
// monitor and gMediaCache's monitor. So we need to unlock ours to avoid
// deadlock.
mDemuxerMonitor->AssertCurrentThreadOwns();
MonitorAutoUnlock unlock(*mDemuxerMonitor);
nsresult rv = mResource->ReadFromCache(reinterpret_cast<char*>(aBuffer),
aOffset, aCount);
if (NS_FAILED(rv)) {

View File

@ -9,13 +9,15 @@
#include "mp4_demuxer/mp4_demuxer.h"
#include "mozilla/Monitor.h"
namespace mozilla {
class MediaResource;
class MP4Stream : public mp4_demuxer::Stream {
public:
explicit MP4Stream(MediaResource* aResource);
explicit MP4Stream(MediaResource* aResource, Monitor* aDemuxerMonitor);
virtual ~MP4Stream();
virtual bool ReadAt(int64_t aOffset, void* aBuffer, size_t aCount,
size_t* aBytesRead) MOZ_OVERRIDE;
@ -25,6 +27,7 @@ public:
private:
nsRefPtr<MediaResource> mResource;
Monitor* mDemuxerMonitor;
};
}

View File

@ -18,11 +18,13 @@ public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MP4DemuxerBinding);
nsRefPtr<MockMediaResource> resource;
Monitor mMonitor;
nsAutoPtr<MP4Demuxer> demuxer;
explicit MP4DemuxerBinding(const char* aFileName = "dash_dashinit.mp4")
: resource(new MockMediaResource(aFileName))
, demuxer(new MP4Demuxer(new MP4Stream(resource)))
, mMonitor("TestMP4Demuxer monitor")
, demuxer(new MP4Demuxer(new MP4Stream(resource, &mMonitor), &mMonitor))
{
EXPECT_EQ(NS_OK, resource->Open(nullptr));
}
@ -36,6 +38,7 @@ private:
TEST(MP4Demuxer, Seek)
{
nsRefPtr<MP4DemuxerBinding> b = new MP4DemuxerBinding();
MonitorAutoLock mon(b->mMonitor);
MP4Demuxer* d = b->demuxer;
EXPECT_TRUE(d->Init());
@ -87,6 +90,7 @@ ToCryptoString(CryptoSample& aCrypto)
TEST(MP4Demuxer, CENC)
{
nsRefPtr<MP4DemuxerBinding> b = new MP4DemuxerBinding("short-cenc.mp4");
MonitorAutoLock mon(b->mMonitor);
MP4Demuxer* d = b->demuxer;
EXPECT_TRUE(d->Init());
@ -147,6 +151,7 @@ TEST(MP4Demuxer, CENC)
TEST(MP4Demuxer, CENCFrag)
{
nsRefPtr<MP4DemuxerBinding> b = new MP4DemuxerBinding("gizmo-frag.mp4");
MonitorAutoLock mon(b->mMonitor);
MP4Demuxer* d = b->demuxer;
EXPECT_TRUE(d->Init());

View File

@ -203,7 +203,7 @@ private:
class MP4ContainerParser : public ContainerParser {
public:
MP4ContainerParser() {}
MP4ContainerParser() :mMonitor("MP4ContainerParser Index Monitor") {}
bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
{
@ -244,10 +244,12 @@ public:
bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
int64_t& aStart, int64_t& aEnd)
{
MonitorAutoLock mon(mMonitor); // We're not actually racing against anything,
// but mParser requires us to hold a monitor.
bool initSegment = IsInitSegmentPresent(aData, aLength);
if (initSegment) {
mStream = new mp4_demuxer::BufferStream();
mParser = new mp4_demuxer::MoofParser(mStream, 0);
mParser = new mp4_demuxer::MoofParser(mStream, 0, &mMonitor);
} else if (!mStream || !mParser) {
return false;
}
@ -291,6 +293,7 @@ public:
private:
nsRefPtr<mp4_demuxer::BufferStream> mStream;
nsAutoPtr<mp4_demuxer::MoofParser> mParser;
Monitor mMonitor;
};
/*static*/ ContainerParser*

View File

@ -66,8 +66,7 @@ MediaSourceDecoder::Load(nsIStreamListener**, MediaDecoder*)
NS_ENSURE_SUCCESS(rv, rv);
SetStateMachineParameters();
return NS_OK;
return ScheduleStateMachineThread();
}
nsresult

View File

@ -345,6 +345,9 @@ MediaSourceReader::ContinueShutdown()
MOZ_ASSERT(mAudioPromise.IsEmpty());
MOZ_ASSERT(mVideoPromise.IsEmpty());
mAudioWaitPromise.RejectIfExists(WaitForDataRejectValue(MediaData::AUDIO_DATA, WaitForDataRejectValue::SHUTDOWN), __func__);
mVideoWaitPromise.RejectIfExists(WaitForDataRejectValue(MediaData::VIDEO_DATA, WaitForDataRejectValue::SHUTDOWN), __func__);
MediaDecoderReader::Shutdown()->ChainTo(mMediaSourceShutdownPromise.Steal(), __func__);
}
@ -391,6 +394,15 @@ MediaSourceReader::SelectReader(int64_t aTarget,
return nullptr;
}
bool
MediaSourceReader::HaveData(int64_t aTarget, MediaData::Type aType)
{
TrackBuffer* trackBuffer = aType == MediaData::AUDIO_DATA ? mAudioTrack : mVideoTrack;
MOZ_ASSERT(trackBuffer);
nsRefPtr<MediaDecoderReader> reader = SelectReader(aTarget, trackBuffer->Decoders());
return !!reader;
}
bool
MediaSourceReader::SwitchAudioReader(int64_t aTarget)
{
@ -720,17 +732,42 @@ MediaSourceReader::GetBuffered(dom::TimeRanges* aBuffered)
return NS_OK;
}
nsRefPtr<MediaDecoderReader::WaitForDataPromise>
MediaSourceReader::WaitForData(MediaData::Type aType)
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
nsRefPtr<WaitForDataPromise> p = WaitPromise(aType).Ensure(__func__);
MaybeNotifyHaveData();
return p;
}
void
MediaSourceReader::MaybeNotifyHaveData()
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
bool haveAudio = false, haveVideo = false;
if (!mAudioIsSeeking && mAudioTrack && HaveData(mLastAudioTime, MediaData::AUDIO_DATA)) {
haveAudio = true;
WaitPromise(MediaData::AUDIO_DATA).ResolveIfExists(MediaData::AUDIO_DATA, __func__);
}
if (!mVideoIsSeeking && mVideoTrack && HaveData(mLastVideoTime, MediaData::VIDEO_DATA)) {
haveVideo = true;
WaitPromise(MediaData::VIDEO_DATA).ResolveIfExists(MediaData::VIDEO_DATA, __func__);
}
MSE_DEBUG("MediaSourceReader(%p)::MaybeNotifyHaveData haveAudio=%d, haveVideo=%d", this,
haveAudio, haveVideo);
}
nsresult
MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
MSE_DEBUG("MediaSourceReader(%p)::ReadMetadata tracks=%u/%u audio=%p video=%p",
this, mEssentialTrackBuffers.Length(), mTrackBuffers.Length(),
mAudioTrack.get(), mVideoTrack.get());
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mEssentialTrackBuffers.Clear();
}
mEssentialTrackBuffers.Clear();
if (!mAudioTrack && !mVideoTrack) {
MSE_DEBUG("MediaSourceReader(%p)::ReadMetadata missing track: mAudioTrack=%p mVideoTrack=%p",
this, mAudioTrack.get(), mVideoTrack.get());

View File

@ -61,6 +61,10 @@ public:
void OnSeekCompleted();
void OnSeekFailed(nsresult aResult);
virtual bool IsWaitForDataSupported() MOZ_OVERRIDE { return true; }
virtual nsRefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) MOZ_OVERRIDE;
void MaybeNotifyHaveData();
bool HasVideo() MOZ_OVERRIDE
{
return mInfo.HasVideo();
@ -78,11 +82,11 @@ public:
// as chrome/blink and assumes that we always start at t=0.
virtual int64_t ComputeStartTime(const VideoData* aVideo, const AudioData* aAudio) MOZ_OVERRIDE { return 0; }
// Buffering waits (in which we decline to present decoded frames because we
// "don't have enough") don't really make sense for MSE. The delay is
// essentially a streaming heuristic, but JS is supposed to take care of that
// in the MSE world. Avoid injecting inexplicable delays.
virtual uint32_t GetBufferingWait() { return 0; }
// Buffering heuristics don't make sense for MSE, because the arrival of data
// is at least partly controlled by javascript, and javascript does not expect
// us to sit on unplayed data just because it may not be enough to play
// through.
bool UseBufferingHeuristics() MOZ_OVERRIDE { return false; }
bool IsMediaSeekable() { return true; }
@ -132,6 +136,7 @@ private:
// available in the range requested by aTarget.
already_AddRefed<MediaDecoderReader> SelectReader(int64_t aTarget,
const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders);
bool HaveData(int64_t aTarget, MediaData::Type aType);
void AttemptSeek();
void FinalizeSeek();
@ -148,6 +153,13 @@ private:
MediaPromiseHolder<AudioDataPromise> mAudioPromise;
MediaPromiseHolder<VideoDataPromise> mVideoPromise;
MediaPromiseHolder<WaitForDataPromise> mAudioWaitPromise;
MediaPromiseHolder<WaitForDataPromise> mVideoWaitPromise;
MediaPromiseHolder<WaitForDataPromise>& WaitPromise(MediaData::Type aType)
{
return aType == MediaData::AUDIO_DATA ? mAudioWaitPromise : mVideoWaitPromise;
}
#ifdef MOZ_EME
nsRefPtr<CDMProxy> mCDMProxy;
#endif

View File

@ -183,9 +183,9 @@ TrackBuffer::AppendData(const uint8_t* aData, uint32_t aLength)
return false;
}
// Schedule the state machine thread to ensure playback starts if required
// when data is appended.
mParentDecoder->ScheduleStateMachineThread();
// Tell our reader that we have more data to ensure that playback starts if
// required when data is appended.
mParentDecoder->GetReader()->MaybeNotifyHaveData();
return true;
}
@ -440,6 +440,11 @@ TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder)
RemoveDecoder(aDecoder);
return;
}
// Tell our reader that we have more data to ensure that playback starts if
// required when data is appended.
mParentDecoder->GetReader()->MaybeNotifyHaveData();
MSE_DEBUG("TrackBuffer(%p): Reader %p activated", this, reader);
}

View File

@ -2491,26 +2491,75 @@ this.GECKO_NETWORK_STATE_CONNECTED = 1;
this.GECKO_NETWORK_STATE_DISCONNECTING = 2;
this.GECKO_NETWORK_STATE_DISCONNECTED = 3;
// 3GPP 24.008 Annex H.
this.CALL_FAIL_UNOBTAINABLE_NUMBER = 1;
this.CALL_FAIL_NO_ROUTE_TO_DESTINATION = 3;
this.CALL_FAIL_CHANNEL_UNACCEPTABLE = 6;
this.CALL_FAIL_OPERATOR_DETERMINED_BARRING = 8;
this.CALL_FAIL_NORMAL = 16;
this.CALL_FAIL_BUSY = 17;
this.CALL_FAIL_NO_USER_RESPONDING = 18;
this.CALL_FAIL_USER_ALERTING = 19;
this.CALL_FAIL_CALL_REJECTED = 21;
this.CALL_FAIL_NUMBER_CHANGED = 22;
this.CALL_FAIL_CALL_REJECTED_DESTINATION_FEATURE = 24;
this.CALL_FAIL_CALL_PRE_EMPTION = 25;
this.CALL_FAIL_DEST_OUT_OF_ORDER = 27;
this.CALL_FAIL_INVALID_FORMAT = 28;
this.CALL_FAIL_FACILITY_REJECTED = 29;
this.CALL_FAIL_RESPONSE_TO_STATUS_ENQUIRY = 30;
this.CALL_FAIL_NORMAL_UNSPECIFIED = 31;
this.CALL_FAIL_CONGESTION = 34;
this.CALL_FAIL_NETWORK_OUT_OF_ORDER = 38;
this.CALL_FAIL_NETWORK_TEMP_FAILURE = 41;
this.CALL_FAIL_SWITCHING_EQUIP_CONGESTION = 42;
this.CALL_FAIL_ACCESS_INFO_DISCARDED = 43;
this.CALL_FAIL_REQUESTED_CHANNEL_NOT_AVAILABLE = 44;
this.CALL_FAIL_RESOURCE_UNAVAILABLE = 47;
this.CALL_FAIL_QOS_UNAVAILABLE = 49;
this.CALL_FAIL_REQUESTED_FACILITY_NOT_SUBSCRIBED = 50;
this.CALL_FAIL_INCOMING_CALLS_BARRED_WITHIN_CUG = 55;
this.CALL_FAIL_BEARER_CAPABILITY_NOT_AUTHORIZED = 57;
this.CALL_FAIL_BEARER_CAPABILITY_NOT_AVAILABLE = 58;
this.CALL_FAIL_SERVICE_NOT_AVAILABLE = 63;
this.CALL_FAIL_BEARER_NOT_IMPLEMENTED = 65;
this.CALL_FAIL_ACM_LIMIT_EXCEEDED = 68;
this.CALL_FAIL_REQUESTED_FACILITY_NOT_IMPLEMENTED = 69;
this.CALL_FAIL_UNRESTRICTED_BEARER_NOT_AVAILABLE = 70;
this.CALL_FAIL_SERVICE_NOT_IMPLEMENTED = 79;
this.CALL_FAIL_INVALID_TRANSACTION_ID = 81;
this.CALL_FAIL_USER_NOT_CUG_MEMBER = 87;
this.CALL_FAIL_INCOMPATIBLE_DESTINATION = 88;
this.CALL_FAIL_INVALID_TRANSIT_NETWORK_SELECTION = 91;
this.CALL_FAIL_SEMANTICALLY_INCORRECT_MESSAGE = 95;
this.CALL_FAIL_INVALID_MANDATORY_INFO = 96;
this.CALL_FAIL_MESSAGE_TYPE_NOT_IMPLEMENTED = 97;
this.CALL_FAIL_MESSAGE_TYPE_INCOMPATIBLE_PROTOCOL_STATE = 98;
this.CALL_FAIL_INFO_ELEMENT_NOT_IMPLEMENTED = 99;
this.CALL_FAIL_CONDITIONAL_IE_ERROR = 100;
this.CALL_FAIL_MESSAGE_INCOMPABITLE_PROTOCOL_STATE = 101;
this.CALL_FAIL_RECOVERY_ON_TIMER_EXPIRY = 102;
this.CALL_FAIL_PROTOCOL_ERROR = 111;
this.CALL_FAIL_INTERWORKING = 127;
// AOSP ril.h
this.CALL_FAIL_CALL_BARRED = 240;
this.CALL_FAIL_FDN_BLOCKED = 241;
this.CALL_FAIL_IMSI_UNKNOWN_IN_VLR = 242;
this.CALL_FAIL_IMEI_NOT_ACCEPTED = 243;
this.CALL_FAIL_DIAL_MODIFIED_TO_USSD = 244; // STK Call Control
this.CALL_FAIL_DIAL_MODIFIED_TO_SS = 245;
this.CALL_FAIL_DIAL_MODIFIED_TO_DIAL = 246;
this.CALL_FAIL_CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000;
this.CALL_FAIL_CDMA_DROP = 1001;
this.CALL_FAIL_CDMA_INTERCEPT = 1002;
this.CALL_FAIL_CDMA_REORDER = 1003;
this.CALL_FAIL_CDMA_SO_REJECT = 1004;
this.CALL_FAIL_CDMA_RETRY_ORDER = 1005;
this.CALL_FAIL_CDMA_ACCESS_FAILURE = 1006;
this.CALL_FAIL_CDMA_PREEMPTED = 1007;
this.CALL_FAIL_CDMA_NOT_EMERGENCY = 1008; // For non-emergency number dialed
// during emergency callback mode
this.CALL_FAIL_CDMA_ACCESS_BLOCKED = 1009;
this.CALL_FAIL_ERROR_UNSPECIFIED = 0xffff;
// See nsIMobileConnection::MOBILE_RADIO_STATE_*
@ -2666,50 +2715,139 @@ NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_DENIED_EMERGENC
NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_UNKNOWN_EMERGENCY_CALLS] = GECKO_MOBILE_CONNECTION_STATE_UNKNOWN;
this.GECKO_CALL_ERROR_BAD_NUMBER = "BadNumberError";
this.GECKO_CALL_ERROR_NORMAL_CALL_CLEARING = "NormalCallClearingError";
this.GECKO_CALL_ERROR_BUSY = "BusyError";
this.GECKO_CALL_ERROR_NO_USER_RESPONDING = "NoUserRespondingError";
this.GECKO_CALL_ERROR_USER_ALERTING = "UserAlertingNoAnswerError";
this.GECKO_CALL_ERROR_REJECTED = "CallRejectedError";
this.GECKO_CALL_ERROR_NUMBER_CHANGED = "NumberChangedError";
this.GECKO_CALL_ERROR_PRE_EMPTION = "PreEmptionError";
this.GECKO_CALL_ERROR_DEST_OUT_OF_ORDER = "DestinationOutOfOrderError";
this.GECKO_CALL_ERROR_INVALID_NUMBER_FORMAT = "InvalidNumberFormatError";
this.GECKO_CALL_ERROR_FACILITY_REJECTED = "FacilityRejectedError";
this.GECKO_CALL_ERROR_CONGESTION = "CongestionError";
this.GECKO_CALL_ERROR_NETWORK_OUT_OF_ORDER = "NetworkOutOfOrderError";
this.GECKO_CALL_ERROR_NETWORK_TEMP_FAILURE = "NetworkTempFailureError";
this.GECKO_CALL_ERROR_INCOMING_CALL_EXCEEDED = "IncomingCallExceededError";
this.GECKO_CALL_ERROR_BARRED = "BarredError";
this.GECKO_CALL_ERROR_FDN_BLOCKED = "FDNBlockedError";
this.GECKO_CALL_ERROR_SUBSCRIBER_UNKNOWN = "SubscriberUnknownError";
this.GECKO_CALL_ERROR_DEVICE_NOT_ACCEPTED = "DeviceNotAcceptedError";
this.GECKO_CALL_ERROR_BAD_NUMBER = "BadNumberError";
this.GECKO_CALL_ERROR_NO_ROUTE_TO_DESTINATION = "NoRouteToDestinationError";
this.GECKO_CALL_ERROR_CHANNEL_UNACCEPTABLE = "ChannelUnacceptableError";
this.GECKO_CALL_ERROR_OPERATOR_DETERMINED_BARRING = "OperatorDeterminedBarringError";
this.GECKO_CALL_ERROR_NORMAL_CALL_CLEARING = "NormalCallClearingError";
this.GECKO_CALL_ERROR_BUSY = "BusyError";
this.GECKO_CALL_ERROR_NO_USER_RESPONDING = "NoUserRespondingError";
this.GECKO_CALL_ERROR_USER_ALERTING = "UserAlertingNoAnswerError";
this.GECKO_CALL_ERROR_REJECTED = "CallRejectedError";
this.GECKO_CALL_ERROR_NUMBER_CHANGED = "NumberChangedError";
this.GECKO_CALL_ERROR_REJECTED_DETINATION_FEATURE = "CallRejectedDestinationFeature";
this.GECKO_CALL_ERROR_PRE_EMPTION = "PreEmptionError";
this.GECKO_CALL_ERROR_DEST_OUT_OF_ORDER = "DestinationOutOfOrderError";
this.GECKO_CALL_ERROR_INVALID_NUMBER_FORMAT = "InvalidNumberFormatError";
this.GECKO_CALL_ERROR_FACILITY_REJECTED = "FacilityRejectedError";
this.GECKO_CALL_ERROR_RESPONSE_TO_STATUS_ENQUIRY = "ResponseToStatusEnquiryError";
this.GECKO_CALL_ERROR_CONGESTION = "CongestionError";
this.GECKO_CALL_ERROR_NETWORK_OUT_OF_ORDER = "NetworkOutOfOrderError";
this.GECKO_CALL_ERROR_NETWORK_TEMP_FAILURE = "NetworkTempFailureError";
this.GECKO_CALL_ERROR_SWITCHING_EQUIP_CONGESTION = "SwitchingEquipCongestionError";
this.GECKO_CALL_ERROR_ACCESS_INFO_DISCARDED = "AccessInfoDiscardedError";
this.GECKO_CALL_ERROR_REQUESTED_CHANNEL_NOT_AVAILABLE = "RequestedChannelNotAvailableError";
this.GECKO_CALL_ERROR_RESOURCE_UNAVAILABLE = "ResourceUnavailableError";
this.GECKO_CALL_ERROR_QOS_UNAVAILABLE = "QosUnavailableError";
this.GECKO_CALL_ERROR_REQUESTED_FACILITY_NOT_SUBSCRIBED = "RequestedFacilityNotSubscribedError";
this.GECKO_CALL_ERROR_INCOMING_CALLS_BARRED_WITHIN_CUG = "IncomingCallsBarredWithinCugError";
this.GECKO_CALL_ERROR_BEARER_CAPABILITY_NOT_AUTHORIZED = "BearerCapabilityNotAuthorizedError";
this.GECKO_CALL_ERROR_BEARER_CAPABILITY_NOT_AVAILABLE = "BearerCapabilityNotAvailableError";
this.GECKO_CALL_ERROR_BEARER_NOT_IMPLEMENTED = "BearerNotImplementedError";
this.GECKO_CALL_ERROR_SERVICE_NOT_AVAILABLE = "ServiceNotAvailableError";
this.GECKO_CALL_ERROR_INCOMING_CALL_EXCEEDED = "IncomingCallExceededError";
this.GECKO_CALL_ERROR_REQUESTED_FACILITY_NOT_IMPLEMENTED = "RequestedFacilityNotImplementedError";
this.GECKO_CALL_ERROR_UNRESTRICTED_BEARER_NOT_AVAILABLE = "UnrestrictedBearerNotAvailableError";
this.GECKO_CALL_ERROR_SERVICE_NOT_IMPLEMENTED = "ServiceNotImplementedError";
this.GECKO_CALL_ERROR_INVALID_TRANSACTION_ID = "InvalidTransactionIdError";
this.GECKO_CALL_ERROR_USER_NOT_CUG_MEMBER = "NotCugMemberError";
this.GECKO_CALL_ERROR_INCOMPATIBLE_DESTINATION = "IncompatibleDestinationError";
this.GECKO_CALL_ERROR_INVALID_TRANSIT_NETWORK_SELECTION = "InvalidTransitNetworkSelectionError";
this.GECKO_CALL_ERROR_SEMANTICALLY_INCORRECT_MESSAGE = "SemanticallyIncorrectMessageError";
this.GECKO_CALL_ERROR_INVALID_MANDATORY_INFO = "InvalidMandatoryInfoError";
this.GECKO_CALL_ERROR_MESSAGE_TYPE_NOT_IMPLEMENTED = "MessageTypeNotImplementedError";
this.GECKO_CALL_ERROR_MESSAGE_TYPE_INCOMPATIBLE_PROTOCOL_STATE = "MessageTypeIncompatibleProtocolStateError";
this.GECKO_CALL_ERROR_INFO_ELEMENT_NOT_IMPLEMENTED = "InfoElementNotImplementedError";
this.GECKO_CALL_ERROR_CONDITIONAL_IE = "ConditionalIeError";
this.GECKO_CALL_ERROR_MESSAGE_INCOMPATIBLE_PROTOCOL_STATE = "MessageIncompatibleProtocolStateError";
this.GECKO_CALL_ERROR_RECOVERY_ON_TIMER_EXPIRY = "RecoveryOnTimerExpiryError";
this.GECKO_CALL_ERROR_PROTOCOL = "ProtocolError";
this.GECKO_CALL_ERROR_INTERWORKING = "InterworkingError";
this.GECKO_CALL_ERROR_BARRED = "BarredError";
this.GECKO_CALL_ERROR_FDN_BLOCKED = "FDNBlockedError";
this.GECKO_CALL_ERROR_SUBSCRIBER_UNKNOWN = "SubscriberUnknownError";
this.GECKO_CALL_ERROR_DEVICE_NOT_ACCEPTED = "DeviceNotAcceptedError";
this.GECKO_CALL_ERROR_MODIFIED_TO_DIAL_FAILED = "ModifiedDialError";
this.GECKO_CALL_ERROR_UNSPECIFIED = "UnspecifiedError";
this.GECKO_CALL_ERROR_CDMA_LOCKED_UNTIL_POWER_CYCLE = "CdmaLockedUntilPowerCycleError";
this.GECKO_CALL_ERROR_CDMA_DROP = "CdmaDropError";
this.GECKO_CALL_ERROR_CDMA_INTERCEPT = "CdmaInterceptError";
this.GECKO_CALL_ERROR_CDMA_REORDER = "CdmaReorderError";
this.GECKO_CALL_ERROR_CDMA_SO_REJECT = "CdmaSoRejectError";
this.GECKO_CALL_ERROR_CDMA_RETRY_ORDER = "CdmaRetryOrderError";
this.GECKO_CALL_ERROR_CDMA_ACCESS_FAILURE = "CdmaAcessError";
this.GECKO_CALL_ERROR_CDMA_PREEMPTED = "CdmaPreemptedError";
this.GECKO_CALL_ERROR_CDMA_NOT_EMERGENCY = "CdmaNotEmergencyError";
this.GECKO_CALL_ERROR_CDMA_ACCESS_BLOCKED = "CdmaAccessBlockedError";
this.GECKO_CALL_ERROR_UNSPECIFIED = "UnspecifiedError";
this.RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR = {};
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER] = GECKO_CALL_ERROR_BAD_NUMBER;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NORMAL] = GECKO_CALL_ERROR_NORMAL_CALL_CLEARING;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_BUSY] = GECKO_CALL_ERROR_BUSY;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NO_USER_RESPONDING] = GECKO_CALL_ERROR_NO_USER_RESPONDING;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_USER_ALERTING] = GECKO_CALL_ERROR_USER_ALERTING;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CALL_REJECTED] = GECKO_CALL_ERROR_REJECTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NUMBER_CHANGED] = GECKO_CALL_ERROR_NUMBER_CHANGED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CALL_PRE_EMPTION] = GECKO_CALL_ERROR_PRE_EMPTION;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_DEST_OUT_OF_ORDER] = GECKO_CALL_ERROR_DEST_OUT_OF_ORDER;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INVALID_FORMAT] = GECKO_CALL_ERROR_INVALID_NUMBER_FORMAT;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_FACILITY_REJECTED] = GECKO_CALL_ERROR_FACILITY_REJECTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CONGESTION] = GECKO_CALL_ERROR_CONGESTION;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NETWORK_OUT_OF_ORDER] = GECKO_CALL_ERROR_NETWORK_OUT_OF_ORDER;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NETWORK_TEMP_FAILURE] = GECKO_CALL_ERROR_NETWORK_TEMP_FAILURE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_ACM_LIMIT_EXCEEDED] = GECKO_CALL_ERROR_INCOMING_CALL_EXCEEDED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CALL_BARRED] = GECKO_CALL_ERROR_BARRED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_FDN_BLOCKED] = GECKO_CALL_ERROR_FDN_BLOCKED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_IMSI_UNKNOWN_IN_VLR] = GECKO_CALL_ERROR_SUBSCRIBER_UNKNOWN;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_IMEI_NOT_ACCEPTED] = GECKO_CALL_ERROR_DEVICE_NOT_ACCEPTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER] = GECKO_CALL_ERROR_BAD_NUMBER;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NO_ROUTE_TO_DESTINATION] = GECKO_CALL_ERROR_NO_ROUTE_TO_DESTINATION;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CHANNEL_UNACCEPTABLE] = GECKO_CALL_ERROR_CHANNEL_UNACCEPTABLE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_OPERATOR_DETERMINED_BARRING] = GECKO_CALL_ERROR_OPERATOR_DETERMINED_BARRING;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NORMAL] = GECKO_CALL_ERROR_NORMAL_CALL_CLEARING;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_BUSY] = GECKO_CALL_ERROR_BUSY;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NO_USER_RESPONDING] = GECKO_CALL_ERROR_NO_USER_RESPONDING;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_USER_ALERTING] = GECKO_CALL_ERROR_USER_ALERTING;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CALL_REJECTED] = GECKO_CALL_ERROR_REJECTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NUMBER_CHANGED] = GECKO_CALL_ERROR_NUMBER_CHANGED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CALL_REJECTED_DESTINATION_FEATURE] = GECKO_CALL_ERROR_REJECTED_DETINATION_FEATURE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CALL_PRE_EMPTION] = GECKO_CALL_ERROR_PRE_EMPTION;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_DEST_OUT_OF_ORDER] = GECKO_CALL_ERROR_DEST_OUT_OF_ORDER;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INVALID_FORMAT] = GECKO_CALL_ERROR_INVALID_NUMBER_FORMAT;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_FACILITY_REJECTED] = GECKO_CALL_ERROR_FACILITY_REJECTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_RESPONSE_TO_STATUS_ENQUIRY] = GECKO_CALL_ERROR_RESPONSE_TO_STATUS_ENQUIRY;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NORMAL_UNSPECIFIED] = GECKO_CALL_ERROR_NORMAL_CALL_CLEARING;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CONGESTION] = GECKO_CALL_ERROR_CONGESTION;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NETWORK_OUT_OF_ORDER] = GECKO_CALL_ERROR_NETWORK_OUT_OF_ORDER;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NETWORK_TEMP_FAILURE] = GECKO_CALL_ERROR_NETWORK_TEMP_FAILURE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_SWITCHING_EQUIP_CONGESTION] = GECKO_CALL_ERROR_SWITCHING_EQUIP_CONGESTION;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_ACCESS_INFO_DISCARDED] = GECKO_CALL_ERROR_ACCESS_INFO_DISCARDED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_REQUESTED_CHANNEL_NOT_AVAILABLE] = GECKO_CALL_ERROR_REQUESTED_CHANNEL_NOT_AVAILABLE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_RESOURCE_UNAVAILABLE] = GECKO_CALL_ERROR_RESOURCE_UNAVAILABLE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_QOS_UNAVAILABLE] = GECKO_CALL_ERROR_QOS_UNAVAILABLE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_REQUESTED_FACILITY_NOT_SUBSCRIBED] = GECKO_CALL_ERROR_REQUESTED_FACILITY_NOT_SUBSCRIBED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INCOMING_CALLS_BARRED_WITHIN_CUG] = GECKO_CALL_ERROR_INCOMING_CALLS_BARRED_WITHIN_CUG;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_BEARER_CAPABILITY_NOT_AUTHORIZED] = GECKO_CALL_ERROR_BEARER_CAPABILITY_NOT_AUTHORIZED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_BEARER_CAPABILITY_NOT_AVAILABLE] = GECKO_CALL_ERROR_BEARER_CAPABILITY_NOT_AVAILABLE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_SERVICE_NOT_AVAILABLE] = GECKO_CALL_ERROR_SERVICE_NOT_AVAILABLE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_BEARER_NOT_IMPLEMENTED] = GECKO_CALL_ERROR_BEARER_NOT_IMPLEMENTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_ACM_LIMIT_EXCEEDED] = GECKO_CALL_ERROR_INCOMING_CALL_EXCEEDED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_REQUESTED_FACILITY_NOT_IMPLEMENTED] = GECKO_CALL_ERROR_REQUESTED_FACILITY_NOT_IMPLEMENTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNRESTRICTED_BEARER_NOT_AVAILABLE] = GECKO_CALL_ERROR_UNRESTRICTED_BEARER_NOT_AVAILABLE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_SERVICE_NOT_IMPLEMENTED] = GECKO_CALL_ERROR_SERVICE_NOT_IMPLEMENTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INVALID_TRANSACTION_ID] = GECKO_CALL_ERROR_INVALID_TRANSACTION_ID;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_USER_NOT_CUG_MEMBER] = GECKO_CALL_ERROR_USER_NOT_CUG_MEMBER;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INCOMPATIBLE_DESTINATION] = GECKO_CALL_ERROR_INCOMPATIBLE_DESTINATION;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INVALID_TRANSIT_NETWORK_SELECTION] = GECKO_CALL_ERROR_INVALID_TRANSIT_NETWORK_SELECTION;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_SEMANTICALLY_INCORRECT_MESSAGE] = GECKO_CALL_ERROR_SEMANTICALLY_INCORRECT_MESSAGE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INVALID_MANDATORY_INFO] = GECKO_CALL_ERROR_INVALID_MANDATORY_INFO;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_MESSAGE_TYPE_NOT_IMPLEMENTED] = GECKO_CALL_ERROR_MESSAGE_TYPE_NOT_IMPLEMENTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_MESSAGE_TYPE_INCOMPATIBLE_PROTOCOL_STATE] = GECKO_CALL_ERROR_MESSAGE_TYPE_INCOMPATIBLE_PROTOCOL_STATE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INFO_ELEMENT_NOT_IMPLEMENTED] = GECKO_CALL_ERROR_INFO_ELEMENT_NOT_IMPLEMENTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CONDITIONAL_IE_ERROR] = GECKO_CALL_ERROR_CONDITIONAL_IE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_MESSAGE_INCOMPABITLE_PROTOCOL_STATE] = GECKO_CALL_ERROR_MESSAGE_INCOMPATIBLE_PROTOCOL_STATE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_RECOVERY_ON_TIMER_EXPIRY] = GECKO_CALL_ERROR_RECOVERY_ON_TIMER_EXPIRY;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_PROTOCOL_ERROR] = GECKO_CALL_ERROR_PROTOCOL;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INTERWORKING] = GECKO_CALL_ERROR_INTERWORKING;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CALL_BARRED] = GECKO_CALL_ERROR_BARRED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_FDN_BLOCKED] = GECKO_CALL_ERROR_FDN_BLOCKED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_IMSI_UNKNOWN_IN_VLR] = GECKO_CALL_ERROR_SUBSCRIBER_UNKNOWN;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_IMEI_NOT_ACCEPTED] = GECKO_CALL_ERROR_DEVICE_NOT_ACCEPTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_DIAL_MODIFIED_TO_USSD] = GECKO_CALL_ERROR_MODIFIED_TO_DIAL_FAILED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_DIAL_MODIFIED_TO_SS] = GECKO_CALL_ERROR_MODIFIED_TO_DIAL_FAILED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_DIAL_MODIFIED_TO_DIAL] = GECKO_CALL_ERROR_MODIFIED_TO_DIAL_FAILED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_ERROR_UNSPECIFIED] = GECKO_CALL_ERROR_UNSPECIFIED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_LOCKED_UNTIL_POWER_CYCLE] = GECKO_CALL_ERROR_CDMA_LOCKED_UNTIL_POWER_CYCLE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_DROP] = GECKO_CALL_ERROR_CDMA_DROP;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_INTERCEPT] = GECKO_CALL_ERROR_CDMA_INTERCEPT;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_REORDER] = GECKO_CALL_ERROR_CDMA_REORDER;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_SO_REJECT] = GECKO_CALL_ERROR_CDMA_SO_REJECT;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_RETRY_ORDER] = GECKO_CALL_ERROR_CDMA_RETRY_ORDER;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_ACCESS_FAILURE] = GECKO_CALL_ERROR_CDMA_ACCESS_FAILURE;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_PREEMPTED] = GECKO_CALL_ERROR_CDMA_PREEMPTED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_NOT_EMERGENCY] = GECKO_CALL_ERROR_CDMA_NOT_EMERGENCY;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_ACCESS_BLOCKED] = GECKO_CALL_ERROR_CDMA_ACCESS_BLOCKED;
RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_ERROR_UNSPECIFIED] = GECKO_CALL_ERROR_UNSPECIFIED;
this.GECKO_DATACALL_ERROR_OPERATOR_BARRED = "OperatorBarredError";
this.GECKO_DATACALL_ERROR_INSUFFICIENT_RESOURCES = "InsufficientResourcesError";

View File

@ -3894,7 +3894,8 @@ RilObject.prototype = {
* Helpers for processing call state changes.
*/
_processCalls: function(newCalls, failCause) {
if (DEBUG) this.context.debug("_processCalls: " + JSON.stringify(newCalls));
if (DEBUG) this.context.debug("_processCalls: " + JSON.stringify(newCalls) +
" failCause: " + failCause);
// Let's get the failCause first if there are removed calls. Otherwise, we
// need to trigger another async request when removing call and it cause
@ -5455,7 +5456,16 @@ RilObject.prototype[REQUEST_UDUB] = function REQUEST_UDUB(length, options) {
RilObject.prototype[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) {
let Buf = this.context.Buf;
let num = length ? Buf.readInt32() : 0;
let failCause = num ? RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[Buf.readInt32()] : null;
let failCause = null;
if (num) {
let causeNum = Buf.readInt32();
// To make _processCalls work as design, failCause couldn't be "undefined."
// See Bug 1112550 for details.
failCause = RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[causeNum] || null;
}
if (DEBUG) this.context.debug("Last call fail cause: " + failCause);
if (options.callback) {
options.callback(failCause);
}

View File

@ -8,7 +8,9 @@
ChromeOnly]
interface ActivityRequestHandler
{
[UnsafeInPrerendering]
void postResult(any result);
[UnsafeInPrerendering]
void postError(DOMString error);
[Pure, Cached, Frozen]
readonly attribute ActivityOptions source;

View File

@ -12,6 +12,8 @@
Pref="dom.mozAlarms.enabled"]
interface AlarmsManager {
DOMRequest getAll();
[UnsafeInPrerendering]
DOMRequest add(any date, DOMString respectTimezone, optional any data);
[UnsafeInPrerendering]
void remove(unsigned long id);
};

View File

@ -20,10 +20,10 @@ interface AudioBufferSourceNode : AudioNode {
attribute double loopStart;
attribute double loopEnd;
[Throws]
[Throws, UnsafeInPrerendering]
void start(optional double when = 0, optional double grainOffset = 0,
optional double grainDuration);
[Throws]
[Throws, UnsafeInPrerendering]
void stop(optional double when = 0);
attribute EventHandler onended;

View File

@ -45,9 +45,9 @@ interface AudioContext : EventTarget {
StereoPannerNode createStereoPanner();
[NewObject]
AnalyserNode createAnalyser();
[NewObject, Throws]
[NewObject, Throws, UnsafeInPrerendering]
MediaElementAudioSourceNode createMediaElementSource(HTMLMediaElement mediaElement);
[NewObject, Throws]
[NewObject, Throws, UnsafeInPrerendering]
MediaStreamAudioSourceNode createMediaStreamSource(MediaStream mediaStream);
[NewObject]
GainNode createGain();

View File

@ -33,10 +33,12 @@ interface DOMDownloadManager : EventTarget {
// Removes one download from the downloads set. Returns a promise resolved
// with the finalized download.
[UnsafeInPrerendering]
Promise<DOMDownload> remove(DOMDownload download);
// Removes all the completed downloads from the set. Returns an
// array of the completed downloads that were removed.
[UnsafeInPrerendering]
Promise<sequence<DOMDownload>> clearAllDone();
// Fires when a new download starts.
@ -85,10 +87,12 @@ interface DOMDownload : EventTarget {
readonly attribute DOMError? error;
// Pauses the download.
[UnsafeInPrerendering]
Promise<DOMDownload> pause();
// Resumes the download. This resolves only once the download has
// succeeded.
[UnsafeInPrerendering]
Promise<DOMDownload> resume();
// This event is triggered anytime a property of the object changes:

View File

@ -96,7 +96,7 @@ interface Element : Node {
boolean mozMatchesSelector(DOMString selector);
// Pointer events methods.
[Throws, Pref="dom.w3c_pointer_events.enabled"]
[Throws, Pref="dom.w3c_pointer_events.enabled", UnsafeInPrerendering]
void setPointerCapture(long pointerId);
[Throws, Pref="dom.w3c_pointer_events.enabled"]
@ -128,6 +128,7 @@ interface Element : Node {
*
* @see <https://wiki.mozilla.org/index.php?title=Gecko:FullScreenAPI>
*/
[UnsafeInPrerendering]
void mozRequestFullScreen(optional RequestFullscreenOptions fsOptions);
/**
@ -136,6 +137,7 @@ interface Element : Node {
*
* @see <http://dvcs.w3.org/hg/pointerlock/raw-file/default/index.html>
*/
[UnsafeInPrerendering]
void mozRequestPointerLock();
// Obsolete methods.

View File

@ -7,12 +7,12 @@
[JSImplementation="@mozilla.org/sidebar;1"]
interface External
{
void AddSearchProvider(DOMString aDescriptionURL);
[UnsafeInPrerendering] void AddSearchProvider(DOMString aDescriptionURL);
unsigned long IsSearchProviderInstalled(DOMString aSearchURL);
};
// Mozilla extension
partial interface External {
void addSearchEngine(DOMString engineURL, DOMString iconURL,
DOMString suggestedTitle, DOMString suggestedCategory);
[UnsafeInPrerendering] void addSearchEngine(DOMString engineURL, DOMString iconURL,
DOMString suggestedTitle, DOMString suggestedCategory);
};

View File

@ -110,9 +110,9 @@ partial interface HTMLMediaElement {
[Func="IsChromeOrXBL"] attribute boolean mozIsCasting;
// Mozilla extension: stream capture
[Throws]
[Throws, UnsafeInPrerendering]
MediaStream mozCaptureStream();
[Throws]
[Throws, UnsafeInPrerendering]
MediaStream mozCaptureStreamUntilEnded();
readonly attribute boolean mozAudioCaptured;

View File

@ -254,7 +254,7 @@ partial interface Navigator {
// nsIDOMNavigatorDesktopNotification
partial interface Navigator {
[Throws, Pref="notification.feature.enabled"]
[Throws, Pref="notification.feature.enabled", UnsafeInPrerendering]
readonly attribute DesktopNotificationCenter mozNotification;
};
@ -273,7 +273,7 @@ partial interface Navigator {
// nsIDOMNavigatorCamera
partial interface Navigator {
[Throws, Func="Navigator::HasCameraSupport"]
[Throws, Func="Navigator::HasCameraSupport", UnsafeInPrerendering]
readonly attribute CameraManager mozCameras;
};
@ -289,27 +289,27 @@ partial interface Navigator {
#ifdef MOZ_B2G_RIL
partial interface Navigator {
[Throws, Pref="dom.mobileconnection.enabled", CheckPermissions="mobileconnection mobilenetwork"]
[Throws, Pref="dom.mobileconnection.enabled", CheckPermissions="mobileconnection mobilenetwork", UnsafeInPrerendering]
readonly attribute MozMobileConnectionArray mozMobileConnections;
};
partial interface Navigator {
[Throws, Pref="dom.cellbroadcast.enabled", CheckPermissions="cellbroadcast"]
[Throws, Pref="dom.cellbroadcast.enabled", CheckPermissions="cellbroadcast", UnsafeInPrerendering]
readonly attribute MozCellBroadcast mozCellBroadcast;
};
partial interface Navigator {
[Throws, Pref="dom.voicemail.enabled", CheckPermissions="voicemail"]
[Throws, Pref="dom.voicemail.enabled", CheckPermissions="voicemail", UnsafeInPrerendering]
readonly attribute MozVoicemail mozVoicemail;
};
partial interface Navigator {
[Throws, Pref="dom.icc.enabled", CheckPermissions="mobileconnection"]
[Throws, Pref="dom.icc.enabled", CheckPermissions="mobileconnection", UnsafeInPrerendering]
readonly attribute MozIccManager? mozIccManager;
};
partial interface Navigator {
[Throws, Pref="dom.telephony.enabled", CheckPermissions="telephony"]
[Throws, Pref="dom.telephony.enabled", CheckPermissions="telephony", UnsafeInPrerendering]
readonly attribute Telephony? mozTelephony;
};
#endif // MOZ_B2G_RIL
@ -329,14 +329,14 @@ partial interface Navigator {
#ifdef MOZ_B2G_BT
partial interface Navigator {
[Throws, CheckPermissions="bluetooth"]
[Throws, CheckPermissions="bluetooth", UnsafeInPrerendering]
readonly attribute BluetoothManager mozBluetooth;
};
#endif // MOZ_B2G_BT
#ifdef MOZ_B2G_FM
partial interface Navigator {
[Throws, CheckPermissions="fmradio"]
[Throws, CheckPermissions="fmradio", UnsafeInPrerendering]
readonly attribute FMRadio mozFMRadio;
};
#endif // MOZ_B2G_FM
@ -366,7 +366,7 @@ partial interface Navigator {
readonly attribute MediaDevices mediaDevices;
// Deprecated. Use mediaDevices.getUserMedia instead.
[Throws, Func="Navigator::HasUserMediaSupport"]
[Throws, Func="Navigator::HasUserMediaSupport", UnsafeInPrerendering]
void mozGetUserMedia(MediaStreamConstraints constraints,
NavigatorUserMediaSuccessCallback successCallback,
NavigatorUserMediaErrorCallback errorCallback);

View File

@ -12,7 +12,8 @@
*/
[Pref="dom.webnotifications.enabled",
Constructor(DOMString title, optional NotificationOptions options)]
Constructor(DOMString title, optional NotificationOptions options),
UnsafeInPrerendering]
interface Notification : EventTarget {
[GetterThrows]
static readonly attribute NotificationPermission permission;

View File

@ -26,9 +26,9 @@ interface OscillatorNode : AudioNode {
readonly attribute AudioParam frequency; // in Hertz
readonly attribute AudioParam detune; // in Cents
[Throws]
[Throws, UnsafeInPrerendering]
void start(optional double when = 0);
[Throws]
[Throws, UnsafeInPrerendering]
void stop(optional double when = 0);
void setPeriodicWave(PeriodicWave periodicWave);

View File

@ -102,8 +102,11 @@ interface mozRTCPeerConnection : EventTarget {
attribute DOMString id;
RTCConfiguration getConfiguration ();
[UnsafeInPrerendering]
sequence<MediaStream> getLocalStreams ();
[UnsafeInPrerendering]
sequence<MediaStream> getRemoteStreams ();
[UnsafeInPrerendering]
MediaStream? getStreamById (DOMString streamId);
void addStream (MediaStream stream);
void removeStream (MediaStream stream);

View File

@ -40,13 +40,14 @@ interface Screen : EventTarget {
/**
* Lock screen orientation to the specified type.
*/
[Throws]
[Throws, UnsafeInPrerendering]
boolean mozLockOrientation(DOMString orientation);
[Throws]
[Throws, UnsafeInPrerendering]
boolean mozLockOrientation(sequence<DOMString> orientation);
/**
* Unlock the screen orientation.
*/
[UnsafeInPrerendering]
void mozUnlockOrientation();
};

View File

@ -28,7 +28,7 @@ interface SpeechRecognition : EventTarget {
attribute DOMString serviceURI;
// methods to drive the speech interaction
[Throws]
[Throws, UnsafeInPrerendering]
void start(optional MediaStream stream);
void stop();
void abort();

View File

@ -16,9 +16,11 @@ interface SpeechSynthesis {
readonly attribute boolean speaking;
readonly attribute boolean paused;
[UnsafeInPrerendering]
void speak(SpeechSynthesisUtterance utterance);
void cancel();
void pause();
[UnsafeInPrerendering]
void resume();
sequence<SpeechSynthesisVoice> getVoices();
};

View File

@ -234,8 +234,8 @@ hb-gobject-enums.%: hb-gobject-enums.%.tmpl $(HBHEADERS)
$(AM_V_GEN) $(GLIB_MKENUMS) \
--identifier-prefix hb_ --symbol-prefix hb_gobject \
--template $^ | \
sed 's/_t_get_type/_get_type/g; s/_T (/ (/g' > "$@.tmp" && \
mv "$@.tmp" "$@" || ( $(RM) "@.tmp" && false )
sed 's/_t_get_type/_get_type/g; s/_T (/ (/g' > "$@" \
|| ($(RM) "$@"; false)
endif
EXTRA_DIST += \
harfbuzz-gobject.pc.in \
@ -251,8 +251,8 @@ EXTRA_DIST += \
-e 's@%libdir%@$(libdir)@g' \
-e 's@%includedir%@$(includedir)@g' \
-e 's@%VERSION%@$(VERSION)@g' \
"$<" \
> "$@.tmp" && mv "$@.tmp" "$@" || ( $(RM) "$@.tmp"; false )
"$<" > "$@" \
|| ($(RM) "$@"; false)
CLEANFILES += $(pkgconfig_DATA)
@ -265,8 +265,9 @@ harfbuzz.def: $(HBHEADERS) $(HBNODISTHEADERS)
sed -e 's/ (.*//' | \
LANG=C sort; \
echo LIBRARY libharfbuzz-$(HB_VERSION_MAJOR).dll; \
) >"$@.tmp"
@ ! grep -q hb_ERROR "$@.tmp" && mv "$@.tmp" "$@" || ($(RM) "$@"; false)
) >"$@"
@ ! grep -q hb_ERROR "$@" \
|| ($(RM) "$@"; false)
GENERATORS = \
@ -278,26 +279,25 @@ EXTRA_DIST += $(GENERATORS)
unicode-tables: arabic-table indic-table
indic-table: gen-indic-table.py IndicSyllabicCategory.txt IndicMatraCategory.txt Blocks.txt
$(AM_V_GEN) $(builddir)/$^ > hb-ot-shape-complex-indic-table.cc.tmp && \
mv hb-ot-shape-complex-indic-table.cc.tmp $(srcdir)/hb-ot-shape-complex-indic-table.cc || \
($(RM) hb-ot-shape-complex-indic-table.cc.tmp; false)
$(AM_V_GEN) $(builddir)/$^ > hb-ot-shape-complex-indic-table.cc \
|| ($(RM) hb-ot-shape-complex-indic-table.cc; false)
arabic-table: gen-arabic-table.py ArabicShaping.txt UnicodeData.txt Blocks.txt
$(AM_V_GEN) $(builddir)/$^ > hb-ot-shape-complex-arabic-table.hh.tmp && \
mv hb-ot-shape-complex-arabic-table.hh.tmp $(srcdir)/hb-ot-shape-complex-arabic-table.hh || \
($(RM) hb-ot-shape-complex-arabic-table.hh.tmp; false)
$(AM_V_GEN) $(builddir)/$^ > hb-ot-shape-complex-arabic-table.hh \
|| ($(RM) hb-ot-shape-complex-arabic-table.hh; false)
built-sources: $(BUILT_SOURCES)
.PHONY: unicode-tables arabic-table indic-table built-sources
BUILT_SOURCES += \
hb-buffer-deserialize-json.hh \
hb-buffer-deserialize-text.hh \
hb-ot-shape-complex-indic-machine.hh \
hb-ot-shape-complex-myanmar-machine.hh \
hb-ot-shape-complex-sea-machine.hh \
RAGEL_GENERATED = \
$(srcdir)/hb-buffer-deserialize-json.hh \
$(srcdir)/hb-buffer-deserialize-text.hh \
$(srcdir)/hb-ot-shape-complex-indic-machine.hh \
$(srcdir)/hb-ot-shape-complex-myanmar-machine.hh \
$(srcdir)/hb-ot-shape-complex-sea-machine.hh \
$(NULL)
BUILT_SOURCES += $(RAGEL_GENERATED)
EXTRA_DIST += \
hb-buffer-deserialize-json.rl \
hb-buffer-deserialize-text.rl \
@ -305,9 +305,10 @@ EXTRA_DIST += \
hb-ot-shape-complex-myanmar-machine.rl \
hb-ot-shape-complex-sea-machine.rl \
$(NULL)
.rl.hh:
$(AM_V_GEN)$(RAGEL) -e -F1 -o "$@.tmp" "$<" && \
mv "$@.tmp" "$@" || ( $(RM) "$@.tmp" && false )
MAINTAINERCLEANFILES += $(RAGEL_GENERATED)
$(srcdir)/%.hh: $(srcdir)/%.rl
$(AM_V_GEN)(cd $(srcdir) && $(RAGEL) -e -F1 -o "$*.hh" "$*.rl") \
|| ($(RM) "$@"; false)
noinst_PROGRAMS = \
main \

View File

@ -17,14 +17,14 @@ fi
tested=false
for suffix in so dylib; do
so=.libs/libharfbuzz.$suffix
if test -f "$so"; then
echo "Checking that we are not linking to libstdc++"
if ldd $so | grep 'libstdc[+][+]'; then
echo "Ouch, linked to libstdc++"
stat=1
fi
tested=true
if ! test -f "$so"; then continue; fi
echo "Checking that we are not linking to libstdc++"
if ldd $so | grep 'libstdc[+][+]'; then
echo "Ouch, linked to libstdc++"
stat=1
fi
tested=true
done
if ! $tested; then
echo "check-libstdc++.sh: libharfbuzz shared library not found; skipping test"

View File

@ -22,8 +22,8 @@ fi
echo "Checking that no object file has static initializers"
for obj in $OBJS; do
if objdump -t "$obj" | grep '[.]ctors'; then
echo "Ouch, $obj has static initializers"
if objdump -t "$obj" | grep '[.][cd]tors' | grep -v '\<00*\>'; then
echo "Ouch, $obj has static initializers/finalizers"
stat=1
fi
done

View File

@ -16,11 +16,17 @@ fi
echo "Checking that we are not exposing internal symbols"
tested=false
for so in `ls .libs/lib*.so .libs/lib*.dylib 2>/dev/null` ; do
for suffix in so dylib; do
so=.libs/libharfbuzz.$suffix
if ! test -f "$so"; then continue; fi
EXPORTED_SYMBOLS="`nm "$so" | grep ' [BCDGINRSTVW] ' | grep -v ' _fini\>\| _init\>\| _fdata\>\| _ftext\>\| _fbss\>\| __bss_start\>\| __bss_start__\>\| __bss_end__\>\| _edata\>\| _end\>\| _bss_end__\>\| __end__\>' | cut -d' ' -f3`"
EXPORTED_SYMBOLS="`nm "$so" | grep ' [BCDGINRSTVW] ' | grep -v ' _fini\>\| _init\>\| _fdata\>\| _ftext\>\| _fbss\>\| __bss_start\>\| __bss_start__\>\| __bss_end__\>\| _edata\>\| _end\>\| _bss_end__\>\| __end__\>\| __gcov_flush\>\| llvm_' | cut -d' ' -f3`"
prefix=`basename "$so" | sed 's/libharfbuzz/hb/; s/-/_/g; s/[.].*//'`
# On mac, C symbols are prefixed with _
if test $suffix = dylib; then prefix="_$prefix"; fi
echo "Processing $so"
if echo "$EXPORTED_SYMBOLS" | grep -v "^${prefix}_"; then
echo "Ouch, internal symbols exposed"

View File

@ -102,7 +102,10 @@ hb_blob_create (const char *data,
{
hb_blob_t *blob;
if (!length || !(blob = hb_object_create<hb_blob_t> ())) {
if (!length ||
length >= 1u << 31 ||
data + length < data /* overflows */ ||
!(blob = hb_object_create<hb_blob_t> ())) {
if (destroy)
destroy (user_data);
return hb_blob_get_empty ();

View File

@ -117,8 +117,8 @@ _hb_buffer_deserialize_glyphs_json (hb_buffer_t *buffer,
const char *tok = NULL;
int cs;
hb_glyph_info_t info;
hb_glyph_position_t pos;
hb_glyph_info_t info = {0};
hb_glyph_position_t pos = {0};
%%{
write init;
write exec;

View File

@ -48,15 +48,13 @@ struct hb_buffer_t {
ASSERT_POD ();
/* Information about how the text in the buffer should be treated */
hb_unicode_funcs_t *unicode; /* Unicode functions */
hb_segment_properties_t props; /* Script, language, direction */
hb_buffer_flags_t flags; /* BOT / EOT / etc. */
hb_codepoint_t replacement; /* U+FFFD or something else. */
/* Buffer contents */
hb_buffer_content_type_t content_type;
hb_segment_properties_t props; /* Script, language, direction */
bool in_error; /* Allocation failed */
bool have_output; /* Whether we have an output buffer going on */
@ -183,6 +181,9 @@ struct hb_buffer_t {
inline bool ensure (unsigned int size)
{ return likely (!size || size < allocated) ? true : enlarge (size); }
inline bool ensure_inplace (unsigned int size)
{ return likely (!size || size < allocated); }
HB_INTERNAL bool make_room_for (unsigned int num_in, unsigned int num_out);
HB_INTERNAL bool shift_forward (unsigned int count);

View File

@ -178,6 +178,7 @@ hb_buffer_t::reset (void)
hb_unicode_funcs_destroy (unicode);
unicode = hb_unicode_funcs_get_default ();
flags = HB_BUFFER_FLAG_DEFAULT;
replacement = HB_BUFFER_REPLACEMENT_CODEPOINT_DEFAULT;
clear ();
@ -191,7 +192,6 @@ hb_buffer_t::clear (void)
hb_segment_properties_t default_props = HB_SEGMENT_PROPERTIES_DEFAULT;
props = default_props;
flags = HB_BUFFER_FLAG_DEFAULT;
content_type = HB_BUFFER_CONTENT_TYPE_INVALID;
in_error = false;
@ -702,11 +702,11 @@ hb_buffer_get_empty (void)
HB_OBJECT_HEADER_STATIC,
const_cast<hb_unicode_funcs_t *> (&_hb_unicode_funcs_nil),
HB_SEGMENT_PROPERTIES_DEFAULT,
HB_BUFFER_FLAG_DEFAULT,
HB_BUFFER_REPLACEMENT_CODEPOINT_DEFAULT,
HB_BUFFER_CONTENT_TYPE_INVALID,
HB_SEGMENT_PROPERTIES_DEFAULT,
true, /* in_error */
true, /* have_output */
true /* have_positions */

View File

@ -234,7 +234,7 @@ struct hb_language_item_t {
static hb_language_item_t *langs;
#ifdef HAVE_ATEXIT
#ifdef HB_USE_ATEXIT
static inline
void free_langs (void)
{
@ -269,7 +269,7 @@ retry:
goto retry;
}
#ifdef HAVE_ATEXIT
#ifdef HB_USE_ATEXIT
if (!first_lang)
atexit (free_langs); /* First person registers atexit() callback. */
#endif

View File

@ -123,12 +123,13 @@ hb_direction_from_string (const char *str, int len);
const char *
hb_direction_to_string (hb_direction_t direction);
#define HB_DIRECTION_IS_VALID(dir) ((((unsigned int) (dir)) & ~3U) == 4)
/* Direction must be valid for the following */
#define HB_DIRECTION_IS_HORIZONTAL(dir) ((((unsigned int) (dir)) & ~1U) == 4)
#define HB_DIRECTION_IS_VERTICAL(dir) ((((unsigned int) (dir)) & ~1U) == 6)
#define HB_DIRECTION_IS_FORWARD(dir) ((((unsigned int) (dir)) & ~2U) == 4)
#define HB_DIRECTION_IS_BACKWARD(dir) ((((unsigned int) (dir)) & ~2U) == 5)
#define HB_DIRECTION_IS_VALID(dir) ((((unsigned int) (dir)) & ~3U) == 4)
#define HB_DIRECTION_REVERSE(dir) ((hb_direction_t) (((unsigned int) (dir)) ^ 1)) /* Direction must be valid */
#define HB_DIRECTION_REVERSE(dir) ((hb_direction_t) (((unsigned int) (dir)) ^ 1))
/* hb_language_t */

View File

@ -27,6 +27,7 @@
*/
#define HB_SHAPER coretext
#define hb_coretext_shaper_face_data_t CGFont
#include "hb-shaper-impl-private.hh"
#include "hb-coretext.h"
@ -77,10 +78,6 @@ HB_SHAPER_DATA_ENSURE_DECLARE(coretext, font)
* shaper face data
*/
struct hb_coretext_shaper_face_data_t {
CGFontRef cg_font;
};
static void
release_data (void *info, const void *data, size_t size)
{
@ -93,13 +90,11 @@ release_data (void *info, const void *data, size_t size)
hb_coretext_shaper_face_data_t *
_hb_coretext_shaper_face_data_create (hb_face_t *face)
{
hb_coretext_shaper_face_data_t *data = (hb_coretext_shaper_face_data_t *) calloc (1, sizeof (hb_coretext_shaper_face_data_t));
if (unlikely (!data))
return NULL;
hb_coretext_shaper_face_data_t *data = NULL;
if (face->destroy == (hb_destroy_func_t) CGFontRelease)
{
data->cg_font = CGFontRetain ((CGFontRef) face->user_data);
data = CGFontRetain ((CGFontRef) face->user_data);
}
else
{
@ -110,14 +105,15 @@ _hb_coretext_shaper_face_data_create (hb_face_t *face)
DEBUG_MSG (CORETEXT, face, "Face has empty blob");
CGDataProviderRef provider = CGDataProviderCreateWithData (blob, blob_data, blob_length, &release_data);
data->cg_font = CGFontCreateWithDataProvider (provider);
CGDataProviderRelease (provider);
if (likely (provider))
{
data = CGFontCreateWithDataProvider (provider);
CGDataProviderRelease (provider);
}
}
if (unlikely (!data->cg_font)) {
if (unlikely (!data)) {
DEBUG_MSG (CORETEXT, face, "Face CGFontCreateWithDataProvider() failed");
free (data);
return NULL;
}
return data;
@ -126,8 +122,7 @@ _hb_coretext_shaper_face_data_create (hb_face_t *face)
void
_hb_coretext_shaper_face_data_destroy (hb_coretext_shaper_face_data_t *data)
{
CFRelease (data->cg_font);
free (data);
CFRelease (data);
}
CGFontRef
@ -135,7 +130,7 @@ hb_coretext_face_get_cg_font (hb_face_t *face)
{
if (unlikely (!hb_coretext_shaper_face_data_ensure (face))) return NULL;
hb_coretext_shaper_face_data_t *face_data = HB_SHAPER_DATA_GET (face);
return face_data->cg_font;
return face_data;
}
@ -159,7 +154,7 @@ _hb_coretext_shaper_font_data_create (hb_font_t *font)
hb_face_t *face = font->face;
hb_coretext_shaper_face_data_t *face_data = HB_SHAPER_DATA_GET (face);
data->ct_font = CTFontCreateWithGraphicsFont (face_data->cg_font, font->y_scale, NULL, NULL);
data->ct_font = CTFontCreateWithGraphicsFont (face_data, font->y_scale, NULL, NULL);
if (unlikely (!data->ct_font)) {
DEBUG_MSG (CORETEXT, font, "Font CTFontCreateWithGraphicsFont() failed");
free (data);
@ -332,7 +327,7 @@ struct range_record_t {
#define kUpperCaseType 38
/* Table data courtesy of Apple. */
struct feature_mapping_t {
static const struct feature_mapping_t {
FourCharCode otFeatureTag;
uint16_t aatFeatureType;
uint16_t selectorToEnable;
@ -436,12 +431,29 @@ _hb_coretext_shape (hb_shape_plan_t *shape_plan,
hb_coretext_shaper_face_data_t *face_data = HB_SHAPER_DATA_GET (face);
hb_coretext_shaper_font_data_t *font_data = HB_SHAPER_DATA_GET (font);
/* Attach marks to their bases, to match the 'ot' shaper.
* Adapted from hb-ot-shape:hb_form_clusters().
* Note that this only makes us be closer to the 'ot' shaper,
* but by no means the same. For example, if there's
* B1 M1 B2 M2, and B1-B2 form a ligature, M2's cluster will
* continue pointing to B2 even though B2 was merged into B1's
* cluster... */
{
hb_unicode_funcs_t *unicode = buffer->unicode;
unsigned int count = buffer->len;
hb_glyph_info_t *info = buffer->info;
for (unsigned int i = 1; i < count; i++)
if (HB_UNICODE_GENERAL_CATEGORY_IS_MARK (unicode->general_category (info[i].codepoint)))
buffer->merge_clusters (i - 1, i + 1);
}
hb_auto_array_t<feature_record_t> feature_records;
hb_auto_array_t<range_record_t> range_records;
/*
* Set up features.
* (copied + modified from code from hb-uniscribe.cc)
*/
hb_auto_array_t<feature_record_t> feature_records;
hb_auto_array_t<range_record_t> range_records;
if (num_features)
{
/* Sort features by start/end events. */
@ -549,7 +561,6 @@ _hb_coretext_shape (hb_shape_plan_t *shape_plan,
CFRelease (attributes);
range->font = CTFontCreateCopyWithAttributes (font_data->ct_font, 0.0, NULL, font_desc);
CFRelease (font_desc);
}
else
@ -584,32 +595,26 @@ _hb_coretext_shape (hb_shape_plan_t *shape_plan,
num_features = 0;
}
#define FAIL(...) \
HB_STMT_START { \
DEBUG_MSG (CORETEXT, NULL, __VA_ARGS__); \
return false; \
} HB_STMT_END;
unsigned int scratch_size;
hb_buffer_t::scratch_buffer_t *scratch = buffer->get_scratch_buffer (&scratch_size);
#define ALLOCATE_ARRAY(Type, name, len) \
#define ALLOCATE_ARRAY(Type, name, len, on_no_room) \
Type *name = (Type *) scratch; \
{ \
unsigned int _consumed = DIV_CEIL ((len) * sizeof (Type), sizeof (*scratch)); \
assert (_consumed <= scratch_size); \
if (unlikely (_consumed > scratch_size)) \
{ \
on_no_room; \
assert (0); \
} \
scratch += _consumed; \
scratch_size -= _consumed; \
}
#define utf16_index() var1.u32
ALLOCATE_ARRAY (UniChar, pchars, buffer->len * 2);
ALLOCATE_ARRAY (UniChar, pchars, buffer->len * 2, /*nothing*/);
unsigned int chars_len = 0;
for (unsigned int i = 0; i < buffer->len; i++) {
hb_codepoint_t c = buffer->info[i].codepoint;
buffer->info[i].utf16_index() = chars_len;
if (likely (c <= 0xFFFFu))
pchars[chars_len++] = c;
else if (unlikely (c > 0x10FFFFu))
@ -620,232 +625,439 @@ _hb_coretext_shape (hb_shape_plan_t *shape_plan,
}
}
#undef utf16_index
CFStringRef string_ref = CFStringCreateWithCharactersNoCopy (NULL,
pchars, chars_len,
kCFAllocatorNull);
CFMutableAttributedStringRef attr_string = CFAttributedStringCreateMutable (NULL, chars_len);
CFAttributedStringReplaceString (attr_string, CFRangeMake (0, 0), string_ref);
CFAttributedStringSetAttribute (attr_string, CFRangeMake (0, chars_len),
kCTFontAttributeName, font_data->ct_font);
if (num_features)
ALLOCATE_ARRAY (unsigned int, log_clusters, chars_len, /*nothing*/);
chars_len = 0;
for (unsigned int i = 0; i < buffer->len; i++)
{
ALLOCATE_ARRAY (unsigned int, log_clusters, chars_len);
/* Need log_clusters to assign features. */
chars_len = 0;
for (unsigned int i = 0; i < buffer->len; i++)
{
hb_codepoint_t c = buffer->info[i].codepoint;
unsigned int cluster = buffer->info[i].cluster;
log_clusters[chars_len++] = cluster;
if (hb_in_range (c, 0x10000u, 0x10FFFFu))
log_clusters[chars_len++] = cluster; /* Surrogates. */
}
unsigned int start = 0;
range_record_t *last_range = &range_records[0];
for (unsigned int k = 0; k < chars_len; k++)
{
range_record_t *range = last_range;
while (log_clusters[k] < range->index_first)
range--;
while (log_clusters[k] > range->index_last)
range++;
if (range != last_range)
{
if (last_range->font)
CFAttributedStringSetAttribute (attr_string, CFRangeMake (start, k - start),
kCTFontAttributeName, last_range->font);
start = k;
}
last_range = range;
}
if (start != chars_len && last_range->font)
CFAttributedStringSetAttribute (attr_string, CFRangeMake (start, chars_len - start - 1),
kCTFontAttributeName, last_range->font);
for (unsigned int i = 0; i < range_records.len; i++)
if (range_records[i].font)
CFRelease (range_records[i].font);
hb_codepoint_t c = buffer->info[i].codepoint;
unsigned int cluster = buffer->info[i].cluster;
log_clusters[chars_len++] = cluster;
if (hb_in_range (c, 0x10000u, 0x10FFFFu))
log_clusters[chars_len++] = cluster; /* Surrogates. */
}
CTLineRef line = CTLineCreateWithAttributedString (attr_string);
CFRelease (attr_string);
#define FAIL(...) \
HB_STMT_START { \
DEBUG_MSG (CORETEXT, NULL, __VA_ARGS__); \
ret = false; \
goto fail; \
} HB_STMT_END;
CFArrayRef glyph_runs = CTLineGetGlyphRuns (line);
unsigned int num_runs = CFArrayGetCount (glyph_runs);
bool ret = true;
CFStringRef string_ref = NULL;
CTLineRef line = NULL;
buffer->len = 0;
const CFRange range_all = CFRangeMake (0, 0);
for (unsigned int i = 0; i < num_runs; i++)
if (0)
{
CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (glyph_runs, i);
resize_and_retry:
DEBUG_MSG (CORETEXT, buffer, "Buffer resize");
/* string_ref uses the scratch-buffer for backing store, and line references
* string_ref (via attr_string). We must release those before resizing buffer. */
assert (string_ref);
assert (line);
CFRelease (string_ref);
CFRelease (line);
string_ref = NULL;
line = NULL;
/* CoreText does automatic font fallback (AKA "cascading") for characters
* not supported by the requested font, and provides no way to turn it off,
* so we detect if the returned run uses a font other than the requested
* one and fill in the buffer with .notdef glyphs instead of random glyph
* indices from a different font.
*/
CFDictionaryRef attributes = CTRunGetAttributes (run);
CTFontRef run_ct_font = static_cast<CTFontRef>(CFDictionaryGetValue (attributes, kCTFontAttributeName));
CGFontRef run_cg_font = CTFontCopyGraphicsFont (run_ct_font, 0);
if (!CFEqual (run_cg_font, face_data->cg_font))
{
CFRelease (run_cg_font);
/* Get previous start-of-scratch-area, that we use later for readjusting
* our existing scratch arrays. */
unsigned int old_scratch_used;
hb_buffer_t::scratch_buffer_t *old_scratch;
old_scratch = buffer->get_scratch_buffer (&old_scratch_used);
old_scratch_used = scratch - old_scratch;
CFRange range = CTRunGetStringRange (run);
buffer->ensure (buffer->len + range.length);
if (buffer->in_error)
FAIL ("Buffer resize failed");
hb_glyph_info_t *info = buffer->info + buffer->len;
CGGlyph notdef = 0;
double advance = CTFontGetAdvancesForGlyphs (font_data->ct_font, kCTFontHorizontalOrientation, &notdef, NULL, 1);
for (CFIndex j = range.location; j < range.location + range.length; j++)
{
UniChar ch = CFStringGetCharacterAtIndex (string_ref, j);
if (hb_in_range<UniChar> (ch, 0xDC00u, 0xDFFFu) && range.location < j)
{
ch = CFStringGetCharacterAtIndex (string_ref, j - 1);
if (hb_in_range<UniChar> (ch, 0xD800u, 0xDBFFu))
/* This is the second of a surrogate pair. Don't need .notdef
* for this one. */
continue;
}
info->codepoint = notdef;
/* TODO We have to fixup clusters later. See vis_clusters in
* hb-uniscribe.cc for example. */
info->cluster = j;
info->mask = advance;
info->var1.u32 = 0;
info->var2.u32 = 0;
info++;
buffer->len++;
}
continue;
}
CFRelease (run_cg_font);
unsigned int num_glyphs = CTRunGetGlyphCount (run);
if (num_glyphs == 0)
continue;
buffer->ensure (buffer->len + num_glyphs);
if (unlikely (!buffer->ensure (buffer->allocated * 2)))
FAIL ("Buffer resize failed");
/* Adjust scratch, pchars, and log_cluster arrays. This is ugly, but really the
* cleanest way to do without completely restructuring the rest of this shaper. */
scratch = buffer->get_scratch_buffer (&scratch_size);
pchars = reinterpret_cast<UniChar *> (((char *) scratch + ((char *) pchars - (char *) old_scratch)));
log_clusters = reinterpret_cast<unsigned int *> (((char *) scratch + ((char *) log_clusters - (char *) old_scratch)));
scratch += old_scratch_used;
scratch_size -= old_scratch_used;
}
retry:
{
string_ref = CFStringCreateWithCharactersNoCopy (NULL,
pchars, chars_len,
kCFAllocatorNull);
if (unlikely (!string_ref))
FAIL ("CFStringCreateWithCharactersNoCopy failed");
/* Testing indicates that CTRunGetGlyphsPtr, etc (almost?) always
* succeed, and so copying data to our own buffer will be rare. */
/* Create an attributed string, populate it, and create a line from it, then release attributed string. */
{
CFMutableAttributedStringRef attr_string = CFAttributedStringCreateMutable (kCFAllocatorDefault,
chars_len);
if (unlikely (!attr_string))
FAIL ("CFAttributedStringCreateMutable failed");
CFAttributedStringReplaceString (attr_string, CFRangeMake (0, 0), string_ref);
if (HB_DIRECTION_IS_VERTICAL (buffer->props.direction))
{
CFAttributedStringSetAttribute (attr_string, CFRangeMake (0, chars_len),
kCTVerticalFormsAttributeName, kCFBooleanTrue);
}
const CGGlyph* glyphs = CTRunGetGlyphsPtr (run);
if (!glyphs) {
ALLOCATE_ARRAY (CGGlyph, glyph_buf, num_glyphs);
CTRunGetGlyphs (run, range_all, glyph_buf);
glyphs = glyph_buf;
if (buffer->props.language)
{
/* What's the iOS equivalent of this check?
* The symbols was introduced in iOS 7.0.
* At any rate, our fallback is safe and works fine. */
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1090
# define kCTLanguageAttributeName CFSTR ("NSLanguage")
#endif
CFStringRef lang = CFStringCreateWithCStringNoCopy (kCFAllocatorDefault,
hb_language_to_string (buffer->props.language),
kCFStringEncodingUTF8,
kCFAllocatorNull);
if (unlikely (!lang))
FAIL ("CFStringCreateWithCStringNoCopy failed");
CFAttributedStringSetAttribute (attr_string, CFRangeMake (0, chars_len),
kCTLanguageAttributeName, lang);
CFRelease (lang);
}
CFAttributedStringSetAttribute (attr_string, CFRangeMake (0, chars_len),
kCTFontAttributeName, font_data->ct_font);
if (num_features)
{
unsigned int start = 0;
range_record_t *last_range = &range_records[0];
for (unsigned int k = 0; k < chars_len; k++)
{
range_record_t *range = last_range;
while (log_clusters[k] < range->index_first)
range--;
while (log_clusters[k] > range->index_last)
range++;
if (range != last_range)
{
if (last_range->font)
CFAttributedStringSetAttribute (attr_string, CFRangeMake (start, k - start),
kCTFontAttributeName, last_range->font);
start = k;
}
last_range = range;
}
if (start != chars_len && last_range->font)
CFAttributedStringSetAttribute (attr_string, CFRangeMake (start, chars_len - start),
kCTFontAttributeName, last_range->font);
}
int level = HB_DIRECTION_IS_FORWARD (buffer->props.direction) ? 0 : 1;
CFNumberRef level_number = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &level);
CFDictionaryRef options = CFDictionaryCreate (kCFAllocatorDefault,
(const void **) &kCTTypesetterOptionForcedEmbeddingLevel,
(const void **) &level_number,
1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (unlikely (!options))
FAIL ("CFDictionaryCreate failed");
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedStringAndOptions (attr_string, options);
CFRelease (options);
CFRelease (attr_string);
if (unlikely (!typesetter))
FAIL ("CTTypesetterCreateWithAttributedStringAndOptions failed");
line = CTTypesetterCreateLine (typesetter, CFRangeMake(0, 0));
CFRelease (typesetter);
if (unlikely (!line))
FAIL ("CTTypesetterCreateLine failed");
}
const CGPoint* positions = CTRunGetPositionsPtr (run);
if (!positions) {
ALLOCATE_ARRAY (CGPoint, position_buf, num_glyphs);
CTRunGetPositions (run, range_all, position_buf);
positions = position_buf;
}
CFArrayRef glyph_runs = CTLineGetGlyphRuns (line);
unsigned int num_runs = CFArrayGetCount (glyph_runs);
DEBUG_MSG (CORETEXT, NULL, "Num runs: %d", num_runs);
const CFIndex* string_indices = CTRunGetStringIndicesPtr (run);
if (!string_indices) {
ALLOCATE_ARRAY (CFIndex, index_buf, num_glyphs);
CTRunGetStringIndices (run, range_all, index_buf);
string_indices = index_buf;
}
buffer->len = 0;
uint32_t status_and = ~0, status_or = 0;
const CFRange range_all = CFRangeMake (0, 0);
for (unsigned int i = 0; i < num_runs; i++)
{
CTRunRef run = static_cast<CTRunRef>(CFArrayGetValueAtIndex (glyph_runs, i));
CTRunStatus run_status = CTRunGetStatus (run);
status_or |= run_status;
status_and &= run_status;
DEBUG_MSG (CORETEXT, run, "CTRunStatus: %x", run_status);
/* CoreText does automatic font fallback (AKA "cascading") for characters
* not supported by the requested font, and provides no way to turn it off,
* so we must detect if the returned run uses a font other than the requested
* one and fill in the buffer with .notdef glyphs instead of random glyph
* indices from a different font.
*/
CFDictionaryRef attributes = CTRunGetAttributes (run);
CTFontRef run_ct_font = static_cast<CTFontRef>(CFDictionaryGetValue (attributes, kCTFontAttributeName));
if (!CFEqual (run_ct_font, font_data->ct_font))
{
/* The run doesn't use our main font instance. We have to figure out
* whether font fallback happened, or this is just CoreText giving us
* another CTFont using the same underlying CGFont. CoreText seems
* to do that in a variety of situations, one of which being vertical
* text, but also perhaps for caching reasons.
*
* First, see if it uses any of our subfonts created to set font features...
*
* Next, compare the CGFont to the one we used to create our fonts.
* Even this doesn't work all the time.
*
* Finally, we compare PS names, which I don't think are unique...
*
* Looks like if we really want to be sure here we have to modify the
* font to change the name table, similar to what we do in the uniscribe
* backend.
*
* However, even that wouldn't work if we were passed in the CGFont to
* begin with.
*
* Webkit uses a slightly different approach: it installs LastResort
* as fallback chain, and then checks PS name of used font against
* LastResort. That one is safe for any font except for LastResort,
* as opposed to ours, which can fail if we are using any uninstalled
* font that has the same name as an installed font.
*
* See: http://github.com/behdad/harfbuzz/pull/36
*/
bool matched = false;
for (unsigned int i = 0; i < range_records.len; i++)
if (range_records[i].font && CFEqual (run_ct_font, range_records[i].font))
{
matched = true;
break;
}
if (!matched)
{
CGFontRef run_cg_font = CTFontCopyGraphicsFont (run_ct_font, 0);
if (run_cg_font)
{
matched = CFEqual (run_cg_font, face_data);
CFRelease (run_cg_font);
}
}
if (!matched)
{
CFStringRef font_ps_name = CTFontCopyName (font_data->ct_font, kCTFontPostScriptNameKey);
CFStringRef run_ps_name = CTFontCopyName (run_ct_font, kCTFontPostScriptNameKey);
CFComparisonResult result = CFStringCompare (run_ps_name, font_ps_name, 0);
CFRelease (run_ps_name);
CFRelease (font_ps_name);
if (result == kCFCompareEqualTo)
matched = true;
}
if (!matched)
{
CFRange range = CTRunGetStringRange (run);
DEBUG_MSG (CORETEXT, run, "Run used fallback font: %ld..%ld",
range.location, range.location + range.length);
if (!buffer->ensure_inplace (buffer->len + range.length))
goto resize_and_retry;
hb_glyph_info_t *info = buffer->info + buffer->len;
CGGlyph notdef = 0;
double advance = CTFontGetAdvancesForGlyphs (font_data->ct_font, kCTFontHorizontalOrientation, &notdef, NULL, 1);
unsigned int old_len = buffer->len;
for (CFIndex j = range.location; j < range.location + range.length; j++)
{
UniChar ch = CFStringGetCharacterAtIndex (string_ref, j);
if (hb_in_range<UniChar> (ch, 0xDC00u, 0xDFFFu) && range.location < j)
{
ch = CFStringGetCharacterAtIndex (string_ref, j - 1);
if (hb_in_range<UniChar> (ch, 0xD800u, 0xDBFFu))
/* This is the second of a surrogate pair. Don't need .notdef
* for this one. */
continue;
}
info->codepoint = notdef;
info->cluster = log_clusters[j];
info->mask = advance;
info->var1.u32 = 0;
info->var2.u32 = 0;
info++;
buffer->len++;
}
if (HB_DIRECTION_IS_BACKWARD (buffer->props.direction))
buffer->reverse_range (old_len, buffer->len);
continue;
}
}
unsigned int num_glyphs = CTRunGetGlyphCount (run);
if (num_glyphs == 0)
continue;
if (!buffer->ensure_inplace (buffer->len + num_glyphs))
goto resize_and_retry;
hb_glyph_info_t *run_info = buffer->info + buffer->len;
/* Testing used to indicate that CTRunGetGlyphsPtr, etc (almost?) always
* succeed, and so copying data to our own buffer will be rare. Reports
* have it that this changed in OS X 10.10 Yosemite, and NULL is returned
* frequently. At any rate, we can test that codepath by setting USE_PTR
* to false. */
#define USE_PTR true
#define SCRATCH_SAVE() \
unsigned int scratch_size_saved = scratch_size; \
hb_buffer_t::scratch_buffer_t *scratch_saved = scratch
#define SCRATCH_RESTORE() \
scratch_size = scratch_size_saved; \
scratch = scratch_saved;
{
SCRATCH_SAVE();
const CGGlyph* glyphs = USE_PTR ? CTRunGetGlyphsPtr (run) : NULL;
if (!glyphs) {
ALLOCATE_ARRAY (CGGlyph, glyph_buf, num_glyphs, goto resize_and_retry);
CTRunGetGlyphs (run, range_all, glyph_buf);
glyphs = glyph_buf;
}
const CFIndex* string_indices = USE_PTR ? CTRunGetStringIndicesPtr (run) : NULL;
if (!string_indices) {
ALLOCATE_ARRAY (CFIndex, index_buf, num_glyphs, goto resize_and_retry);
CTRunGetStringIndices (run, range_all, index_buf);
string_indices = index_buf;
}
hb_glyph_info_t *info = run_info;
for (unsigned int j = 0; j < num_glyphs; j++)
{
info->codepoint = glyphs[j];
info->cluster = log_clusters[string_indices[j]];
info++;
}
SCRATCH_RESTORE();
}
{
SCRATCH_SAVE();
const CGPoint* positions = USE_PTR ? CTRunGetPositionsPtr (run) : NULL;
if (!positions) {
ALLOCATE_ARRAY (CGPoint, position_buf, num_glyphs, goto resize_and_retry);
CTRunGetPositions (run, range_all, position_buf);
positions = position_buf;
}
double run_advance = CTRunGetTypographicBounds (run, range_all, NULL, NULL, NULL);
DEBUG_MSG (CORETEXT, run, "Run advance: %g", run_advance);
hb_glyph_info_t *info = run_info;
if (HB_DIRECTION_IS_HORIZONTAL (buffer->props.direction))
{
for (unsigned int j = 0; j < num_glyphs; j++)
{
double advance = (j + 1 < num_glyphs ? positions[j + 1].x : positions[0].x + run_advance) - positions[j].x;
info->mask = advance;
info->var1.u32 = positions[0].x; /* Yes, zero. */
info->var2.u32 = positions[j].y;
info++;
}
}
else
{
run_advance = -run_advance;
for (unsigned int j = 0; j < num_glyphs; j++)
{
double advance = (j + 1 < num_glyphs ? positions[j + 1].y : positions[0].y + run_advance) - positions[j].y;
info->mask = advance;
info->var1.u32 = positions[j].x;
info->var2.u32 = positions[0].y; /* Yes, zero. */
info++;
}
}
SCRATCH_RESTORE();
}
#undef SCRATCH_RESTORE
#undef SCRATCH_SAVE
#undef USE_PTR
#undef ALLOCATE_ARRAY
double run_width = CTRunGetTypographicBounds (run, range_all, NULL, NULL, NULL);
for (unsigned int j = 0; j < num_glyphs; j++) {
double advance = (j + 1 < num_glyphs ? positions[j + 1].x : positions[0].x + run_width) - positions[j].x;
hb_glyph_info_t *info = &buffer->info[buffer->len];
info->codepoint = glyphs[j];
info->cluster = string_indices[j];
/* Currently, we do all x-positioning by setting the advance, we never use x-offset. */
info->mask = advance;
info->var1.u32 = 0;
info->var2.u32 = positions[j].y;
buffer->len++;
buffer->len += num_glyphs;
}
}
buffer->clear_positions ();
/* 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);
unsigned int count = buffer->len;
for (unsigned int i = 0; i < count; ++i) {
hb_glyph_info_t *info = &buffer->info[i];
hb_glyph_position_t *pos = &buffer->pos[i];
buffer->clear_positions ();
/* TODO vertical */
pos->x_advance = info->mask;
pos->x_offset = info->var1.u32;
pos->y_offset = info->var2.u32;
}
/* Fix up clusters so that we never return out-of-order indices;
* if core text has reordered glyphs, we'll merge them to the
* beginning of the reordered cluster.
*
* This does *not* mean we'll form the same clusters as Uniscribe
* or the native OT backend, only that the cluster indices will be
* monotonic in the output buffer. */
if (HB_DIRECTION_IS_FORWARD (buffer->props.direction)) {
unsigned int prev_cluster = 0;
for (unsigned int i = 0; i < count; i++) {
unsigned int curr_cluster = buffer->info[i].cluster;
if (curr_cluster < prev_cluster) {
for (unsigned int j = i; j > 0; j--) {
if (buffer->info[j - 1].cluster > curr_cluster)
buffer->info[j - 1].cluster = curr_cluster;
else
break;
}
unsigned int count = buffer->len;
hb_glyph_info_t *info = buffer->info;
hb_glyph_position_t *pos = buffer->pos;
if (HB_DIRECTION_IS_HORIZONTAL (buffer->props.direction))
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;
info++, pos++;
}
prev_cluster = curr_cluster;
}
} else {
unsigned int prev_cluster = (unsigned int)-1;
for (unsigned int i = 0; i < count; i++) {
unsigned int curr_cluster = buffer->info[i].cluster;
if (curr_cluster > prev_cluster) {
for (unsigned int j = i; j > 0; j--) {
if (buffer->info[j - 1].cluster < curr_cluster)
buffer->info[j - 1].cluster = curr_cluster;
else
break;
}
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;
info++, pos++;
}
/* Fix up clusters so that we never return out-of-order indices;
* if core text has reordered glyphs, we'll merge them to the
* beginning of the reordered cluster. CoreText is nice enough
* to tell us whenever it has produced nonmonotonic results...
* Note that we assume the input clusters were nonmonotonic to
* begin with.
*
* This does *not* mean we'll form the same clusters as Uniscribe
* or the native OT backend, only that the cluster indices will be
* monotonic in the output buffer. */
if (count > 1 && (status_or & kCTRunStatusNonMonotonic))
{
hb_glyph_info_t *info = buffer->info;
if (HB_DIRECTION_IS_FORWARD (buffer->props.direction))
{
unsigned int cluster = info[count - 1].cluster;
for (unsigned int i = count - 1; i > 0; i--)
{
cluster = MIN (cluster, info[i - 1].cluster);
info[i - 1].cluster = cluster;
}
}
else
{
unsigned int cluster = info[0].cluster;
for (unsigned int i = 1; i < count; i++)
{
cluster = MIN (cluster, info[i].cluster);
info[i].cluster = cluster;
}
}
prev_cluster = curr_cluster;
}
}
CFRelease (string_ref);
CFRelease (line);
#undef FAIL
return true;
fail:
if (string_ref)
CFRelease (string_ref);
if (line)
CFRelease (line);
for (unsigned int i = 0; i < range_records.len; i++)
if (range_records[i].font)
CFRelease (range_records[i].font);
return ret;
}

View File

@ -66,7 +66,7 @@ struct hb_face_t {
{
hb_blob_t *blob;
if (unlikely (!this || !reference_table_func))
if (unlikely (!reference_table_func))
return hb_blob_get_empty ();
blob = reference_table_func (/*XXX*/const_cast<hb_face_t *> (this), tag, user_data);

View File

@ -136,7 +136,7 @@ hb_ft_get_glyph_v_origin (hb_font_t *font HB_UNUSED,
void *user_data HB_UNUSED)
{
FT_Face ft_face = (FT_Face) font_data;
int load_flags = FT_LOAD_DEFAULT;
int load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING;
if (unlikely (FT_Load_Glyph (ft_face, glyph, load_flags)))
return false;
@ -185,7 +185,7 @@ hb_ft_get_glyph_extents (hb_font_t *font HB_UNUSED,
void *user_data HB_UNUSED)
{
FT_Face ft_face = (FT_Face) font_data;
int load_flags = FT_LOAD_DEFAULT;
int load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING;
if (unlikely (FT_Load_Glyph (ft_face, glyph, load_flags)))
return false;
@ -455,7 +455,7 @@ retry:
goto retry;
}
#ifdef HAVE_ATEXIT
#ifdef HB_USE_ATEXIT
atexit (free_ft_library); /* First person registers atexit() callback. */
#endif
}

View File

@ -274,8 +274,8 @@ _hb_graphite2_shape (hb_shape_plan_t *shape_plan,
while ((DIV_CEIL (sizeof (hb_graphite2_cluster_t) * buffer->len, sizeof (*scratch)) +
DIV_CEIL (sizeof (hb_codepoint_t) * glyph_count, sizeof (*scratch))) > scratch_size)
{
buffer->ensure (buffer->allocated * 2);
if (unlikely (buffer->in_error)) {
if (unlikely (!buffer->ensure (buffer->allocated * 2)))
{
if (feats) gr_featureval_destroy (feats);
gr_seg_destroy (seg);
return false;

View File

@ -46,7 +46,7 @@
#include <windows.h>
typedef CRITICAL_SECTION hb_mutex_impl_t;
#define HB_MUTEX_IMPL_INIT { NULL, 0, 0, NULL, NULL, 0 }
#define HB_MUTEX_IMPL_INIT {0}
#define hb_mutex_impl_init(M) InitializeCriticalSection (M)
#define hb_mutex_impl_lock(M) EnterCriticalSection (M)
#define hb_mutex_impl_unlock(M) LeaveCriticalSection (M)

View File

@ -68,8 +68,6 @@ struct hb_reference_count_t
#define HB_USER_DATA_ARRAY_INIT {HB_MUTEX_INIT, HB_LOCKABLE_SET_INIT}
struct hb_user_data_array_t
{
/* TODO Add tracing. */
struct hb_user_data_item_t {
hb_user_data_key_t *key;
void *data;
@ -106,69 +104,6 @@ struct hb_object_header_t
#define HB_OBJECT_HEADER_STATIC {HB_REFERENCE_COUNT_INVALID, HB_USER_DATA_ARRAY_INIT}
static inline void *create (unsigned int size) {
hb_object_header_t *obj = (hb_object_header_t *) calloc (1, size);
if (likely (obj))
obj->init ();
return obj;
}
inline void init (void) {
ref_count.init (1);
user_data.init ();
}
inline bool is_inert (void) const {
return unlikely (ref_count.is_invalid ());
}
inline void reference (void) {
if (unlikely (!this || this->is_inert ()))
return;
ref_count.inc ();
}
inline bool destroy (void) {
if (unlikely (!this || this->is_inert ()))
return false;
if (ref_count.dec () != 1)
return false;
ref_count.finish (); /* Do this before user_data */
user_data.finish ();
return true;
}
inline bool set_user_data (hb_user_data_key_t *key,
void * data,
hb_destroy_func_t destroy_func,
hb_bool_t replace) {
if (unlikely (!this || this->is_inert ()))
return false;
return user_data.set (key, data, destroy_func, replace);
}
inline void *get_user_data (hb_user_data_key_t *key) {
if (unlikely (!this || this->is_inert ()))
return NULL;
return user_data.get (key);
}
inline void trace (const char *function) const {
if (unlikely (!this)) return;
/* TODO We cannot use DEBUG_MSG_FUNC here since that one currently only
* prints the class name and throws away the template info. */
DEBUG_MSG (OBJECT, (void *) this,
"%s refcount=%d",
function,
this ? ref_count.ref_count : 0);
}
private:
ASSERT_POD ();
};
@ -179,32 +114,56 @@ struct hb_object_header_t
template <typename Type>
static inline void hb_object_trace (const Type *obj, const char *function)
{
obj->header.trace (function);
DEBUG_MSG (OBJECT, (void *) obj,
"%s refcount=%d",
function,
obj ? obj->header.ref_count.ref_count : 0);
}
template <typename Type>
static inline Type *hb_object_create (void)
{
Type *obj = (Type *) hb_object_header_t::create (sizeof (Type));
Type *obj = (Type *) calloc (1, sizeof (Type));
if (unlikely (!obj))
return obj;
hb_object_init (obj);
hb_object_trace (obj, HB_FUNC);
return obj;
}
template <typename Type>
static inline void hb_object_init (Type *obj)
{
obj->header.ref_count.init (1);
obj->header.user_data.init ();
}
template <typename Type>
static inline bool hb_object_is_inert (const Type *obj)
{
return unlikely (obj->header.is_inert ());
return unlikely (obj->header.ref_count.is_invalid ());
}
template <typename Type>
static inline Type *hb_object_reference (Type *obj)
{
hb_object_trace (obj, HB_FUNC);
obj->header.reference ();
if (unlikely (!obj || hb_object_is_inert (obj)))
return obj;
obj->header.ref_count.inc ();
return obj;
}
template <typename Type>
static inline bool hb_object_destroy (Type *obj)
{
hb_object_trace (obj, HB_FUNC);
return obj->header.destroy ();
if (unlikely (!obj || hb_object_is_inert (obj)))
return false;
if (obj->header.ref_count.dec () != 1)
return false;
obj->header.ref_count.finish (); /* Do this before user_data */
obj->header.user_data.finish ();
return true;
}
template <typename Type>
static inline bool hb_object_set_user_data (Type *obj,
@ -213,14 +172,18 @@ static inline bool hb_object_set_user_data (Type *obj,
hb_destroy_func_t destroy,
hb_bool_t replace)
{
return obj->header.set_user_data (key, data, destroy, replace);
if (unlikely (!obj || hb_object_is_inert (obj)))
return false;
return obj->header.user_data.set (key, data, destroy, replace);
}
template <typename Type>
static inline void *hb_object_get_user_data (Type *obj,
hb_user_data_key_t *key)
{
return obj->header.get_user_data (key);
if (unlikely (!obj || hb_object_is_inert (obj)))
return NULL;
return obj->header.user_data.get (key);
}

View File

@ -197,6 +197,8 @@ struct TTCHeader
struct OpenTypeFontFile
{
static const hb_tag_t tableTag = HB_TAG ('_','_','_','_'); /* Sanitizer needs this. */
static const hb_tag_t CFFTag = HB_TAG ('O','T','T','O'); /* OpenType with Postscript outlines */
static const hb_tag_t TrueTypeTag = HB_TAG ( 0 , 1 , 0 , 0 ); /* OpenType with TrueType outlines */
static const hb_tag_t TTCTag = HB_TAG ('t','t','c','f'); /* TrueType Collection */

View File

@ -194,10 +194,11 @@ struct hb_sanitize_context_t
{
this->start = hb_blob_get_data (this->blob, NULL);
this->end = this->start + hb_blob_get_length (this->blob);
assert (this->start <= this->end); /* Must not overflow. */
this->edit_count = 0;
this->debug_depth = 0;
DEBUG_MSG_LEVEL (SANITIZE, this->blob, 0, +1,
DEBUG_MSG_LEVEL (SANITIZE, start, 0, +1,
"start [%p..%p] (%lu bytes)",
this->start, this->end,
(unsigned long) (this->end - this->start));
@ -205,7 +206,7 @@ struct hb_sanitize_context_t
inline void end_processing (void)
{
DEBUG_MSG_LEVEL (SANITIZE, this->blob, 0, -1,
DEBUG_MSG_LEVEL (SANITIZE, this->start, 0, -1,
"end [%p..%p] %u edit requests",
this->start, this->end, this->edit_count);
@ -217,28 +218,31 @@ struct hb_sanitize_context_t
inline bool check_range (const void *base, unsigned int len) const
{
const char *p = (const char *) base;
bool ok = this->start <= p && p <= this->end && (unsigned int) (this->end - p) >= len;
hb_auto_trace_t<HB_DEBUG_SANITIZE, bool> trace
(&this->debug_depth, "SANITIZE", this->blob, NULL,
"check_range [%p..%p] (%d bytes) in [%p..%p]",
DEBUG_MSG_LEVEL (SANITIZE, p, this->debug_depth+1, 0,
"check_range [%p..%p] (%d bytes) in [%p..%p] -> %s",
p, p + len, len,
this->start, this->end);
this->start, this->end,
ok ? "OK" : "OUT-OF-RANGE");
return TRACE_RETURN (likely (this->start <= p && p <= this->end && (unsigned int) (this->end - p) >= len));
return likely (ok);
}
inline bool check_array (const void *base, unsigned int record_size, unsigned int len) const
{
const char *p = (const char *) base;
bool overflows = _hb_unsigned_int_mul_overflows (len, record_size);
unsigned int array_size = record_size * len;
bool ok = !overflows && this->check_range (base, array_size);
hb_auto_trace_t<HB_DEBUG_SANITIZE, bool> trace
(&this->debug_depth, "SANITIZE", this->blob, NULL,
"check_array [%p..%p] (%d*%d=%ld bytes) in [%p..%p]",
p, p + (record_size * len), record_size, len, (unsigned long) record_size * len,
this->start, this->end);
DEBUG_MSG_LEVEL (SANITIZE, p, this->debug_depth+1, 0,
"check_array [%p..%p] (%d*%d=%d bytes) in [%p..%p] -> %s",
p, p + (record_size * len), record_size, len, (unsigned int) array_size,
this->start, this->end,
overflows ? "OVERFLOWS" : ok ? "OK" : "OUT-OF-RANGE");
return TRACE_RETURN (likely (!overflows && this->check_range (base, record_size * len)));
return likely (ok);
}
template <typename Type>
@ -255,15 +259,14 @@ struct hb_sanitize_context_t
const char *p = (const char *) base;
this->edit_count++;
hb_auto_trace_t<HB_DEBUG_SANITIZE, bool> trace
(&this->debug_depth, "SANITIZE", this->blob, NULL,
DEBUG_MSG_LEVEL (SANITIZE, p, this->debug_depth+1, 0,
"may_edit(%u) [%p..%p] (%d bytes) in [%p..%p] -> %s",
this->edit_count,
p, p + len, len,
this->start, this->end,
this->writable ? "GRANTED" : "DENIED");
return TRACE_RETURN (this->writable);
return this->writable;
}
template <typename Type, typename ValueType>
@ -297,7 +300,7 @@ struct Sanitizer
c->init (blob);
retry:
DEBUG_MSG_FUNC (SANITIZE, blob, "start");
DEBUG_MSG_FUNC (SANITIZE, c->start, "start");
c->start_processing ();
@ -311,13 +314,13 @@ struct Sanitizer
sane = t->sanitize (c);
if (sane) {
if (c->edit_count) {
DEBUG_MSG_FUNC (SANITIZE, blob, "passed first round with %d edits; going for second round", c->edit_count);
DEBUG_MSG_FUNC (SANITIZE, c->start, "passed first round with %d edits; going for second round", c->edit_count);
/* sanitize again to ensure no toe-stepping */
c->edit_count = 0;
sane = t->sanitize (c);
if (c->edit_count) {
DEBUG_MSG_FUNC (SANITIZE, blob, "requested %d edits in second round; FAILLING", c->edit_count);
DEBUG_MSG_FUNC (SANITIZE, c->start, "requested %d edits in second round; FAILLING", c->edit_count);
sane = false;
}
}
@ -330,7 +333,7 @@ struct Sanitizer
if (c->start) {
c->writable = true;
/* ok, we made it writable by relocating. try again */
DEBUG_MSG_FUNC (SANITIZE, blob, "retry");
DEBUG_MSG_FUNC (SANITIZE, c->start, "retry");
goto retry;
}
}
@ -338,7 +341,7 @@ struct Sanitizer
c->end_processing ();
DEBUG_MSG_FUNC (SANITIZE, blob, sane ? "PASSED" : "FAILED");
DEBUG_MSG_FUNC (SANITIZE, c->start, sane ? "PASSED" : "FAILED");
if (sane)
return blob;
else {
@ -533,32 +536,77 @@ template <typename Type>
struct BEInt<Type, 2>
{
public:
inline void set (Type i) { hb_be_uint16_put (v,i); }
inline operator Type (void) const { return hb_be_uint16_get (v); }
inline bool operator == (const BEInt<Type, 2>& o) const { return hb_be_uint16_eq (v, o.v); }
inline void set (Type V)
{
v[0] = (V >> 8) & 0xFF;
v[1] = (V ) & 0xFF;
}
inline operator Type (void) const
{
return (v[0] << 8)
+ (v[1] );
}
inline bool operator == (const BEInt<Type, 2>& o) const
{
return v[0] == o.v[0]
&& v[1] == o.v[1];
}
inline bool operator != (const BEInt<Type, 2>& o) const { return !(*this == o); }
private: uint8_t v[2];
};
template <typename Type>
struct BEInt<Type, 4>
{
public:
inline void set (Type i) { hb_be_uint32_put (v,i); }
inline operator Type (void) const { return hb_be_uint32_get (v); }
inline bool operator == (const BEInt<Type, 4>& o) const { return hb_be_uint32_eq (v, o.v); }
inline bool operator != (const BEInt<Type, 4>& o) const { return !(*this == o); }
private: uint8_t v[4];
};
template <typename Type>
struct BEInt<Type, 3>
{
public:
inline void set (Type i) { hb_be_uint24_put (v,i); }
inline operator Type (void) const { return hb_be_uint24_get (v); }
inline bool operator == (const BEInt<Type, 3>& o) const { return hb_be_uint24_eq (v, o.v); }
inline void set (Type V)
{
v[0] = (V >> 16) & 0xFF;
v[1] = (V >> 8) & 0xFF;
v[2] = (V ) & 0xFF;
}
inline operator Type (void) const
{
return (v[0] << 16)
+ (v[1] << 8)
+ (v[2] );
}
inline bool operator == (const BEInt<Type, 3>& o) const
{
return v[0] == o.v[0]
&& v[1] == o.v[1]
&& v[2] == o.v[2];
}
inline bool operator != (const BEInt<Type, 3>& o) const { return !(*this == o); }
private: uint8_t v[3];
};
template <typename Type>
struct BEInt<Type, 4>
{
public:
inline void set (Type V)
{
v[0] = (V >> 24) & 0xFF;
v[1] = (V >> 16) & 0xFF;
v[2] = (V >> 8) & 0xFF;
v[3] = (V ) & 0xFF;
}
inline operator Type (void) const
{
return (v[0] << 24)
+ (v[1] << 16)
+ (v[2] << 8)
+ (v[3] );
}
inline bool operator == (const BEInt<Type, 4>& o) const
{
return v[0] == o.v[0]
&& v[1] == o.v[1]
&& v[2] == o.v[2]
&& v[3] == o.v[3];
}
inline bool operator != (const BEInt<Type, 4>& o) const { return !(*this == o); }
private: uint8_t v[4];
};
/* Integer types in big-endian order and no alignment requirement */
template <typename Type, unsigned int Size>

View File

@ -35,17 +35,128 @@
#include "hb-ot-hmtx-table.hh"
struct hb_ot_face_metrics_accelerator_t
{
unsigned int num_metrics;
unsigned int num_advances;
unsigned int default_advance;
const OT::_mtx *table;
hb_blob_t *blob;
inline void init (hb_face_t *face,
hb_tag_t _hea_tag, hb_tag_t _mtx_tag,
unsigned int default_advance)
{
this->default_advance = default_advance;
this->num_metrics = face->get_num_glyphs ();
hb_blob_t *_hea_blob = OT::Sanitizer<OT::_hea>::sanitize (face->reference_table (_hea_tag));
const OT::_hea *_hea = OT::Sanitizer<OT::_hea>::lock_instance (_hea_blob);
this->num_advances = _hea->numberOfLongMetrics;
hb_blob_destroy (_hea_blob);
this->blob = OT::Sanitizer<OT::_mtx>::sanitize (face->reference_table (_mtx_tag));
if (unlikely (!this->num_advances ||
2 * (this->num_advances + this->num_metrics) < hb_blob_get_length (this->blob)))
{
this->num_metrics = this->num_advances = 0;
hb_blob_destroy (this->blob);
this->blob = hb_blob_get_empty ();
}
this->table = OT::Sanitizer<OT::_mtx>::lock_instance (this->blob);
}
inline void fini (void)
{
hb_blob_destroy (this->blob);
}
inline unsigned int get_advance (hb_codepoint_t glyph) const
{
if (unlikely (glyph >= this->num_metrics))
{
/* If this->num_metrics is zero, it means we don't have the metrics table
* for this direction: return one EM. Otherwise, it means that the glyph
* index is out of bound: return zero. */
if (this->num_metrics)
return 0;
else
return this->default_advance;
}
if (glyph >= this->num_advances)
glyph = this->num_advances - 1;
return this->table->longMetric[glyph].advance;
}
};
struct hb_ot_face_cmap_accelerator_t
{
const OT::CmapSubtable *table;
const OT::CmapSubtable *uvs_table;
hb_blob_t *blob;
inline void init (hb_face_t *face)
{
this->blob = OT::Sanitizer<OT::cmap>::sanitize (face->reference_table (HB_OT_TAG_cmap));
const OT::cmap *cmap = OT::Sanitizer<OT::cmap>::lock_instance (this->blob);
const OT::CmapSubtable *subtable = NULL;
const OT::CmapSubtable *subtable_uvs = NULL;
/* 32-bit subtables. */
if (!subtable) subtable = cmap->find_subtable (3, 10);
if (!subtable) subtable = cmap->find_subtable (0, 6);
if (!subtable) subtable = cmap->find_subtable (0, 4);
/* 16-bit subtables. */
if (!subtable) subtable = cmap->find_subtable (3, 1);
if (!subtable) subtable = cmap->find_subtable (0, 3);
if (!subtable) subtable = cmap->find_subtable (0, 2);
if (!subtable) subtable = cmap->find_subtable (0, 1);
if (!subtable) subtable = cmap->find_subtable (0, 0);
/* Meh. */
if (!subtable) subtable = &OT::Null(OT::CmapSubtable);
/* UVS subtable. */
if (!subtable_uvs) subtable_uvs = cmap->find_subtable (0, 5);
/* Meh. */
if (!subtable_uvs) subtable_uvs = &OT::Null(OT::CmapSubtable);
this->table = subtable;
this->uvs_table = subtable_uvs;
}
inline void fini (void)
{
hb_blob_destroy (this->blob);
}
inline bool get_glyph (hb_codepoint_t unicode,
hb_codepoint_t variation_selector,
hb_codepoint_t *glyph) const
{
if (unlikely (variation_selector))
{
switch (this->uvs_table->get_glyph_variant (unicode,
variation_selector,
glyph))
{
case OT::GLYPH_VARIANT_NOT_FOUND: return false;
case OT::GLYPH_VARIANT_FOUND: return true;
case OT::GLYPH_VARIANT_USE_DEFAULT: break;
}
}
return this->table->get_glyph (unicode, glyph);
}
};
struct hb_ot_font_t
{
unsigned int num_glyphs;
unsigned int num_hmetrics;
const OT::hmtx *hmtx;
hb_blob_t *hmtx_blob;
const OT::CmapSubtable *cmap;
const OT::CmapSubtable *cmap_uvs;
hb_blob_t *cmap_blob;
hb_ot_face_cmap_accelerator_t cmap;
hb_ot_face_metrics_accelerator_t h_metrics;
hb_ot_face_metrics_accelerator_t v_metrics;
};
@ -53,50 +164,16 @@ static hb_ot_font_t *
_hb_ot_font_create (hb_font_t *font)
{
hb_ot_font_t *ot_font = (hb_ot_font_t *) calloc (1, sizeof (hb_ot_font_t));
hb_face_t *face = font->face;
if (unlikely (!ot_font))
return NULL;
ot_font->num_glyphs = font->face->get_num_glyphs ();
unsigned int upem = face->get_upem ();
{
hb_blob_t *hhea_blob = OT::Sanitizer<OT::hhea>::sanitize (font->face->reference_table (HB_OT_TAG_hhea));
const OT::hhea *hhea = OT::Sanitizer<OT::hhea>::lock_instance (hhea_blob);
ot_font->num_hmetrics = hhea->numberOfHMetrics;
hb_blob_destroy (hhea_blob);
}
ot_font->hmtx_blob = OT::Sanitizer<OT::hmtx>::sanitize (font->face->reference_table (HB_OT_TAG_hmtx));
if (unlikely (!ot_font->num_hmetrics ||
2 * (ot_font->num_hmetrics + ot_font->num_glyphs) < hb_blob_get_length (ot_font->hmtx_blob)))
{
hb_blob_destroy (ot_font->hmtx_blob);
free (ot_font);
return NULL;
}
ot_font->hmtx = OT::Sanitizer<OT::hmtx>::lock_instance (ot_font->hmtx_blob);
ot_font->cmap_blob = OT::Sanitizer<OT::cmap>::sanitize (font->face->reference_table (HB_OT_TAG_cmap));
const OT::cmap *cmap = OT::Sanitizer<OT::cmap>::lock_instance (ot_font->cmap_blob);
const OT::CmapSubtable *subtable = NULL;
const OT::CmapSubtable *subtable_uvs = NULL;
/* 32-bit subtables. */
if (!subtable) subtable = cmap->find_subtable (0, 6);
if (!subtable) subtable = cmap->find_subtable (0, 4);
if (!subtable) subtable = cmap->find_subtable (3, 10);
/* 16-bit subtables. */
if (!subtable) subtable = cmap->find_subtable (0, 3);
if (!subtable) subtable = cmap->find_subtable (3, 1);
/* Meh. */
if (!subtable) subtable = &OT::Null(OT::CmapSubtable);
/* UVS subtable. */
if (!subtable_uvs) subtable_uvs = cmap->find_subtable (0, 5);
/* Meh. */
if (!subtable_uvs) subtable_uvs = &OT::Null(OT::CmapSubtable);
ot_font->cmap = subtable;
ot_font->cmap_uvs = subtable_uvs;
ot_font->cmap.init (face);
ot_font->h_metrics.init (face, HB_OT_TAG_hhea, HB_OT_TAG_hmtx, upem>>1);
ot_font->v_metrics.init (face, HB_OT_TAG_vhea, HB_OT_TAG_vmtx, upem); /* TODO Can we do this lazily? */
return ot_font;
}
@ -104,8 +181,9 @@ _hb_ot_font_create (hb_font_t *font)
static void
_hb_ot_font_destroy (hb_ot_font_t *ot_font)
{
hb_blob_destroy (ot_font->cmap_blob);
hb_blob_destroy (ot_font->hmtx_blob);
ot_font->cmap.fini ();
ot_font->h_metrics.fini ();
ot_font->v_metrics.fini ();
free (ot_font);
}
@ -121,20 +199,7 @@ hb_ot_get_glyph (hb_font_t *font HB_UNUSED,
{
const hb_ot_font_t *ot_font = (const hb_ot_font_t *) font_data;
if (unlikely (variation_selector))
{
switch (ot_font->cmap_uvs->get_glyph_variant (unicode,
variation_selector,
glyph))
{
case OT::GLYPH_VARIANT_NOT_FOUND: return false;
case OT::GLYPH_VARIANT_FOUND: return true;
case OT::GLYPH_VARIANT_USE_DEFAULT: break;
}
}
return ot_font->cmap->get_glyph (unicode, glyph);
return ot_font->cmap.get_glyph (unicode, variation_selector, glyph);
}
static hb_position_t
@ -144,14 +209,7 @@ hb_ot_get_glyph_h_advance (hb_font_t *font HB_UNUSED,
void *user_data HB_UNUSED)
{
const hb_ot_font_t *ot_font = (const hb_ot_font_t *) font_data;
if (unlikely (glyph >= ot_font->num_glyphs))
return 0; /* Maybe better to return notdef's advance instead? */
if (glyph >= ot_font->num_hmetrics)
glyph = ot_font->num_hmetrics - 1;
return font->em_scale_x (ot_font->hmtx->longHorMetric[glyph].advanceWidth);
return font->em_scale_x (ot_font->h_metrics.get_advance (glyph));
}
static hb_position_t
@ -160,8 +218,8 @@ hb_ot_get_glyph_v_advance (hb_font_t *font HB_UNUSED,
hb_codepoint_t glyph,
void *user_data HB_UNUSED)
{
/* TODO */
return 0;
const hb_ot_font_t *ot_font = (const hb_ot_font_t *) font_data;
return font->em_scale_y (-ot_font->v_metrics.get_advance (glyph));
}
static hb_bool_t
@ -206,6 +264,7 @@ hb_ot_get_glyph_v_kerning (hb_font_t *font HB_UNUSED,
hb_codepoint_t bottom_glyph HB_UNUSED,
void *user_data HB_UNUSED)
{
/* OpenType doesn't have vertical-kerning other than GPOS. */
return 0;
}

View File

@ -35,14 +35,19 @@ namespace OT {
/*
* hhea -- The Horizontal Header Table
* vhea -- The Vertical Header Table
*/
#define HB_OT_TAG_hhea HB_TAG('h','h','e','a')
#define HB_OT_TAG_vhea HB_TAG('v','h','e','a')
struct hhea
struct _hea
{
static const hb_tag_t tableTag = HB_OT_TAG_hhea;
static const hb_tag_t tableTag = HB_TAG('_','h','e','a');
static const hb_tag_t hheaTag = HB_OT_TAG_hhea;
static const hb_tag_t vheaTag = HB_OT_TAG_vhea;
inline bool sanitize (hb_sanitize_context_t *c) {
TRACE_SANITIZE (this);
@ -51,45 +56,45 @@ struct hhea
public:
FixedVersion version; /* 0x00010000u for version 1.0. */
FWORD ascender; /* Typographic ascent. <a
* href="http://developer.apple.com/fonts/TTRefMan/RM06/Chap6hhea.html">
* (Distance from baseline of highest
* ascender)</a> */
FWORD descender; /* Typographic descent. <a
* href="http://developer.apple.com/fonts/TTRefMan/RM06/Chap6hhea.html">
* (Distance from baseline of lowest
* descender)</a> */
FWORD lineGap; /* Typographic line gap. Negative
* LineGap values are treated as zero
* in Windows 3.1, System 6, and
* System 7. */
UFWORD advanceWidthMax; /* Maximum advance width value in
* 'hmtx' table. */
FWORD minLeftSideBearing; /* Minimum left sidebearing value in
* 'hmtx' table. */
FWORD minRightSideBearing; /* Minimum right sidebearing value;
FWORD ascender; /* Typographic ascent. */
FWORD descender; /* Typographic descent. */
FWORD lineGap; /* Typographic line gap. */
UFWORD advanceMax; /* Maximum advance width/height value in
* metrics table. */
FWORD minLeadingBearing; /* Minimum left/top sidebearing value in
* metrics table. */
FWORD minTrailingBearing; /* Minimum right/bottom sidebearing value;
* calculated as Min(aw - lsb -
* (xMax - xMin)). */
FWORD xMaxExtent; /* Max(lsb + (xMax - xMin)). */
* (xMax - xMin)) for horizontal. */
FWORD maxExtent; /* horizontal: Max(lsb + (xMax - xMin)),
* vertical: minLeadingBearing+(yMax-yMin). */
SHORT caretSlopeRise; /* Used to calculate the slope of the
* cursor (rise/run); 1 for vertical. */
SHORT caretSlopeRun; /* 0 for vertical. */
* cursor (rise/run); 1 for vertical caret,
* 0 for horizontal.*/
SHORT caretSlopeRun; /* 0 for vertical caret, 1 for horizontal. */
SHORT caretOffset; /* The amount by which a slanted
* highlight on a glyph needs
* to be shifted to produce the
* best appearance. Set to 0 for
* non--slanted fonts */
SHORT reserved1; /* set to 0 */
SHORT reserved2; /* set to 0 */
SHORT reserved3; /* set to 0 */
SHORT reserved4; /* set to 0 */
* non-slanted fonts. */
SHORT reserved1; /* Set to 0. */
SHORT reserved2; /* Set to 0. */
SHORT reserved3; /* Set to 0. */
SHORT reserved4; /* Set to 0. */
SHORT metricDataFormat; /* 0 for current format. */
USHORT numberOfHMetrics; /* Number of hMetric entries in 'hmtx'
* table */
USHORT numberOfLongMetrics; /* Number of LongMetric entries in metric
* table. */
public:
DEFINE_SIZE_STATIC (36);
};
struct hhea : _hea {
static const hb_tag_t tableTag = HB_OT_TAG_hhea;
};
struct vhea : _hea {
static const hb_tag_t tableTag = HB_OT_TAG_vhea;
};
} /* namespace OT */

View File

@ -35,22 +35,27 @@ namespace OT {
/*
* hmtx -- The Horizontal Metrics Table
* vmtx -- The Vertical Metrics Table
*/
#define HB_OT_TAG_hmtx HB_TAG('h','m','t','x')
#define HB_OT_TAG_vmtx HB_TAG('v','m','t','x')
struct LongHorMetric
struct LongMetric
{
USHORT advanceWidth;
SHORT lsb;
USHORT advance; /* Advance width/height. */
SHORT lsb; /* Leading (left/top) side bearing. */
public:
DEFINE_SIZE_STATIC (4);
};
struct hmtx
struct _mtx
{
static const hb_tag_t tableTag = HB_OT_TAG_hmtx;
static const hb_tag_t tableTag = HB_TAG('_','m','t','x');
static const hb_tag_t hmtxTag = HB_OT_TAG_hmtx;
static const hb_tag_t vmtxTag = HB_OT_TAG_vmtx;
inline bool sanitize (hb_sanitize_context_t *c) {
TRACE_SANITIZE (this);
@ -60,7 +65,7 @@ struct hmtx
}
public:
LongHorMetric longHorMetric[VAR]; /* Paired advance width and left side
LongMetric longMetric[VAR]; /* Paired advance width and leading
* bearing values for each glyph. The
* value numOfHMetrics comes from
* the 'hhea' table. If the font is
@ -68,23 +73,29 @@ struct hmtx
* be in the array, but that entry is
* required. The last entry applies to
* all subsequent glyphs. */
SHORT leftSideBearingX[VAR]; /* Here the advanceWidth is assumed
* to be the same as the advanceWidth
SHORT leadingBearingX[VAR]; /* Here the advance is assumed
* to be the same as the advance
* for the last entry above. The
* number of entries in this array is
* derived from numGlyphs (from 'maxp'
* table) minus numberOfHMetrics. This
* generally is used with a run of
* monospaced glyphs (e.g., Kanji
* table) minus numberOfLongMetrics.
* This generally is used with a run
* of monospaced glyphs (e.g., Kanji
* fonts or Courier fonts). Only one
* run is allowed and it must be at
* the end. This allows a monospaced
* font to vary the left side bearing
* font to vary the side bearing
* values for each glyph. */
public:
DEFINE_SIZE_ARRAY2 (0, longHorMetric, leftSideBearingX);
DEFINE_SIZE_ARRAY2 (0, longMetric, leadingBearingX);
};
struct hmtx : _mtx {
static const hb_tag_t tableTag = HB_OT_TAG_hmtx;
};
struct vmtx : _mtx {
static const hb_tag_t tableTag = HB_OT_TAG_vmtx;
};
} /* namespace OT */

View File

@ -345,8 +345,8 @@ struct AnchorMatrix
inline const Anchor& get_anchor (unsigned int row, unsigned int col, unsigned int cols, bool *found) const {
*found = false;
if (unlikely (row >= rows || col >= cols)) return Null(Anchor);
*found = !matrix[row * cols + col].is_null ();
return this+matrix[row * cols + col];
*found = !matrixZ[row * cols + col].is_null ();
return this+matrixZ[row * cols + col];
}
inline bool sanitize (hb_sanitize_context_t *c, unsigned int cols) {
@ -354,19 +354,19 @@ struct AnchorMatrix
if (!c->check_struct (this)) return TRACE_RETURN (false);
if (unlikely (rows > 0 && cols >= ((unsigned int) -1) / rows)) return TRACE_RETURN (false);
unsigned int count = rows * cols;
if (!c->check_array (matrix, matrix[0].static_size, count)) return TRACE_RETURN (false);
if (!c->check_array (matrixZ, matrixZ[0].static_size, count)) return TRACE_RETURN (false);
for (unsigned int i = 0; i < count; i++)
if (!matrix[i].sanitize (c, this)) return TRACE_RETURN (false);
if (!matrixZ[i].sanitize (c, this)) return TRACE_RETURN (false);
return TRACE_RETURN (true);
}
USHORT rows; /* Number of rows */
protected:
OffsetTo<Anchor>
matrix[VAR]; /* Matrix of offsets to Anchor tables--
matrixZ[VAR]; /* Matrix of offsets to Anchor tables--
* from beginning of AnchorMatrix table */
public:
DEFINE_SIZE_ARRAY (2, matrix);
DEFINE_SIZE_ARRAY (2, matrixZ);
};
@ -530,7 +530,7 @@ struct SinglePos
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
case 2: return TRACE_RETURN (c->dispatch (u.format2));
@ -583,7 +583,7 @@ struct PairSet
unsigned int len2 = valueFormats[1].get_len ();
unsigned int record_size = USHORT::static_size * (1 + len1 + len2);
const PairValueRecord *record = CastP<PairValueRecord> (array);
const PairValueRecord *record = CastP<PairValueRecord> (arrayZ);
unsigned int count = len;
for (unsigned int i = 0; i < count; i++)
{
@ -602,7 +602,7 @@ struct PairSet
unsigned int len2 = valueFormats[1].get_len ();
unsigned int record_size = USHORT::static_size * (1 + len1 + len2);
const PairValueRecord *record = CastP<PairValueRecord> (array);
const PairValueRecord *record = CastP<PairValueRecord> (arrayZ);
unsigned int count = len;
for (unsigned int i = 0; i < count; i++)
{
@ -634,20 +634,20 @@ struct PairSet
inline bool sanitize (hb_sanitize_context_t *c, const sanitize_closure_t *closure) {
TRACE_SANITIZE (this);
if (!(c->check_struct (this)
&& c->check_array (array, USHORT::static_size * closure->stride, len))) return TRACE_RETURN (false);
&& c->check_array (arrayZ, USHORT::static_size * closure->stride, len))) return TRACE_RETURN (false);
unsigned int count = len;
PairValueRecord *record = CastP<PairValueRecord> (array);
PairValueRecord *record = CastP<PairValueRecord> (arrayZ);
return TRACE_RETURN (closure->valueFormats[0].sanitize_values_stride_unsafe (c, closure->base, &record->values[0], count, closure->stride)
&& closure->valueFormats[1].sanitize_values_stride_unsafe (c, closure->base, &record->values[closure->len1], count, closure->stride));
}
protected:
USHORT len; /* Number of PairValueRecords */
USHORT array[VAR]; /* Array of PairValueRecords--ordered
USHORT arrayZ[VAR]; /* Array of PairValueRecords--ordered
* by GlyphID of the second glyph */
public:
DEFINE_SIZE_ARRAY (2, array);
DEFINE_SIZE_ARRAY (2, arrayZ);
};
struct PairPosFormat1
@ -822,7 +822,7 @@ struct PairPos
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
case 2: return TRACE_RETURN (c->dispatch (u.format2));
@ -989,7 +989,7 @@ struct CursivePos
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
default:return TRACE_RETURN (c->default_return_value ());
@ -1088,7 +1088,7 @@ struct MarkBasePos
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
default:return TRACE_RETURN (c->default_return_value ());
@ -1209,7 +1209,7 @@ struct MarkLigPos
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
default:return TRACE_RETURN (c->default_return_value ());
@ -1328,7 +1328,7 @@ struct MarkMarkPos
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
default:return TRACE_RETURN (c->default_return_value ());
@ -1387,7 +1387,7 @@ struct PosLookupSubTable
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c, unsigned int lookup_type) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, lookup_type);
switch (lookup_type) {
case Single: return TRACE_RETURN (u.single.dispatch (c));
case Pair: return TRACE_RETURN (u.pair.dispatch (c));
@ -1488,8 +1488,8 @@ struct PosLookup : Lookup
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
unsigned int lookup_type = get_type ();
TRACE_DISPATCH (this, lookup_type);
unsigned int count = get_subtable_count ();
for (unsigned int i = 0; i < count; i++) {
typename context_t::return_t r = get_subtable (i).dispatch (c, lookup_type);

View File

@ -200,7 +200,7 @@ struct SingleSubst
TRACE_SERIALIZE (this);
if (unlikely (!c->extend_min (u.format))) return TRACE_RETURN (false);
unsigned int format = 2;
int delta;
int delta = 0;
if (num_glyphs) {
format = 1;
/* TODO(serialize) check for wrap-around */
@ -222,7 +222,7 @@ struct SingleSubst
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
case 2: return TRACE_RETURN (c->dispatch (u.format2));
@ -422,7 +422,7 @@ struct MultipleSubst
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
default:return TRACE_RETURN (c->default_return_value ());
@ -573,7 +573,7 @@ struct AlternateSubst
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
default:return TRACE_RETURN (c->default_return_value ());
@ -889,7 +889,7 @@ struct LigatureSubst
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
default:return TRACE_RETURN (c->default_return_value ());
@ -1053,7 +1053,7 @@ struct ReverseChainSingleSubst
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
default:return TRACE_RETURN (c->default_return_value ());
@ -1100,7 +1100,7 @@ struct SubstLookupSubTable
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c, unsigned int lookup_type) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, lookup_type);
switch (lookup_type) {
case Single: return TRACE_RETURN (u.single.dispatch (c));
case Multiple: return TRACE_RETURN (u.multiple.dispatch (c));
@ -1275,8 +1275,8 @@ struct SubstLookup : Lookup
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
unsigned int lookup_type = get_type ();
TRACE_DISPATCH (this, lookup_type);
unsigned int count = get_subtable_count ();
for (unsigned int i = 0; i < count; i++) {
typename context_t::return_t r = get_subtable (i).dispatch (c, lookup_type);

View File

@ -38,10 +38,10 @@ namespace OT {
#define TRACE_DISPATCH(this) \
#define TRACE_DISPATCH(this, format) \
hb_auto_trace_t<context_t::max_debug_depth, typename context_t::return_t> trace \
(&c->debug_depth, c->get_name (), this, HB_FUNC, \
"");
"format %d", (int) format);
#ifndef HB_DEBUG_CLOSURE
#define HB_DEBUG_CLOSURE (HB_DEBUG+0)
@ -168,6 +168,10 @@ struct hb_collect_glyphs_context_t
if (output == hb_set_get_empty ())
return HB_VOID;
/* Return if new lookup was recursed to before. */
if (recursed_lookups.has (lookup_index))
return HB_VOID;
hb_set_t *old_before = before;
hb_set_t *old_input = input;
hb_set_t *old_after = after;
@ -181,6 +185,8 @@ struct hb_collect_glyphs_context_t
input = old_input;
after = old_after;
recursed_lookups.add (lookup_index);
return HB_VOID;
}
@ -190,6 +196,7 @@ struct hb_collect_glyphs_context_t
hb_set_t *after;
hb_set_t *output;
recurse_func_t recurse_func;
hb_set_t recursed_lookups;
unsigned int nesting_level_left;
unsigned int debug_depth;
@ -205,18 +212,30 @@ struct hb_collect_glyphs_context_t
after (glyphs_after ? glyphs_after : hb_set_get_empty ()),
output (glyphs_output ? glyphs_output : hb_set_get_empty ()),
recurse_func (NULL),
recursed_lookups (),
nesting_level_left (nesting_level_left_),
debug_depth (0) {}
debug_depth (0)
{
recursed_lookups.init ();
}
~hb_collect_glyphs_context_t (void)
{
recursed_lookups.fini ();
}
void set_recurse_func (recurse_func_t func) { recurse_func = func; }
};
#ifndef HB_DEBUG_GET_COVERAGE
#define HB_DEBUG_GET_COVERAGE (HB_DEBUG+0)
#endif
struct hb_get_coverage_context_t
{
inline const char *get_name (void) { return "GET_COVERAGE"; }
static const unsigned int max_debug_depth = 0;
static const unsigned int max_debug_depth = HB_DEBUG_GET_COVERAGE;
typedef const Coverage &return_t;
template <typename T>
inline return_t dispatch (const T &obj) { return obj.get_coverage (); }
@ -1117,9 +1136,9 @@ struct Rule
inline void closure (hb_closure_context_t *c, ContextClosureLookupContext &lookup_context) const
{
TRACE_CLOSURE (this);
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (input, input[0].static_size * (inputCount ? inputCount - 1 : 0));
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (inputZ, inputZ[0].static_size * (inputCount ? inputCount - 1 : 0));
context_closure_lookup (c,
inputCount, input,
inputCount, inputZ,
lookupCount, lookupRecord,
lookup_context);
}
@ -1127,9 +1146,9 @@ struct Rule
inline void collect_glyphs (hb_collect_glyphs_context_t *c, ContextCollectGlyphsLookupContext &lookup_context) const
{
TRACE_COLLECT_GLYPHS (this);
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (input, input[0].static_size * (inputCount ? inputCount - 1 : 0));
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (inputZ, inputZ[0].static_size * (inputCount ? inputCount - 1 : 0));
context_collect_glyphs_lookup (c,
inputCount, input,
inputCount, inputZ,
lookupCount, lookupRecord,
lookup_context);
}
@ -1137,15 +1156,15 @@ struct Rule
inline bool would_apply (hb_would_apply_context_t *c, ContextApplyLookupContext &lookup_context) const
{
TRACE_WOULD_APPLY (this);
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (input, input[0].static_size * (inputCount ? inputCount - 1 : 0));
return TRACE_RETURN (context_would_apply_lookup (c, inputCount, input, lookupCount, lookupRecord, lookup_context));
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (inputZ, inputZ[0].static_size * (inputCount ? inputCount - 1 : 0));
return TRACE_RETURN (context_would_apply_lookup (c, inputCount, inputZ, lookupCount, lookupRecord, lookup_context));
}
inline bool apply (hb_apply_context_t *c, ContextApplyLookupContext &lookup_context) const
{
TRACE_APPLY (this);
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (input, input[0].static_size * (inputCount ? inputCount - 1 : 0));
return TRACE_RETURN (context_apply_lookup (c, inputCount, input, lookupCount, lookupRecord, lookup_context));
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (inputZ, inputZ[0].static_size * (inputCount ? inputCount - 1 : 0));
return TRACE_RETURN (context_apply_lookup (c, inputCount, inputZ, lookupCount, lookupRecord, lookup_context));
}
public:
@ -1153,8 +1172,8 @@ struct Rule
TRACE_SANITIZE (this);
return inputCount.sanitize (c)
&& lookupCount.sanitize (c)
&& c->check_range (input,
input[0].static_size * inputCount
&& c->check_range (inputZ,
inputZ[0].static_size * inputCount
+ lookupRecordX[0].static_size * lookupCount);
}
@ -1163,12 +1182,12 @@ struct Rule
* glyph sequence--includes the first
* glyph */
USHORT lookupCount; /* Number of LookupRecords */
USHORT input[VAR]; /* Array of match inputs--start with
USHORT inputZ[VAR]; /* Array of match inputs--start with
* second glyph */
LookupRecord lookupRecordX[VAR]; /* Array of LookupRecords--in
* design order */
public:
DEFINE_SIZE_ARRAY2 (4, input, lookupRecordX);
DEFINE_SIZE_ARRAY2 (4, inputZ, lookupRecordX);
};
struct RuleSet
@ -1413,16 +1432,16 @@ struct ContextFormat3
inline void closure (hb_closure_context_t *c) const
{
TRACE_CLOSURE (this);
if (!(this+coverage[0]).intersects (c->glyphs))
if (!(this+coverageZ[0]).intersects (c->glyphs))
return;
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (coverage, coverage[0].static_size * glyphCount);
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (coverageZ, coverageZ[0].static_size * glyphCount);
struct ContextClosureLookupContext lookup_context = {
{intersects_coverage},
this
};
context_closure_lookup (c,
glyphCount, (const USHORT *) (coverage + 1),
glyphCount, (const USHORT *) (coverageZ + 1),
lookupCount, lookupRecord,
lookup_context);
}
@ -1430,16 +1449,16 @@ struct ContextFormat3
inline void collect_glyphs (hb_collect_glyphs_context_t *c) const
{
TRACE_COLLECT_GLYPHS (this);
(this+coverage[0]).add_coverage (c->input);
(this+coverageZ[0]).add_coverage (c->input);
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (coverage, coverage[0].static_size * glyphCount);
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (coverageZ, coverageZ[0].static_size * glyphCount);
struct ContextCollectGlyphsLookupContext lookup_context = {
{collect_coverage},
this
};
context_collect_glyphs_lookup (c,
glyphCount, (const USHORT *) (coverage + 1),
glyphCount, (const USHORT *) (coverageZ + 1),
lookupCount, lookupRecord,
lookup_context);
}
@ -1448,41 +1467,42 @@ struct ContextFormat3
{
TRACE_WOULD_APPLY (this);
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (coverage, coverage[0].static_size * glyphCount);
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (coverageZ, coverageZ[0].static_size * glyphCount);
struct ContextApplyLookupContext lookup_context = {
{match_coverage},
this
};
return TRACE_RETURN (context_would_apply_lookup (c, glyphCount, (const USHORT *) (coverage + 1), lookupCount, lookupRecord, lookup_context));
return TRACE_RETURN (context_would_apply_lookup (c, glyphCount, (const USHORT *) (coverageZ + 1), lookupCount, lookupRecord, lookup_context));
}
inline const Coverage &get_coverage (void) const
{
return this+coverage[0];
return this+coverageZ[0];
}
inline bool apply (hb_apply_context_t *c) const
{
TRACE_APPLY (this);
unsigned int index = (this+coverage[0]).get_coverage (c->buffer->cur().codepoint);
unsigned int index = (this+coverageZ[0]).get_coverage (c->buffer->cur().codepoint);
if (likely (index == NOT_COVERED)) return TRACE_RETURN (false);
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (coverage, coverage[0].static_size * glyphCount);
const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (coverageZ, coverageZ[0].static_size * glyphCount);
struct ContextApplyLookupContext lookup_context = {
{match_coverage},
this
};
return TRACE_RETURN (context_apply_lookup (c, glyphCount, (const USHORT *) (coverage + 1), lookupCount, lookupRecord, lookup_context));
return TRACE_RETURN (context_apply_lookup (c, glyphCount, (const USHORT *) (coverageZ + 1), lookupCount, lookupRecord, lookup_context));
}
inline bool sanitize (hb_sanitize_context_t *c) {
TRACE_SANITIZE (this);
if (!c->check_struct (this)) return TRACE_RETURN (false);
unsigned int count = glyphCount;
if (!c->check_array (coverage, coverage[0].static_size, count)) return TRACE_RETURN (false);
if (!count) return TRACE_RETURN (false); /* We want to access coverageZ[0] freely. */
if (!c->check_array (coverageZ, coverageZ[0].static_size, count)) return TRACE_RETURN (false);
for (unsigned int i = 0; i < count; i++)
if (!coverage[i].sanitize (c, this)) return TRACE_RETURN (false);
LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (coverage, coverage[0].static_size * count);
if (!coverageZ[i].sanitize (c, this)) return TRACE_RETURN (false);
LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (coverageZ, coverageZ[0].static_size * count);
return TRACE_RETURN (c->check_array (lookupRecord, lookupRecord[0].static_size, lookupCount));
}
@ -1492,12 +1512,12 @@ struct ContextFormat3
* sequence */
USHORT lookupCount; /* Number of LookupRecords */
OffsetTo<Coverage>
coverage[VAR]; /* Array of offsets to Coverage
coverageZ[VAR]; /* Array of offsets to Coverage
* table in glyph sequence order */
LookupRecord lookupRecordX[VAR]; /* Array of LookupRecords--in
* design order */
public:
DEFINE_SIZE_ARRAY2 (6, coverage, lookupRecordX);
DEFINE_SIZE_ARRAY2 (6, coverageZ, lookupRecordX);
};
struct Context
@ -1505,7 +1525,7 @@ struct Context
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
case 2: return TRACE_RETURN (c->dispatch (u.format2));
@ -2090,6 +2110,7 @@ struct ChainContextFormat3
if (!backtrack.sanitize (c, this)) return TRACE_RETURN (false);
OffsetArrayOf<Coverage> &input = StructAfter<OffsetArrayOf<Coverage> > (backtrack);
if (!input.sanitize (c, this)) return TRACE_RETURN (false);
if (!input.len) return TRACE_RETURN (false); /* To be consistent with Context. */
OffsetArrayOf<Coverage> &lookahead = StructAfter<OffsetArrayOf<Coverage> > (input);
if (!lookahead.sanitize (c, this)) return TRACE_RETURN (false);
ArrayOf<LookupRecord> &lookup = StructAfter<ArrayOf<LookupRecord> > (lookahead);
@ -2122,7 +2143,7 @@ struct ChainContext
template <typename context_t>
inline typename context_t::return_t dispatch (context_t *c) const
{
TRACE_DISPATCH (this);
TRACE_DISPATCH (this, u.format);
switch (u.format) {
case 1: return TRACE_RETURN (c->dispatch (u.format1));
case 2: return TRACE_RETURN (c->dispatch (u.format2));

View File

@ -207,7 +207,7 @@ struct arabic_fallback_plan_t
static const arabic_fallback_plan_t arabic_fallback_plan_nil = {};
#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(HB_WITH_WIN1256)
#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(HB_NO_WIN1256)
#define HB_WITH_WIN1256
#endif

View File

@ -313,8 +313,11 @@ OT_TABLE_END
/*
* Include a second time to get the table data...
*/
#if 0
#include "hb-private.hh" /* Make check-includes.sh happy. */
#endif
#ifdef OT_MEASURE
#include __FILE__
#include "hb-ot-shape-complex-arabic-win1256.hh"
#endif
#define HB_OT_SHAPE_COMPLEX_ARABIC_WIN1256_HH

View File

@ -32,7 +32,7 @@
#include "hb-private.hh"
#line 36 "../../src/hb-ot-shape-complex-indic-machine.hh.tmp"
#line 36 "hb-ot-shape-complex-indic-machine.hh.tmp"
static const unsigned char _indic_syllable_machine_trans_keys[] = {
1u, 16u, 13u, 13u, 5u, 7u, 5u, 7u, 7u, 7u, 5u, 7u, 5u, 7u, 7u, 7u,
5u, 7u, 5u, 7u, 7u, 7u, 5u, 7u, 5u, 7u, 7u, 7u, 4u, 4u, 6u, 6u,
@ -1550,7 +1550,7 @@ find_syllables (hb_buffer_t *buffer)
int cs;
hb_glyph_info_t *info = buffer->info;
#line 1554 "../../src/hb-ot-shape-complex-indic-machine.hh.tmp"
#line 1554 "hb-ot-shape-complex-indic-machine.hh.tmp"
{
cs = indic_syllable_machine_start;
ts = 0;
@ -1567,7 +1567,7 @@ find_syllables (hb_buffer_t *buffer)
unsigned int last = 0;
unsigned int syllable_serial = 1;
#line 1571 "../../src/hb-ot-shape-complex-indic-machine.hh.tmp"
#line 1571 "hb-ot-shape-complex-indic-machine.hh.tmp"
{
int _slen;
int _trans;
@ -1581,7 +1581,7 @@ _resume:
#line 1 "NONE"
{ts = p;}
break;
#line 1585 "../../src/hb-ot-shape-complex-indic-machine.hh.tmp"
#line 1585 "hb-ot-shape-complex-indic-machine.hh.tmp"
}
_keys = _indic_syllable_machine_trans_keys + (cs<<1);
@ -1700,7 +1700,7 @@ _eof_trans:
#line 93 "../../src/hb-ot-shape-complex-indic-machine.rl"
{act = 6;}
break;
#line 1704 "../../src/hb-ot-shape-complex-indic-machine.hh.tmp"
#line 1704 "hb-ot-shape-complex-indic-machine.hh.tmp"
}
_again:
@ -1709,7 +1709,7 @@ _again:
#line 1 "NONE"
{ts = 0;}
break;
#line 1713 "../../src/hb-ot-shape-complex-indic-machine.hh.tmp"
#line 1713 "hb-ot-shape-complex-indic-machine.hh.tmp"
}
if ( ++p != pe )

View File

@ -69,7 +69,7 @@ cn = c.ZWJ?.n?;
forced_rakar = ZWJ H ZWJ Ra;
symbol = Symbol.N?;
matra_group = z{0,3}.M.N?.(H | forced_rakar)?;
syllable_tail = (SM.SM?.ZWNJ?)? A{0,3}? VD{0,2};
syllable_tail = (z?.SM.SM?.ZWNJ?)? A{0,3}? VD{0,2};
place_holder = PLACEHOLDER | DOTTEDCIRCLE;
halant_group = (z?.h.(ZWJ.N?)?);
final_halant_group = halant_group | h.ZWNJ;

View File

@ -96,6 +96,8 @@
#if (defined(__WIN32__) && !defined(__WINE__)) || defined(_MSC_VER)
#define snprintf _snprintf
/* Windows CE only has _strdup, while rest of Windows has both. */
#define strdup _strdup
#endif
#ifdef _MSC_VER
@ -126,10 +128,47 @@
# ifndef _WIN32_WINNT
# define _WIN32_WINNT 0x0600
# endif
# define WIN32_LEAN_AND_MEAN
# define STRICT
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN 1
# endif
# ifndef STRICT
# define STRICT 1
# endif
#endif
#ifdef _WIN32_WCE
/* Some things not defined on Windows CE. */
#define MemoryBarrier()
#define getenv(Name) NULL
#define setlocale(Category, Locale) "C"
static int errno = 0; /* Use something better? */
#endif
#if HAVE_ATEXIT
/* atexit() is only safe to be called from shared libraries on certain
* platforms. Whitelist.
* https://bugs.freedesktop.org/show_bug.cgi?id=82246 */
# if defined(__linux) && defined(__GLIBC_PREREQ)
# if __GLIBC_PREREQ(2,3)
/* From atexit() manpage, it's safe with glibc 2.2.3 on Linux. */
# define HB_USE_ATEXIT 1
# endif
# elif defined(_MSC_VER) || defined(__MINGW32__)
/* For MSVC:
* http://msdn.microsoft.com/en-ca/library/tze57ck3.aspx
* http://msdn.microsoft.com/en-ca/library/zk17ww08.aspx
* mingw32 headers say atexit is safe to use in shared libraries.
*/
# define HB_USE_ATEXIT 1
# elif defined(__ANDROID__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
/* This was fixed in Android NKD r8 or r8b:
* https://code.google.com/p/android/issues/detail?id=6455
* which introduced GCC 4.6:
* https://developer.android.com/tools/sdk/ndk/index.html
*/
# define HB_USE_ATEXIT 1
# endif
#endif
/* Basics */
@ -500,47 +539,6 @@ struct hb_lockable_set_t
};
/* Big-endian handling */
static inline uint16_t hb_be_uint16 (const uint16_t v)
{
const uint8_t *V = (const uint8_t *) &v;
return (V[0] << 8) | V[1];
}
static inline uint16_t hb_uint16_swap (const uint16_t v)
{
return (v >> 8) | (v << 8);
}
static inline uint32_t hb_uint32_swap (const uint32_t v)
{
return (hb_uint16_swap (v) << 16) | hb_uint16_swap (v >> 16);
}
/* Note, of the following macros, uint16_get is the one called many many times.
* If there is any optimizations to be done, it's in that macro. However, I
* already confirmed that on my T400 ThinkPad at least, using bswap_16(), which
* results in a single ror instruction, does NOT speed this up. In fact, it
* resulted in a minor slowdown. At any rate, note that v may not be correctly
* aligned, so I think the current implementation is optimal.
*/
#define hb_be_uint16_put(v,V) HB_STMT_START { v[0] = (V>>8); v[1] = (V); } HB_STMT_END
#define hb_be_uint16_get(v) (uint16_t) ((v[0] << 8) + v[1])
#define hb_be_uint16_eq(a,b) (a[0] == b[0] && a[1] == b[1])
#define hb_be_uint32_put(v,V) HB_STMT_START { v[0] = (V>>24); v[1] = (V>>16); v[2] = (V>>8); v[3] = (V); } HB_STMT_END
#define hb_be_uint32_get(v) (uint32_t) ((v[0] << 24) + (v[1] << 16) + (v[2] << 8) + v[3])
#define hb_be_uint32_eq(a,b) (a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3])
#define hb_be_uint24_put(v,V) HB_STMT_START { v[0] = (V>>16); v[1] = (V>>8); v[2] = (V); } HB_STMT_END
#define hb_be_uint24_get(v) (uint32_t) ((v[0] << 16) + (v[1] << 8) + v[2])
#define hb_be_uint24_eq(a,b) (a[0] == b[0] && a[1] == b[1] && a[2] == b[2])
/* ASCII tag/character handling */
static inline bool ISALPHA (unsigned char c)
@ -585,6 +583,15 @@ _hb_debug (unsigned int level,
#define DEBUG_LEVEL_ENABLED(WHAT, LEVEL) (_hb_debug ((LEVEL), HB_DEBUG_##WHAT))
#define DEBUG_ENABLED(WHAT) (DEBUG_LEVEL_ENABLED (WHAT, 0))
template <int max_level> static inline void
_hb_debug_msg_va (const char *what,
const void *obj,
const char *func,
bool indented,
unsigned int level,
int level_dir,
const char *message,
va_list ap) HB_PRINTF_FUNC(7, 0);
template <int max_level> static inline void
_hb_debug_msg_va (const char *what,
const void *obj,
@ -708,7 +715,9 @@ _hb_debug_msg<0> (const char *what HB_UNUSED,
*/
template <typename T>
struct hb_printer_t {};
struct hb_printer_t {
const char *print (const T&) { return "something"; }
};
template <>
struct hb_printer_t<bool> {
@ -815,7 +824,9 @@ hb_in_range (T u, T lo, T hi)
* to generate a warning than unused variables. */
ASSERT_STATIC (sizeof (hb_assert_unsigned_t<T>) >= 0);
return (u - lo) <= (hi - lo);
/* The casts below are important as if T is smaller than int,
* the subtract results will become a signed int! */
return (T)(u - lo) <= (T)(hi - lo);
}
template <typename T> static inline bool
@ -839,7 +850,7 @@ hb_in_ranges (T u, T lo1, T hi1, T lo2, T hi2, T lo3, T hi3)
#define FLAG_RANGE(x,y) (ASSERT_STATIC_EXPR_ZERO ((x) < (y)) + FLAG(y+1) - FLAG(x))
template <typename T, typename T2> inline void
template <typename T, typename T2> static inline void
hb_bubble_sort (T *array, unsigned int len, int(*compar)(const T *, const T *), T2 *array2)
{
if (unlikely (!len))
@ -872,7 +883,7 @@ hb_bubble_sort (T *array, unsigned int len, int(*compar)(const T *, const T *),
} while (k);
}
template <typename T> inline void
template <typename T> static inline void
hb_bubble_sort (T *array, unsigned int len, int(*compar)(const T *, const T *))
{
hb_bubble_sort (array, len, compar, (int *) NULL);
@ -901,12 +912,12 @@ hb_codepoint_parse (const char *s, unsigned int len, int base, hb_codepoint_t *o
struct hb_options_t
{
int initialized : 1;
int uniscribe_bug_compatible : 1;
unsigned int initialized : 1;
unsigned int uniscribe_bug_compatible : 1;
};
union hb_options_union_t {
int i;
unsigned int i;
hb_options_t opts;
};
ASSERT_STATIC (sizeof (int) == sizeof (hb_options_union_t));

View File

@ -150,7 +150,7 @@ struct hb_set_t
bool in_error;
inline void init (void) {
header.init ();
hb_object_init (this);
clear ();
}
inline void fini (void) {

View File

@ -29,6 +29,12 @@
#include "hb-font-private.hh"
#include "hb-buffer-private.hh"
#ifndef HB_DEBUG_SHAPE_PLAN
#define HB_DEBUG_SHAPE_PLAN (HB_DEBUG+0)
#endif
#define HB_SHAPER_IMPLEMENT(shaper) \
HB_SHAPER_DATA_ENSURE_DECLARE(shaper, face) \
HB_SHAPER_DATA_ENSURE_DECLARE(shaper, font)
@ -42,6 +48,11 @@ hb_shape_plan_plan (hb_shape_plan_t *shape_plan,
unsigned int num_user_features,
const char * const *shaper_list)
{
DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan,
"num_features=%d shaper_list=%p",
num_user_features,
shaper_list);
const hb_shaper_pair_t *shapers = _hb_shapers_get ();
#define HB_SHAPER_PLAN(shaper) \
@ -104,6 +115,12 @@ hb_shape_plan_create (hb_face_t *face,
unsigned int num_user_features,
const char * const *shaper_list)
{
DEBUG_MSG_FUNC (SHAPE_PLAN, NULL,
"face=%p num_features=%d shaper_list=%p",
face,
num_user_features,
shaper_list);
hb_shape_plan_t *shape_plan;
hb_feature_t *features = NULL;
@ -271,6 +288,11 @@ hb_shape_plan_execute (hb_shape_plan_t *shape_plan,
const hb_feature_t *features,
unsigned int num_features)
{
DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan,
"num_features=%d shaper_func=%p",
num_features,
shape_plan->shaper_func);
if (unlikely (hb_object_is_inert (shape_plan) ||
hb_object_is_inert (font) ||
hb_object_is_inert (buffer)))
@ -383,6 +405,12 @@ hb_shape_plan_create_cached (hb_face_t *face,
unsigned int num_user_features,
const char * const *shaper_list)
{
DEBUG_MSG_FUNC (SHAPE_PLAN, NULL,
"face=%p num_features=%d shaper_list=%p",
face,
num_user_features,
shaper_list);
hb_shape_plan_proposal_t proposal = {
*props,
shaper_list,
@ -392,25 +420,22 @@ hb_shape_plan_create_cached (hb_face_t *face,
};
if (shaper_list) {
/* Choose shaper. Adapted from hb_shape_plan_plan(). */
#define HB_SHAPER_PLAN(shaper) \
HB_STMT_START { \
if (hb_##shaper##_shaper_face_data_ensure (face)) \
proposal.shaper_func = _hb_##shaper##_shape; \
} HB_STMT_END
/* Choose shaper. Adapted from hb_shape_plan_plan().
* Must choose shaper exactly the same way as that function. */
for (const char * const *shaper_item = shaper_list; *shaper_item; shaper_item++)
if (0)
;
#define HB_SHAPER_IMPLEMENT(shaper) \
else if (0 == strcmp (*shaper_item, #shaper)) \
HB_SHAPER_PLAN (shaper);
else if (0 == strcmp (*shaper_item, #shaper) && \
hb_##shaper##_shaper_face_data_ensure (face)) \
{ \
proposal.shaper_func = _hb_##shaper##_shape; \
break; \
}
#include "hb-shaper-list.hh"
#undef HB_SHAPER_IMPLEMENT
#undef HB_SHAPER_PLAN
if (unlikely (!proposal.shaper_list))
if (unlikely (!proposal.shaper_func))
return hb_shape_plan_get_empty ();
}
@ -419,7 +444,10 @@ retry:
hb_face_t::plan_node_t *cached_plan_nodes = (hb_face_t::plan_node_t *) hb_atomic_ptr_get (&face->shape_plans);
for (hb_face_t::plan_node_t *node = cached_plan_nodes; node; node = node->next)
if (hb_shape_plan_matches (node->shape_plan, &proposal))
{
DEBUG_MSG_FUNC (SHAPE_PLAN, node->shape_plan, "fulfilled from cache");
return hb_shape_plan_reference (node->shape_plan);
}
/* Not found. */
@ -442,6 +470,7 @@ retry:
free (node);
goto retry;
}
DEBUG_MSG_FUNC (SHAPE_PLAN, shape_plan, "inserted into cache");
return hb_shape_plan_reference (shape_plan);
}

View File

@ -320,7 +320,7 @@ retry:
goto retry;
}
#ifdef HAVE_ATEXIT
#ifdef HB_USE_ATEXIT
atexit (free_static_shaper_list); /* First person registers atexit() callback. */
#endif
}

View File

@ -100,7 +100,7 @@ retry:
goto retry;
}
#ifdef HAVE_ATEXIT
#ifdef HB_USE_ATEXIT
atexit (free_static_shapers); /* First person registers atexit() callback. */
#endif
}

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