mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
merge mozilla-inbound to mozilla-central. r=merge a=merge
MozReview-Commit-ID: F5aZ4FC75r7
This commit is contained in:
commit
0e1faab582
1
AUTHORS
1
AUTHORS
@ -797,6 +797,7 @@ Pete Collins <petejc@collab.net>
|
||||
Peter Annema <disttsc@bart.nl>
|
||||
Peter Bajusz <hyp-x@inf.bme.hu>
|
||||
Peter Lubczynski <peterl@netscape.com>
|
||||
Peter Moore <petemoore@gmx.net>
|
||||
Peter Naulls
|
||||
Peter Parente <parente@cs.unc.edu>
|
||||
Peter Seliger
|
||||
|
@ -4,7 +4,7 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
if CONFIG['COMPILE_ENVIRONMENT']:
|
||||
if CONFIG['COMPILE_ENVIRONMENT'] and CONFIG['ACCESSIBILITY']:
|
||||
DIRS += [
|
||||
'handler',
|
||||
'typelib',
|
||||
|
@ -24,12 +24,8 @@ const MAX_EXPIRY = Math.pow(2, 62);
|
||||
* The external API implemented by the SessionCookies module.
|
||||
*/
|
||||
this.SessionCookies = Object.freeze({
|
||||
update(windows) {
|
||||
SessionCookiesInternal.update(windows);
|
||||
},
|
||||
|
||||
getHostsForWindow(window) {
|
||||
return SessionCookiesInternal.getHostsForWindow(window);
|
||||
collect() {
|
||||
return SessionCookiesInternal.collect();
|
||||
},
|
||||
|
||||
restore(cookies) {
|
||||
@ -47,69 +43,17 @@ var SessionCookiesInternal = {
|
||||
_initialized: false,
|
||||
|
||||
/**
|
||||
* Retrieve the list of all hosts contained in the given windows' session
|
||||
* history entries (per window) and collect the associated cookies for those
|
||||
* hosts, if any. The given state object is being modified.
|
||||
*
|
||||
* @param windows
|
||||
* Array of window state objects.
|
||||
* [{ tabs: [...], cookies: [...] }, ...]
|
||||
* Retrieve an array of all stored session cookies.
|
||||
*/
|
||||
update(windows) {
|
||||
collect() {
|
||||
this._ensureInitialized();
|
||||
|
||||
// Check whether we're allowed to store cookies.
|
||||
let storeAnyCookies = PrivacyLevel.canSave(false);
|
||||
let storeSecureCookies = PrivacyLevel.canSave(true);
|
||||
|
||||
for (let window of windows) {
|
||||
let cookies = [];
|
||||
|
||||
if (storeAnyCookies) {
|
||||
// Collect all cookies for the current window.
|
||||
for (let host of this.getHostsForWindow(window)) {
|
||||
for (let cookie of CookieStore.getCookiesForHost(host)) {
|
||||
if (!cookie.secure || storeSecureCookies) {
|
||||
cookies.push(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't include/keep empty cookie sections.
|
||||
if (cookies.length) {
|
||||
window.cookies = cookies;
|
||||
} else if ("cookies" in window) {
|
||||
delete window.cookies;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a map of all hosts for a given window that we might want to
|
||||
* collect cookies for.
|
||||
*
|
||||
* @param window
|
||||
* A window state object containing tabs with history entries.
|
||||
* @return {set} A set of hosts for a given window state object.
|
||||
*/
|
||||
getHostsForWindow(window) {
|
||||
let hosts = new Set();
|
||||
|
||||
for (let tab of window.tabs) {
|
||||
for (let entry of tab.entries) {
|
||||
this._extractHostsFromEntry(entry, hosts);
|
||||
}
|
||||
}
|
||||
|
||||
return hosts;
|
||||
return CookieStore.toArray();
|
||||
},
|
||||
|
||||
/**
|
||||
* Restores a given list of session cookies.
|
||||
*/
|
||||
restore(cookies) {
|
||||
|
||||
for (let cookie of cookies) {
|
||||
let expiry = "expiry" in cookie ? cookie.expiry : MAX_EXPIRY;
|
||||
let cookieObj = {
|
||||
@ -117,10 +61,12 @@ var SessionCookiesInternal = {
|
||||
path: cookie.path || "",
|
||||
name: cookie.name || ""
|
||||
};
|
||||
if (!Services.cookies.cookieExists(cookieObj, cookie.originAttributes || {})) {
|
||||
|
||||
let originAttributes = cookie.originAttributes || {};
|
||||
if (!Services.cookies.cookieExists(cookieObj, originAttributes)) {
|
||||
Services.cookies.add(cookie.host, cookie.path || "", cookie.name || "",
|
||||
cookie.value, !!cookie.secure, !!cookie.httponly,
|
||||
/* isSession = */ true, expiry, cookie.originAttributes || {});
|
||||
/* isSession = */ true, expiry, originAttributes);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -146,10 +92,6 @@ var SessionCookiesInternal = {
|
||||
case "batch-deleted":
|
||||
this._removeCookies(subject);
|
||||
break;
|
||||
case "reload":
|
||||
CookieStore.clear();
|
||||
this._reloadCookies();
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unhandled cookie-changed notification.");
|
||||
}
|
||||
@ -164,31 +106,11 @@ var SessionCookiesInternal = {
|
||||
this._reloadCookies();
|
||||
this._initialized = true;
|
||||
Services.obs.addObserver(this, "cookie-changed", false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fill a given map with hosts found in the given entry's session history and
|
||||
* any child entries.
|
||||
*
|
||||
* @param entry
|
||||
* the history entry, serialized
|
||||
* @param hosts
|
||||
* the set that will be used to store hosts
|
||||
*/
|
||||
_extractHostsFromEntry(entry, hosts) {
|
||||
try {
|
||||
// It's alright if this throws for about: URIs.
|
||||
let {host, scheme} = Utils.makeURI(entry.url);
|
||||
if (/^(file|https?)$/.test(scheme)) {
|
||||
hosts.add(host);
|
||||
}
|
||||
} catch (ex) { }
|
||||
|
||||
if (entry.children) {
|
||||
for (let child of entry.children) {
|
||||
this._extractHostsFromEntry(child, hosts);
|
||||
}
|
||||
// Listen for privacy level changes to reload cookies when needed.
|
||||
Services.prefs.addObserver("browser.sessionstore.privacy_level", () => {
|
||||
this._reloadCookies();
|
||||
}, false);
|
||||
}
|
||||
},
|
||||
|
||||
@ -198,8 +120,9 @@ var SessionCookiesInternal = {
|
||||
_addCookie(cookie) {
|
||||
cookie.QueryInterface(Ci.nsICookie2);
|
||||
|
||||
if (cookie.isSession) {
|
||||
CookieStore.set(cookie);
|
||||
// Store only session cookies, obey the privacy level.
|
||||
if (cookie.isSession && PrivacyLevel.canSave(cookie.isSecure)) {
|
||||
CookieStore.add(cookie);
|
||||
}
|
||||
},
|
||||
|
||||
@ -209,8 +132,9 @@ var SessionCookiesInternal = {
|
||||
_updateCookie(cookie) {
|
||||
cookie.QueryInterface(Ci.nsICookie2);
|
||||
|
||||
if (cookie.isSession) {
|
||||
CookieStore.set(cookie);
|
||||
// Store only session cookies, obey the privacy level.
|
||||
if (cookie.isSession && PrivacyLevel.canSave(cookie.isSecure)) {
|
||||
CookieStore.add(cookie);
|
||||
} else {
|
||||
CookieStore.delete(cookie);
|
||||
}
|
||||
@ -238,9 +162,16 @@ var SessionCookiesInternal = {
|
||||
|
||||
/**
|
||||
* Iterates all cookies in the cookies service and puts them into the store
|
||||
* if they're session cookies.
|
||||
* if they're session cookies. Obeys the user's chosen privacy level.
|
||||
*/
|
||||
_reloadCookies() {
|
||||
CookieStore.clear();
|
||||
|
||||
// Bail out if we're not supposed to store cookies at all.
|
||||
if (!PrivacyLevel.canSave(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let iter = Services.cookies.enumerator;
|
||||
while (iter.hasMoreElements()) {
|
||||
this._addCookie(iter.getNext());
|
||||
@ -249,108 +180,13 @@ var SessionCookiesInternal = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates all possible subdomains for a given host and prepends a leading
|
||||
* dot to all variants.
|
||||
*
|
||||
* See http://tools.ietf.org/html/rfc6265#section-5.1.3
|
||||
* http://en.wikipedia.org/wiki/HTTP_cookie#Domain_and_Path
|
||||
*
|
||||
* All cookies belonging to a web page will be internally represented by a
|
||||
* nsICookie object. nsICookie.host will be the request host if no domain
|
||||
* parameter was given when setting the cookie. If a specific domain was given
|
||||
* then nsICookie.host will contain that specific domain and prepend a leading
|
||||
* dot to it.
|
||||
*
|
||||
* We thus generate all possible subdomains for a given domain and prepend a
|
||||
* leading dot to them as that is the value that was used as the map key when
|
||||
* the cookie was set.
|
||||
*/
|
||||
function* getPossibleSubdomainVariants(host) {
|
||||
// Try given domain with a leading dot (.www.example.com).
|
||||
yield "." + host;
|
||||
|
||||
// Stop if there are only two parts left (e.g. example.com was given).
|
||||
let parts = host.split(".");
|
||||
if (parts.length < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the first subdomain (www.example.com -> example.com).
|
||||
let rest = parts.slice(1).join(".");
|
||||
|
||||
// Try possible parent subdomains.
|
||||
yield* getPossibleSubdomainVariants(rest);
|
||||
}
|
||||
|
||||
/**
|
||||
* The internal cookie storage that keeps track of every active session cookie.
|
||||
* These are stored using maps per host, path, and cookie name.
|
||||
* The internal storage that keeps track of session cookies.
|
||||
*/
|
||||
var CookieStore = {
|
||||
/**
|
||||
* The internal structure holding all known cookies.
|
||||
*
|
||||
* Host =>
|
||||
* Path =>
|
||||
* Name => {path: "/", name: "sessionid", secure: true}
|
||||
*
|
||||
* Maps are used for storage but the data structure is equivalent to this:
|
||||
*
|
||||
* this._hosts = {
|
||||
* "www.mozilla.org": {
|
||||
* "/": {
|
||||
* "username": {name: "username", value: "my_name_is", etc...},
|
||||
* "sessionid": {name: "sessionid", value: "1fdb3a", etc...}
|
||||
* }
|
||||
* },
|
||||
* "tbpl.mozilla.org": {
|
||||
* "/path": {
|
||||
* "cookiename": {name: "cookiename", value: "value", etc...}
|
||||
* }
|
||||
* },
|
||||
* ".example.com": {
|
||||
* "/path": {
|
||||
* "cookiename": {name: "cookiename", value: "value", etc...}
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
* The internal map holding all known session cookies.
|
||||
*/
|
||||
_hosts: new Map(),
|
||||
|
||||
/**
|
||||
* Returns the list of stored session cookies for a given host.
|
||||
*
|
||||
* @param mainHost
|
||||
* A string containing the host name we want to get cookies for.
|
||||
*/
|
||||
getCookiesForHost(mainHost) {
|
||||
let cookies = [];
|
||||
|
||||
let appendCookiesForHost = host => {
|
||||
if (!this._hosts.has(host)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let pathToNamesMap of this._hosts.get(host).values()) {
|
||||
for (let nameToCookiesMap of pathToNamesMap.values()) {
|
||||
cookies.push(...nameToCookiesMap.values());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to find cookies for the given host, e.g. <www.example.com>.
|
||||
// The full hostname will be in the map if the Set-Cookie header did not
|
||||
// have a domain= attribute, i.e. the cookie will only be stored for the
|
||||
// request domain. Also, try to find cookies for subdomains, e.g.
|
||||
// <.example.com>. We will find those variants with a leading dot in the
|
||||
// map if the Set-Cookie header had a domain= attribute, i.e. the cookie
|
||||
// will be stored for a parent domain and we send it for any subdomain.
|
||||
for (let variant of [mainHost, ...getPossibleSubdomainVariants(mainHost)]) {
|
||||
appendCookiesForHost(variant);
|
||||
}
|
||||
|
||||
return cookies;
|
||||
},
|
||||
_entries: new Map(),
|
||||
|
||||
/**
|
||||
* Stores a given cookie.
|
||||
@ -358,7 +194,7 @@ var CookieStore = {
|
||||
* @param cookie
|
||||
* The nsICookie2 object to add to the storage.
|
||||
*/
|
||||
set(cookie) {
|
||||
add(cookie) {
|
||||
let jscookie = {host: cookie.host, value: cookie.value};
|
||||
|
||||
// Only add properties with non-default values to save a few bytes.
|
||||
@ -386,7 +222,7 @@ var CookieStore = {
|
||||
jscookie.originAttributes = cookie.originAttributes;
|
||||
}
|
||||
|
||||
this._ensureMap(cookie).set(cookie.name, jscookie);
|
||||
this._entries.set(this._getKeyForCookie(cookie), jscookie);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -396,60 +232,38 @@ var CookieStore = {
|
||||
* The nsICookie2 object to be removed from storage.
|
||||
*/
|
||||
delete(cookie) {
|
||||
// Deletion shouldn't create new maps.
|
||||
// Bail out whenever we find that an entry or a map doesn't exist.
|
||||
let map = this._hosts.get(cookie.host);
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
|
||||
map = map.get(ChromeUtils.originAttributesToSuffix(cookie.originAttributes));
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
|
||||
map = map.get(cookie.path);
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
|
||||
map.delete(cookie.name);
|
||||
this._entries.delete(this._getKeyForCookie(cookie));
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all cookies.
|
||||
*/
|
||||
clear() {
|
||||
this._hosts.clear();
|
||||
this._entries.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates all maps necessary to store a given cookie.
|
||||
* Return all cookies as an array.
|
||||
*/
|
||||
toArray() {
|
||||
return [...this._entries.values()];
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the key needed to properly store and identify a given cookie.
|
||||
* A cookie is uniquely identified by the combination of its host, name,
|
||||
* path, and originAttributes properties.
|
||||
*
|
||||
* @param cookie
|
||||
* The nsICookie2 object to create maps for.
|
||||
*
|
||||
* @return The newly created Map instance mapping cookie names to
|
||||
* internal jscookies, in the given path of the given host.
|
||||
* The nsICookie2 object to compute a key for.
|
||||
* @return string
|
||||
*/
|
||||
_ensureMap(cookie) {
|
||||
if (!this._hosts.has(cookie.host)) {
|
||||
this._hosts.set(cookie.host, new Map());
|
||||
}
|
||||
|
||||
let originAttributesMap = this._hosts.get(cookie.host);
|
||||
// If cookie.originAttributes is null, originAttributes will be an empty string.
|
||||
let originAttributes = ChromeUtils.originAttributesToSuffix(cookie.originAttributes);
|
||||
if (!originAttributesMap.has(originAttributes)) {
|
||||
originAttributesMap.set(originAttributes, new Map());
|
||||
}
|
||||
|
||||
let pathToNamesMap = originAttributesMap.get(originAttributes);
|
||||
|
||||
if (!pathToNamesMap.has(cookie.path)) {
|
||||
pathToNamesMap.set(cookie.path, new Map());
|
||||
}
|
||||
|
||||
return pathToNamesMap.get(cookie.path);
|
||||
_getKeyForCookie(cookie) {
|
||||
return JSON.stringify({
|
||||
host: cookie.host,
|
||||
name: cookie.name,
|
||||
path: cookie.path,
|
||||
attr: ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -634,10 +634,15 @@ var SessionStoreInternal = {
|
||||
let [iniState, remainingState] = this._prepDataForDeferredRestore(state);
|
||||
// If we have a iniState with windows, that means that we have windows
|
||||
// with app tabs to restore.
|
||||
if (iniState.windows.length)
|
||||
if (iniState.windows.length) {
|
||||
// Move cookies over from the remaining state so that they're
|
||||
// restored right away, and pinned tabs will load correctly.
|
||||
iniState.cookies = remainingState.cookies;
|
||||
delete remainingState.cookies;
|
||||
state = iniState;
|
||||
else
|
||||
} else {
|
||||
state = null;
|
||||
}
|
||||
|
||||
if (remainingState.windows.length) {
|
||||
LastSession.setState(remainingState);
|
||||
@ -1153,6 +1158,9 @@ var SessionStoreInternal = {
|
||||
// it happens before observers are notified
|
||||
this._globalState.setFromState(aInitialState);
|
||||
|
||||
// Restore session cookies before loading any tabs.
|
||||
SessionCookies.restore(aInitialState.cookies || []);
|
||||
|
||||
let overwrite = this._isCmdLineEmpty(aWindow, aInitialState);
|
||||
let options = {firstWindow: true, overwriteTabs: overwrite};
|
||||
this.restoreWindows(aWindow, aInitialState, options);
|
||||
@ -1381,7 +1389,6 @@ var SessionStoreInternal = {
|
||||
winData.title = tabbrowser.selectedBrowser.contentTitle || tabbrowser.selectedTab.label;
|
||||
winData.title = this._replaceLoadingTitle(winData.title, tabbrowser,
|
||||
tabbrowser.selectedTab);
|
||||
SessionCookies.update([winData]);
|
||||
}
|
||||
|
||||
if (AppConstants.platform != "macosx") {
|
||||
@ -2221,6 +2228,9 @@ var SessionStoreInternal = {
|
||||
// it happens before observers are notified
|
||||
this._globalState.setFromState(state);
|
||||
|
||||
// Restore session cookies.
|
||||
SessionCookies.restore(state.cookies || []);
|
||||
|
||||
// restore to the given state
|
||||
this.restoreWindows(window, state, {overwriteTabs: true});
|
||||
|
||||
@ -2686,6 +2696,9 @@ var SessionStoreInternal = {
|
||||
// it happens before observers are notified
|
||||
this._globalState.setFromState(lastSessionState);
|
||||
|
||||
// Restore session cookies.
|
||||
SessionCookies.restore(lastSessionState.cookies || []);
|
||||
|
||||
// Restore into windows or open new ones as needed.
|
||||
for (let i = 0; i < lastSessionState.windows.length; i++) {
|
||||
let winState = lastSessionState.windows[i];
|
||||
@ -3046,10 +3059,6 @@ var SessionStoreInternal = {
|
||||
nonPopupCount++;
|
||||
}
|
||||
|
||||
TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
|
||||
SessionCookies.update(total);
|
||||
TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
|
||||
|
||||
// collect the data for all windows yet to be restored
|
||||
for (ix in this._statesToRestore) {
|
||||
for (let winData of this._statesToRestore[ix].windows) {
|
||||
@ -3102,6 +3111,11 @@ var SessionStoreInternal = {
|
||||
global: this._globalState.getState()
|
||||
};
|
||||
|
||||
// Collect and store session cookies.
|
||||
TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
|
||||
state.cookies = SessionCookies.collect();
|
||||
TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
|
||||
|
||||
if (Cu.isModuleLoaded("resource://devtools/client/scratchpad/scratchpad-manager.jsm")) {
|
||||
// get open Scratchpad window states too
|
||||
let scratchpads = ScratchpadManager.getSessionState();
|
||||
@ -3139,10 +3153,7 @@ var SessionStoreInternal = {
|
||||
this._collectWindowData(aWindow);
|
||||
}
|
||||
|
||||
let windows = [this._windows[aWindow.__SSi]];
|
||||
SessionCookies.update(windows);
|
||||
|
||||
return { windows };
|
||||
return { windows: [this._windows[aWindow.__SSi]] };
|
||||
},
|
||||
|
||||
/**
|
||||
@ -3386,9 +3397,10 @@ var SessionStoreInternal = {
|
||||
this.restoreWindowFeatures(aWindow, winData);
|
||||
delete this._windows[aWindow.__SSi].extData;
|
||||
}
|
||||
if (winData.cookies) {
|
||||
SessionCookies.restore(winData.cookies);
|
||||
}
|
||||
|
||||
// Restore cookies from legacy sessions, i.e. before bug 912717.
|
||||
SessionCookies.restore(winData.cookies || []);
|
||||
|
||||
if (winData.extData) {
|
||||
if (!this._windows[aWindow.__SSi].extData) {
|
||||
this._windows[aWindow.__SSi].extData = {};
|
||||
@ -4312,8 +4324,7 @@ var SessionStoreInternal = {
|
||||
* (defaultState) will be a state that should still be restored at startup,
|
||||
* while the second part (state) is a state that should be saved for later.
|
||||
* defaultState will be comprised of windows with only pinned tabs, extracted
|
||||
* from state. It will contain the cookies that go along with the history
|
||||
* entries in those tabs. It will also contain window position information.
|
||||
* from state. It will also contain window position information.
|
||||
*
|
||||
* defaultState will be restored at startup. state will be passed into
|
||||
* LastSession and will be kept in case the user explicitly wants
|
||||
@ -4338,7 +4349,7 @@ var SessionStoreInternal = {
|
||||
let window = state.windows[wIndex];
|
||||
window.selected = window.selected || 1;
|
||||
// We're going to put the state of the window into this object
|
||||
let pinnedWindowState = { tabs: [], cookies: []};
|
||||
let pinnedWindowState = { tabs: [] };
|
||||
for (let tIndex = 0; tIndex < window.tabs.length;) {
|
||||
if (window.tabs[tIndex].pinned) {
|
||||
// Adjust window.selected
|
||||
@ -4379,9 +4390,6 @@ var SessionStoreInternal = {
|
||||
window.__lastSessionWindowID = pinnedWindowState.__lastSessionWindowID
|
||||
= "" + Date.now() + Math.random();
|
||||
|
||||
// Extract the cookies that belong with each pinned tab
|
||||
this._splitCookiesFromWindow(window, pinnedWindowState);
|
||||
|
||||
// Actually add this window to our defaultState
|
||||
defaultState.windows.push(pinnedWindowState);
|
||||
// Remove the window from the state if it doesn't have any tabs
|
||||
@ -4404,36 +4412,6 @@ var SessionStoreInternal = {
|
||||
return [defaultState, state];
|
||||
},
|
||||
|
||||
/**
|
||||
* Splits out the cookies from aWinState into aTargetWinState based on the
|
||||
* tabs that are in aTargetWinState.
|
||||
* This alters the state of aWinState and aTargetWinState.
|
||||
*/
|
||||
_splitCookiesFromWindow:
|
||||
function ssi_splitCookiesFromWindow(aWinState, aTargetWinState) {
|
||||
if (!aWinState.cookies || !aWinState.cookies.length)
|
||||
return;
|
||||
|
||||
// Get the hosts for history entries in aTargetWinState
|
||||
let cookieHosts = SessionCookies.getHostsForWindow(aTargetWinState);
|
||||
|
||||
// By creating a regex we reduce overhead and there is only one loop pass
|
||||
// through either array (cookieHosts and aWinState.cookies).
|
||||
let hosts = [...cookieHosts].join("|").replace(/\./g, "\\.");
|
||||
// If we don't actually have any hosts, then we don't want to do anything.
|
||||
if (!hosts.length)
|
||||
return;
|
||||
let cookieRegex = new RegExp(".*(" + hosts + ")");
|
||||
for (let cIndex = 0; cIndex < aWinState.cookies.length;) {
|
||||
if (cookieRegex.test(aWinState.cookies[cIndex].host)) {
|
||||
aTargetWinState.cookies =
|
||||
aTargetWinState.cookies.concat(aWinState.cookies.splice(cIndex, 1));
|
||||
continue;
|
||||
}
|
||||
cIndex++;
|
||||
}
|
||||
},
|
||||
|
||||
_sendRestoreCompletedNotifications: function ssi_sendRestoreCompletedNotifications() {
|
||||
// not all windows restored, yet
|
||||
if (this._restoreCount > 1) {
|
||||
|
@ -12,7 +12,6 @@ support-files =
|
||||
head.js
|
||||
content.js
|
||||
content-forms.js
|
||||
browser_cookies.sjs
|
||||
browser_formdata_sample.html
|
||||
browser_formdata_xpath_sample.html
|
||||
browser_frametree_sample.html
|
||||
@ -80,7 +79,6 @@ skip-if = debug # bug 1211084
|
||||
[browser_broadcast.js]
|
||||
[browser_capabilities.js]
|
||||
[browser_cleaner.js]
|
||||
[browser_cookies.js]
|
||||
[browser_crashedTabs.js]
|
||||
skip-if = !e10s || !crashreporter
|
||||
[browser_unrestored_crashedTabs.js]
|
||||
@ -251,3 +249,7 @@ skip-if = !e10s # GroupedSHistory is e10s-only
|
||||
[browser_duplicate_history.js]
|
||||
[browser_tabicon_after_bg_tab_crash.js]
|
||||
skip-if = !e10s # Tabs can't crash without e10s
|
||||
|
||||
[browser_cookies.js]
|
||||
[browser_cookies_legacy.js]
|
||||
[browser_cookies_privacy.js]
|
||||
|
@ -15,15 +15,12 @@ add_task(function*() {
|
||||
set: [["browser.sessionstore.interval", 0]]
|
||||
});
|
||||
|
||||
let win = yield BrowserTestUtils.openNewBrowserWindow();
|
||||
let browser = win.gBrowser.selectedBrowser;
|
||||
browser.loadURI(testURL);
|
||||
yield BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
yield TabStateFlusher.flush(browser);
|
||||
let tab = gBrowser.addTab(testURL);
|
||||
yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
yield TabStateFlusher.flush(tab.linkedBrowser);
|
||||
|
||||
// get the sessionstore state for the window
|
||||
let state = ss.getWindowState(win);
|
||||
let state = ss.getBrowserState();
|
||||
|
||||
// verify our cookie got set during pageload
|
||||
let enumerator = Services.cookies.enumerator;
|
||||
@ -39,7 +36,7 @@ add_task(function*() {
|
||||
Services.cookies.removeAll();
|
||||
|
||||
// restore the window state
|
||||
ss.setWindowState(win, state, true);
|
||||
ss.setBrowserState(state);
|
||||
|
||||
// at this point, the cookie should be restored...
|
||||
enumerator = Services.cookies.enumerator;
|
||||
@ -55,5 +52,5 @@ add_task(function*() {
|
||||
|
||||
// clean up
|
||||
Services.cookies.removeAll();
|
||||
yield BrowserTestUtils.closeWindow(win);
|
||||
yield promiseRemoveTab(tab);
|
||||
});
|
||||
|
@ -1,275 +1,73 @@
|
||||
"use strict";
|
||||
|
||||
const PATH = "/browser/browser/components/sessionstore/test/";
|
||||
|
||||
/**
|
||||
* Remove all cookies to start off a clean slate.
|
||||
*/
|
||||
add_task(function* test_setup() {
|
||||
requestLongerTimeout(3);
|
||||
Services.cookies.removeAll();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test multiple scenarios with different Set-Cookie header domain= params.
|
||||
*/
|
||||
add_task(function* test_run() {
|
||||
// Set-Cookie: foobar=random()
|
||||
// The domain of the cookie should be the request domain (www.example.com).
|
||||
// We should collect data only for the request domain, no parent or subdomains.
|
||||
yield testCookieCollection({
|
||||
host: "http://www.example.com",
|
||||
cookieHost: "www.example.com",
|
||||
cookieURIs: ["http://www.example.com" + PATH],
|
||||
noCookieURIs: ["http://example.com/" + PATH]
|
||||
});
|
||||
|
||||
// Set-Cookie: foobar=random()
|
||||
// The domain of the cookie should be the request domain (example.com).
|
||||
// We should collect data only for the request domain, no parent or subdomains.
|
||||
yield testCookieCollection({
|
||||
host: "http://example.com",
|
||||
cookieHost: "example.com",
|
||||
cookieURIs: ["http://example.com" + PATH],
|
||||
noCookieURIs: ["http://www.example.com/" + PATH]
|
||||
});
|
||||
|
||||
// Set-Cookie: foobar=random(); Domain=example.com
|
||||
// The domain of the cookie should be the given one (.example.com).
|
||||
// We should collect data for the given domain and its subdomains.
|
||||
yield testCookieCollection({
|
||||
host: "http://example.com",
|
||||
domain: "example.com",
|
||||
cookieHost: ".example.com",
|
||||
cookieURIs: ["http://example.com" + PATH, "http://www.example.com/" + PATH],
|
||||
noCookieURIs: ["about:robots"]
|
||||
});
|
||||
|
||||
// Set-Cookie: foobar=random(); Domain=.example.com
|
||||
// The domain of the cookie should be the given one (.example.com).
|
||||
// We should collect data for the given domain and its subdomains.
|
||||
yield testCookieCollection({
|
||||
host: "http://example.com",
|
||||
domain: ".example.com",
|
||||
cookieHost: ".example.com",
|
||||
cookieURIs: ["http://example.com" + PATH, "http://www.example.com/" + PATH],
|
||||
noCookieURIs: ["about:robots"]
|
||||
});
|
||||
|
||||
// Set-Cookie: foobar=random(); Domain=www.example.com
|
||||
// The domain of the cookie should be the given one (.www.example.com).
|
||||
// We should collect data for the given domain and its subdomains.
|
||||
yield testCookieCollection({
|
||||
host: "http://www.example.com",
|
||||
domain: "www.example.com",
|
||||
cookieHost: ".www.example.com",
|
||||
cookieURIs: ["http://www.example.com/" + PATH],
|
||||
noCookieURIs: ["http://example.com"]
|
||||
});
|
||||
|
||||
// Set-Cookie: foobar=random(); Domain=.www.example.com
|
||||
// The domain of the cookie should be the given one (.www.example.com).
|
||||
// We should collect data for the given domain and its subdomains.
|
||||
yield testCookieCollection({
|
||||
host: "http://www.example.com",
|
||||
domain: ".www.example.com",
|
||||
cookieHost: ".www.example.com",
|
||||
cookieURIs: ["http://www.example.com/" + PATH],
|
||||
noCookieURIs: ["http://example.com"]
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test multiple scenarios with different privacy levels.
|
||||
*/
|
||||
add_task(function* test_run_privacy_level() {
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
|
||||
});
|
||||
|
||||
// With the default privacy level we collect all cookies.
|
||||
yield testCookieCollection({
|
||||
host: "http://example.com",
|
||||
domain: ".example.com",
|
||||
cookieHost: ".example.com",
|
||||
cookieURIs: ["https://example.com/" + PATH, "http://example.com/" + PATH],
|
||||
noCookieURIs: ["about:robots"]
|
||||
});
|
||||
|
||||
// With the default privacy level we collect all cookies.
|
||||
yield testCookieCollection({
|
||||
host: "https://example.com",
|
||||
domain: ".example.com",
|
||||
cookieHost: ".example.com",
|
||||
cookieURIs: ["https://example.com/" + PATH, "http://example.com/" + PATH],
|
||||
noCookieURIs: ["about:robots"]
|
||||
});
|
||||
|
||||
// With the default privacy level we collect all cookies.
|
||||
yield testCookieCollection({
|
||||
isSecure: true,
|
||||
host: "https://example.com",
|
||||
domain: ".example.com",
|
||||
cookieHost: ".example.com",
|
||||
cookieURIs: ["https://example.com/" + PATH, "http://example.com/" + PATH],
|
||||
noCookieURIs: ["about:robots"]
|
||||
});
|
||||
|
||||
// Set level=encrypted, don't store any secure cookies.
|
||||
Services.prefs.setIntPref("browser.sessionstore.privacy_level", 1);
|
||||
|
||||
// With level=encrypted, non-secure cookies will be stored.
|
||||
yield testCookieCollection({
|
||||
host: "http://example.com",
|
||||
domain: ".example.com",
|
||||
cookieHost: ".example.com",
|
||||
cookieURIs: ["https://example.com/" + PATH, "http://example.com/" + PATH],
|
||||
noCookieURIs: ["about:robots"]
|
||||
});
|
||||
|
||||
// With level=encrypted, non-secure cookies will be stored,
|
||||
// even if sent by an HTTPS site.
|
||||
yield testCookieCollection({
|
||||
host: "https://example.com",
|
||||
domain: ".example.com",
|
||||
cookieHost: ".example.com",
|
||||
cookieURIs: ["https://example.com/" + PATH, "http://example.com/" + PATH],
|
||||
noCookieURIs: ["about:robots"]
|
||||
});
|
||||
|
||||
// With level=encrypted, secure cookies will NOT be stored.
|
||||
yield testCookieCollection({
|
||||
isSecure: true,
|
||||
host: "https://example.com",
|
||||
domain: ".example.com",
|
||||
cookieHost: ".example.com",
|
||||
noCookieURIs: ["https://example.com/" + PATH, "http://example.com/" + PATH]
|
||||
});
|
||||
|
||||
// Set level=full, don't store any cookies.
|
||||
Services.prefs.setIntPref("browser.sessionstore.privacy_level", 2);
|
||||
|
||||
// With level=full we must not store any cookies.
|
||||
yield testCookieCollection({
|
||||
host: "http://example.com",
|
||||
domain: ".example.com",
|
||||
cookieHost: ".example.com",
|
||||
noCookieURIs: ["https://example.com/" + PATH, "http://example.com/" + PATH]
|
||||
});
|
||||
|
||||
// With level=full we must not store any cookies.
|
||||
yield testCookieCollection({
|
||||
host: "https://example.com",
|
||||
domain: ".example.com",
|
||||
cookieHost: ".example.com",
|
||||
noCookieURIs: ["https://example.com/" + PATH, "http://example.com/" + PATH]
|
||||
});
|
||||
|
||||
// With level=full we must not store any cookies.
|
||||
yield testCookieCollection({
|
||||
isSecure: true,
|
||||
host: "https://example.com",
|
||||
domain: ".example.com",
|
||||
cookieHost: ".example.com",
|
||||
noCookieURIs: ["https://example.com/" + PATH, "http://example.com/" + PATH]
|
||||
});
|
||||
|
||||
Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
|
||||
});
|
||||
|
||||
/**
|
||||
* Generic test function to check sessionstore's cookie collection module with
|
||||
* different cookie domains given in the Set-Cookie header. See above for some
|
||||
* usage examples.
|
||||
*/
|
||||
var testCookieCollection = async function(params) {
|
||||
let tab = gBrowser.addTab("about:blank");
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
let urlParams = new URLSearchParams();
|
||||
let value = Math.random();
|
||||
urlParams.append("value", value);
|
||||
|
||||
if (params.domain) {
|
||||
urlParams.append("domain", params.domain);
|
||||
}
|
||||
|
||||
if (params.isSecure) {
|
||||
urlParams.append("secure", "1");
|
||||
}
|
||||
|
||||
// Construct request URI.
|
||||
let requestUri = `${params.host}${PATH}browser_cookies.sjs?${urlParams}`;
|
||||
|
||||
// Wait for the browser to load and the cookie to be set.
|
||||
// These two events can probably happen in no particular order,
|
||||
// so let's wait for them in parallel.
|
||||
await Promise.all([
|
||||
waitForNewCookie(),
|
||||
replaceCurrentURI(browser, requestUri)
|
||||
function promiseSetCookie(cookie) {
|
||||
info(`Set-Cookie: ${cookie}`);
|
||||
return Promise.all([
|
||||
waitForCookieChanged(),
|
||||
ContentTask.spawn(gBrowser.selectedBrowser, cookie,
|
||||
passedCookie => content.document.cookie = passedCookie)
|
||||
]);
|
||||
}
|
||||
|
||||
// Check all URIs for which the cookie should be collected.
|
||||
for (let uri of params.cookieURIs || []) {
|
||||
await replaceCurrentURI(browser, uri);
|
||||
|
||||
// Check the cookie.
|
||||
let cookie = getCookie();
|
||||
is(cookie.host, params.cookieHost, "cookie host is correct");
|
||||
is(cookie.path, PATH, "cookie path is correct");
|
||||
is(cookie.name, "foobar", "cookie name is correct");
|
||||
is(cookie.value, value, "cookie value is correct");
|
||||
}
|
||||
|
||||
// Check all URIs for which the cookie should NOT be collected.
|
||||
for (let uri of params.noCookieURIs || []) {
|
||||
await replaceCurrentURI(browser, uri);
|
||||
|
||||
// Cookie should be ignored.
|
||||
ok(!getCookie(), "no cookie collected");
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
gBrowser.removeTab(tab);
|
||||
Services.cookies.removeAll();
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace the current URI of the given browser by loading a new URI. The
|
||||
* browser's session history will be completely replaced. This function ensures
|
||||
* that the parent process has the lastest shistory data before resolving.
|
||||
*/
|
||||
var replaceCurrentURI = async function(browser, uri) {
|
||||
// Replace the tab's current URI with the parent domain.
|
||||
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
|
||||
browser.loadURIWithFlags(uri, flags);
|
||||
await promiseBrowserLoaded(browser);
|
||||
|
||||
// Ensure the tab's session history is up-to-date.
|
||||
await TabStateFlusher.flush(browser);
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for a new "*example.com" cookie to be added.
|
||||
*/
|
||||
function waitForNewCookie() {
|
||||
function waitForCookieChanged() {
|
||||
return new Promise(resolve => {
|
||||
Services.obs.addObserver(function observer(subj, topic, data) {
|
||||
let cookie = subj.QueryInterface(Ci.nsICookie2);
|
||||
if (data == "added" && cookie.host.endsWith("example.com")) {
|
||||
Services.obs.removeObserver(observer, topic);
|
||||
resolve();
|
||||
}
|
||||
Services.obs.removeObserver(observer, topic);
|
||||
resolve();
|
||||
}, "cookie-changed", false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the first cookie in the first window from the current sessionstore
|
||||
* state.
|
||||
*/
|
||||
function getCookie() {
|
||||
let state = JSON.parse(ss.getWindowState(window));
|
||||
let cookies = state.windows[0].cookies || [];
|
||||
return cookies[0] || null;
|
||||
function cookieExists(host, name, value) {
|
||||
let {cookies: [c]} = JSON.parse(ss.getBrowserState());
|
||||
return c && c.host == host && c.name == name && c.value == value;
|
||||
}
|
||||
|
||||
// Setup and cleanup.
|
||||
add_task(function* test_setup() {
|
||||
registerCleanupFunction(() => {
|
||||
Services.cookies.removeAll();
|
||||
});
|
||||
});
|
||||
|
||||
// Test session cookie storage.
|
||||
add_task(function* test_run() {
|
||||
Services.cookies.removeAll();
|
||||
|
||||
// Add a new tab for testing.
|
||||
gBrowser.selectedTab = gBrowser.addTab("http://example.com/");
|
||||
yield promiseBrowserLoaded(gBrowser.selectedBrowser);
|
||||
|
||||
// Add a session cookie.
|
||||
yield promiseSetCookie("foo=bar");
|
||||
ok(cookieExists("example.com", "foo", "bar"), "cookie was added");
|
||||
|
||||
// Modify a session cookie.
|
||||
yield promiseSetCookie("foo=baz");
|
||||
ok(cookieExists("example.com", "foo", "baz"), "cookie was modified");
|
||||
|
||||
// Turn the session cookie into a normal one.
|
||||
let expiry = new Date();
|
||||
expiry.setDate(expiry.getDate() + 2);
|
||||
yield promiseSetCookie(`foo=baz; Expires=${expiry.toUTCString()}`);
|
||||
ok(!cookieExists("example.com", "foo", "baz"), "no longer a session cookie");
|
||||
|
||||
// Turn it back into a session cookie.
|
||||
yield promiseSetCookie("foo=bar");
|
||||
ok(cookieExists("example.com", "foo", "bar"), "again a session cookie");
|
||||
|
||||
// Remove the session cookie.
|
||||
yield promiseSetCookie("foo=; Expires=Thu, 01 Jan 1970 00:00:00 GMT");
|
||||
ok(!cookieExists("example.com", "foo", ""), "cookie was removed");
|
||||
|
||||
// Add a session cookie.
|
||||
yield promiseSetCookie("foo=bar");
|
||||
ok(cookieExists("example.com", "foo", "bar"), "cookie was added");
|
||||
|
||||
// Clear all session cookies.
|
||||
Services.cookies.removeAll();
|
||||
ok(!cookieExists("example.com", "foo", "bar"), "cookies were cleared");
|
||||
|
||||
// Cleanup.
|
||||
yield promiseRemoveTab(gBrowser.selectedTab);
|
||||
});
|
||||
|
@ -1,26 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Components.utils.importGlobalProperties(["URLSearchParams"]);
|
||||
|
||||
function handleRequest(req, resp) {
|
||||
resp.setStatusLine(req.httpVersion, 200);
|
||||
|
||||
let params = new URLSearchParams(req.queryString);
|
||||
let value = params.get("value");
|
||||
|
||||
let domain = "";
|
||||
if (params.has("domain")) {
|
||||
domain = `; Domain=${params.get("domain")}`;
|
||||
}
|
||||
|
||||
let secure = "";
|
||||
if (params.has("secure")) {
|
||||
secure = "; Secure";
|
||||
}
|
||||
|
||||
resp.setHeader("Set-Cookie", `foobar=${value}${domain}${secure}`);
|
||||
resp.write("<meta charset=utf-8>hi");
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
"use strict";
|
||||
|
||||
function createTestState() {
|
||||
let r = Math.round(Math.random() * 100000);
|
||||
|
||||
let cookie = {
|
||||
host: "http://example.com",
|
||||
path: "/",
|
||||
name: `name${r}`,
|
||||
value: `value${r}`
|
||||
};
|
||||
|
||||
let state = {
|
||||
windows: [{
|
||||
tabs: [{entries: [{url: "about:robots", triggeringPrincipal_base64}]}],
|
||||
cookies: [cookie]
|
||||
}]
|
||||
};
|
||||
|
||||
return [state, cookie];
|
||||
}
|
||||
|
||||
function waitForNewCookie({host, name, value}) {
|
||||
info(`waiting for cookie ${name}=${value} from ${host}...`);
|
||||
|
||||
return new Promise(resolve => {
|
||||
Services.obs.addObserver(function observer(subj, topic, data) {
|
||||
if (data != "added") {
|
||||
return;
|
||||
}
|
||||
|
||||
let cookie = subj.QueryInterface(Ci.nsICookie2);
|
||||
if (cookie.host == host && cookie.name == name && cookie.value == value) {
|
||||
ok(true, "cookie added by the cookie service");
|
||||
Services.obs.removeObserver(observer, topic);
|
||||
resolve();
|
||||
}
|
||||
}, "cookie-changed", false);
|
||||
});
|
||||
}
|
||||
|
||||
// Setup and cleanup.
|
||||
add_task(function* test_setup() {
|
||||
Services.cookies.removeAll();
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.cookies.removeAll();
|
||||
});
|
||||
});
|
||||
|
||||
// Test that calling ss.setWindowState() with a legacy state object that
|
||||
// contains cookies in the window state restores session cookies properly.
|
||||
add_task(function* test_window() {
|
||||
let [state, cookie] = createTestState();
|
||||
let win = yield promiseNewWindowLoaded();
|
||||
|
||||
let promiseCookie = waitForNewCookie(cookie);
|
||||
ss.setWindowState(win, JSON.stringify(state), true);
|
||||
yield promiseCookie;
|
||||
|
||||
yield BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
||||
// Test that calling ss.setBrowserState() with a legacy state object that
|
||||
// contains cookies in the window state restores session cookies properly.
|
||||
add_task(function* test_browser() {
|
||||
let backupState = ss.getBrowserState();
|
||||
let [state, cookie] = createTestState();
|
||||
yield Promise.all([waitForNewCookie(cookie), promiseBrowserState(state)]);
|
||||
yield promiseBrowserState(backupState);
|
||||
});
|
110
browser/components/sessionstore/test/browser_cookies_privacy.js
Normal file
110
browser/components/sessionstore/test/browser_cookies_privacy.js
Normal file
@ -0,0 +1,110 @@
|
||||
"use strict";
|
||||
|
||||
// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
|
||||
const MAX_EXPIRY = Math.pow(2, 62);
|
||||
|
||||
function addCookie(scheme, secure = false) {
|
||||
let cookie = createTestCookie("http", secure);
|
||||
Services.cookies.add(cookie.host, cookie.path, cookie.name, cookie.value,
|
||||
cookie.secure, /* isHttpOnly = */ false,
|
||||
/* isSession = */ true, MAX_EXPIRY,
|
||||
/* originAttributes = */ {});
|
||||
return cookie;
|
||||
}
|
||||
|
||||
function createTestCookie(scheme, secure = false) {
|
||||
let r = Math.round(Math.random() * 100000);
|
||||
|
||||
let cookie = {
|
||||
host: `${scheme}://example.com`,
|
||||
path: "/",
|
||||
name: `name${r}`,
|
||||
value: `value${r}`,
|
||||
secure
|
||||
};
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
function getCookie() {
|
||||
let state = JSON.parse(ss.getBrowserState());
|
||||
let cookies = state.cookies || [];
|
||||
return cookies[0];
|
||||
}
|
||||
|
||||
function compareCookies(a) {
|
||||
let b = getCookie();
|
||||
return a.host == b.host && a.name == b.name && a.value == b.value;
|
||||
}
|
||||
|
||||
// Setup and cleanup.
|
||||
add_task(function* test_setup() {
|
||||
Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
|
||||
Services.cookies.removeAll();
|
||||
});
|
||||
});
|
||||
|
||||
// Test privacy_level=none (default). We store all session cookies.
|
||||
add_task(function* test_level_none() {
|
||||
Services.cookies.removeAll();
|
||||
|
||||
// Set level=none, store all cookies.
|
||||
Services.prefs.setIntPref("browser.sessionstore.privacy_level", 0);
|
||||
|
||||
// With the default privacy level we collect all cookies.
|
||||
ok(compareCookies(addCookie("http")), "non-secure http cookie stored");
|
||||
Services.cookies.removeAll();
|
||||
|
||||
// With the default privacy level we collect all cookies.
|
||||
ok(compareCookies(addCookie("https")), "non-secure https cookie stored");
|
||||
Services.cookies.removeAll();
|
||||
|
||||
// With the default privacy level we collect all cookies.
|
||||
ok(compareCookies(addCookie("https", true)), "secure https cookie stored");
|
||||
Services.cookies.removeAll();
|
||||
});
|
||||
|
||||
// Test privacy_level=encrypted. We store all non-secure session cookies.
|
||||
add_task(function* test_level_encrypted() {
|
||||
Services.cookies.removeAll();
|
||||
|
||||
// Set level=encrypted, don't store any secure cookies.
|
||||
Services.prefs.setIntPref("browser.sessionstore.privacy_level", 1);
|
||||
|
||||
// With level=encrypted, non-secure cookies will be stored.
|
||||
ok(compareCookies(addCookie("http")), "non-secure http cookie stored");
|
||||
Services.cookies.removeAll();
|
||||
|
||||
// With level=encrypted, non-secure cookies will be stored,
|
||||
// even if sent by an HTTPS site.
|
||||
ok(compareCookies(addCookie("https")), "non-secure https cookie stored");
|
||||
Services.cookies.removeAll();
|
||||
|
||||
// With level=encrypted, non-secure cookies will be stored,
|
||||
// even if sent by an HTTPS site.
|
||||
ok(addCookie("https", true) && !getCookie(), "secure https cookie not stored");
|
||||
Services.cookies.removeAll();
|
||||
});
|
||||
|
||||
// Test privacy_level=full. We store no session cookies.
|
||||
add_task(function* test_level_full() {
|
||||
Services.cookies.removeAll();
|
||||
|
||||
// Set level=full, don't store any cookies.
|
||||
Services.prefs.setIntPref("browser.sessionstore.privacy_level", 2);
|
||||
|
||||
// With level=full we must not store any cookies.
|
||||
ok(addCookie("http") && !getCookie(), "non-secure http cookie not stored");
|
||||
Services.cookies.removeAll();
|
||||
|
||||
// With level=full we must not store any cookies.
|
||||
ok(addCookie("https") && !getCookie(), "non-secure https cookie not stored");
|
||||
Services.cookies.removeAll();
|
||||
|
||||
// With level=full we must not store any cookies.
|
||||
ok(addCookie("https", true) && !getCookie(), "secure https cookie not stored");
|
||||
Services.cookies.removeAll();
|
||||
});
|
@ -110,7 +110,8 @@ add_task(function* test() {
|
||||
"set": [ [ "privacy.userContext.enabled", true ] ]
|
||||
});
|
||||
|
||||
let lastSessionRestore;
|
||||
Services.cookies.removeAll();
|
||||
|
||||
for (let userContextId of Object.keys(USER_CONTEXTS)) {
|
||||
// Load the page in 3 different contexts and set a cookie
|
||||
// which should only be visible in that context.
|
||||
@ -128,13 +129,11 @@ add_task(function* test() {
|
||||
// Ensure the tab's session history is up-to-date.
|
||||
yield TabStateFlusher.flush(browser);
|
||||
|
||||
lastSessionRestore = ss.getWindowState(window);
|
||||
|
||||
// Remove the tab.
|
||||
gBrowser.removeTab(tab);
|
||||
}
|
||||
|
||||
let state = JSON.parse(lastSessionRestore);
|
||||
is(state.windows[0].cookies.length, USER_CONTEXTS.length,
|
||||
let state = JSON.parse(ss.getBrowserState());
|
||||
is(state.cookies.length, USER_CONTEXTS.length,
|
||||
"session restore should have each container's cookie");
|
||||
});
|
||||
|
@ -170,6 +170,7 @@
|
||||
#ifdef ACCESSIBILITY
|
||||
#ifdef XP_WIN32
|
||||
@BINPATH@/Accessible.tlb
|
||||
@BINPATH@/AccessibleHandler.dll
|
||||
@BINPATH@/AccessibleMarshal.dll
|
||||
@BINPATH@/IA2Marshal.dll
|
||||
#endif
|
||||
|
@ -55,6 +55,24 @@
|
||||
!define UpdateChannel "@MOZ_UPDATE_CHANNEL@"
|
||||
!endif
|
||||
|
||||
# AccessibleHandler.dll uses a different CLSID depending on release channel.
|
||||
# These defines must match HANDLER_CLSID defined in
|
||||
# accessible/ipc/win/handler/HandlerData.idl
|
||||
|
||||
#ifndef MOZ_OFFICIAL_BRANDING
|
||||
#ifdef DEBUG
|
||||
!define AccessibleHandlerCLSID "{398FFD8D-5382-48F7-9E3B-19012762D39A}"
|
||||
#else
|
||||
!define AccessibleHandlerCLSID "{CE573FAF-7815-4FC2-A031-B092268ACE9E}"
|
||||
#endif
|
||||
#elifdef NIGHTLY_BUILD
|
||||
!define AccessibleHandlerCLSID "{4629216B-8753-41BF-9527-5BFF51401671}"
|
||||
#elifdef RELEASE_OR_BETA
|
||||
!define AccessibleHandlerCLSID "{1BAA303D-B4B9-45E5-9CCB-E3FCA3E274B6}"
|
||||
#else
|
||||
!define AccessibleHandlerCLSID "{4A195748-DCA2-45FB-9295-0A139E76A9E7}"
|
||||
#endif
|
||||
|
||||
# Due to official and beta using the same branding this is needed to
|
||||
# differentiante between the url used by the stub for downloading.
|
||||
!if "@MOZ_UPDATE_CHANNEL@" == "beta"
|
||||
|
@ -307,6 +307,16 @@ Section "-Application" APP_IDX
|
||||
|
||||
ClearErrors
|
||||
|
||||
${RegisterDLL} "$INSTDIR\AccessibleHandler.dll"
|
||||
${If} ${Errors}
|
||||
${LogMsg} "** ERROR Registering: $INSTDIR\AccessibleHandler.dll **"
|
||||
${Else}
|
||||
${LogUninstall} "DLLReg: \AccessibleHandler.dll"
|
||||
${LogMsg} "Registered: $INSTDIR\AccessibleHandler.dll"
|
||||
${EndIf}
|
||||
|
||||
ClearErrors
|
||||
|
||||
; Default for creating Start Menu shortcut
|
||||
; (1 = create, 0 = don't create)
|
||||
${If} $AddStartMenuSC == ""
|
||||
|
@ -1160,6 +1160,7 @@
|
||||
; should be ${FileMainEXE} so if it is in use the CheckForFilesInUse macro
|
||||
; returns after the first check.
|
||||
Push "end"
|
||||
Push "AccessibleHandler.dll"
|
||||
Push "AccessibleMarshal.dll"
|
||||
Push "IA2Marshal.dll"
|
||||
Push "freebl3.dll"
|
||||
|
@ -383,6 +383,12 @@ Section "Uninstall"
|
||||
${UnregisterDLL} "$INSTDIR\AccessibleMarshal.dll"
|
||||
${EndIf}
|
||||
|
||||
; Only unregister the dll if the registration points to this installation
|
||||
ReadRegStr $R1 HKCR "CLSID\${AccessibleHandlerCLSID}\InprocHandler32" ""
|
||||
${If} "$INSTDIR\AccessibleHandler.dll" == "$R1"
|
||||
${UnregisterDLL} "$INSTDIR\AccessibleHandler.dll"
|
||||
${EndIf}
|
||||
|
||||
${un.RemovePrecompleteEntries} "false"
|
||||
|
||||
${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
|
||||
|
@ -49,7 +49,10 @@ if test -z "$bucket" -a -z "$SCCACHE_DISABLE" -a -z "$no_sccache" -a -z "$MOZ_PG
|
||||
# prevent rerun if az is set, or wget is not available
|
||||
if test -z "$availability_zone" -a -x "$(command -v wget)"; then
|
||||
if test -n "${TASKCLUSTER_WORKER_GROUP}"; then
|
||||
availability_zone="${TASKCLUSTER_WORKER_GROUP}"
|
||||
# TASKCLUSTER_WORKER_GROUP is just the region now, so
|
||||
# stick an extra character on to make the already-convoluted logic
|
||||
# here simpler.
|
||||
availability_zone="${TASKCLUSTER_WORKER_GROUP}x"
|
||||
else
|
||||
# timeout after 1 second, and don't retry (failure indicates instance is not in ec2 or network issue)
|
||||
# availability_zone is of the form <region><letter> where region is e.g. us-west-2, and az is us-west-2a
|
||||
|
BIN
config/external/icu/data/icudt58l.dat
vendored
BIN
config/external/icu/data/icudt58l.dat
vendored
Binary file not shown.
@ -942,6 +942,7 @@ CARGO_BUILD = env $(environment_cleaner) $(rustflags_override) \
|
||||
CLANG_PATH=$(MOZ_CLANG_PATH) \
|
||||
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||
RUST_BACKTRACE=1 \
|
||||
MOZ_TOPOBJDIR=$(topobjdir) \
|
||||
$(CARGO) build $(cargo_build_flags)
|
||||
|
||||
ifdef RUST_LIBRARY_FILE
|
||||
|
@ -653,7 +653,7 @@ skip-if = (toolkit == 'android') # Android: Bug 775227
|
||||
[test_innersize_scrollport.html]
|
||||
[test_integer_attr_with_leading_zero.html]
|
||||
[test_intersectionobservers.html]
|
||||
skip-if = (os == "android") # Timing issues
|
||||
skip-if = true # Track Bug 1320704
|
||||
[test_link_prefetch.html]
|
||||
skip-if = !e10s # Track Bug 1281415
|
||||
[test_link_stylesheet.html]
|
||||
|
@ -3592,7 +3592,7 @@ HTMLInputElement::Select()
|
||||
|
||||
nsTextEditorState* tes = GetEditorState();
|
||||
if (tes) {
|
||||
nsFrameSelection* fs = tes->GetConstFrameSelection();
|
||||
RefPtr<nsFrameSelection> fs = tes->GetConstFrameSelection();
|
||||
if (fs && fs->MouseDownRecorded()) {
|
||||
// This means that we're being called while the frame selection has a mouse
|
||||
// down event recorded to adjust the caret during the mouse up event.
|
||||
|
@ -3076,23 +3076,31 @@ TabChild::ReinitRendering()
|
||||
Unused << cb->SendGetCompositorOptions(mLayersId, &options);
|
||||
mCompositorOptions = Some(options);
|
||||
|
||||
bool success;
|
||||
nsTArray<LayersBackend> ignored;
|
||||
PLayerTransactionChild* shadowManager =
|
||||
cb->SendPLayerTransactionConstructor(ignored, LayersId(), &mTextureFactoryIdentifier, &success);
|
||||
if (!success) {
|
||||
NS_WARNING("failed to re-allocate layer transaction");
|
||||
return;
|
||||
}
|
||||
if (gfxVars::UseWebRender()) {
|
||||
RefPtr<LayerManager> lm = mPuppetWidget->RecreateLayerManager(nullptr);
|
||||
MOZ_ASSERT(lm->AsWebRenderLayerManager());
|
||||
lm->AsWebRenderLayerManager()->Initialize(cb,
|
||||
wr::AsPipelineId(mLayersId),
|
||||
&mTextureFactoryIdentifier);
|
||||
} else {
|
||||
bool success;
|
||||
nsTArray<LayersBackend> ignored;
|
||||
PLayerTransactionChild* shadowManager =
|
||||
cb->SendPLayerTransactionConstructor(ignored, LayersId(), &mTextureFactoryIdentifier, &success);
|
||||
if (!success) {
|
||||
NS_WARNING("failed to re-allocate layer transaction");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shadowManager) {
|
||||
NS_WARNING("failed to re-construct LayersChild");
|
||||
return;
|
||||
}
|
||||
if (!shadowManager) {
|
||||
NS_WARNING("failed to re-construct LayersChild");
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<LayerManager> lm = mPuppetWidget->RecreateLayerManager(shadowManager);
|
||||
ShadowLayerForwarder* lf = lm->AsShadowForwarder();
|
||||
lf->IdentifyTextureHost(mTextureFactoryIdentifier);
|
||||
RefPtr<LayerManager> lm = mPuppetWidget->RecreateLayerManager(shadowManager);
|
||||
ShadowLayerForwarder* lf = lm->AsShadowForwarder();
|
||||
lf->IdentifyTextureHost(mTextureFactoryIdentifier);
|
||||
}
|
||||
|
||||
InitAPZState();
|
||||
|
||||
|
@ -4,3 +4,5 @@ support-files =
|
||||
file_domainPolicy_base.html
|
||||
|
||||
[browser_domainPolicy.js]
|
||||
[browser_memory_distribution_telemetry.js]
|
||||
skip-if = !e10 # This is an e10s only probe.
|
||||
|
71
dom/ipc/tests/browser_memory_distribution_telemetry.js
Normal file
71
dom/ipc/tests/browser_memory_distribution_telemetry.js
Normal file
@ -0,0 +1,71 @@
|
||||
"use strict";
|
||||
|
||||
var session = Cu.import("resource://gre/modules/TelemetrySession.jsm", {});
|
||||
|
||||
const DUMMY_PAGE_DATA_URI = `data:text/html,
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Dummy</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id='header'>Just a regular everyday normal page.</h1>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
/**
|
||||
* Tests the MEMORY_DISTRIBUTION_AMONG_CONTENT probe by opening a few tabs, then triggering
|
||||
* the memory probes and waiting for the "gather-memory-telemetry-finished" notification.
|
||||
*/
|
||||
add_task(function* test_memory_distribution() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
if (SpecialPowers.getIntPref("dom.ipc.processCount", 1) < 2) {
|
||||
ok(true, "Skip this test if e10s-multi is disabled.");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
yield SpecialPowers.pushPrefEnv({set: [["toolkit.telemetry.enabled", true]]});
|
||||
Services.telemetry.canRecordExtended = true;
|
||||
|
||||
let histogram = Services.telemetry.getKeyedHistogramById("MEMORY_DISTRIBUTION_AMONG_CONTENT");
|
||||
histogram.clear();
|
||||
|
||||
let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_PAGE_DATA_URI);
|
||||
let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_PAGE_DATA_URI);
|
||||
let tab3 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_PAGE_DATA_URI);
|
||||
|
||||
let finishedGathering = new Promise(resolve => {
|
||||
let obs = function () {
|
||||
Services.obs.removeObserver(obs, "gather-memory-telemetry-finished");
|
||||
resolve();
|
||||
}
|
||||
Services.obs.addObserver(obs, "gather-memory-telemetry-finished");
|
||||
});
|
||||
|
||||
session.TelemetrySession.getPayload();
|
||||
|
||||
yield finishedGathering;
|
||||
|
||||
let s = histogram.snapshot();
|
||||
ok("0 - 10 tabs" in s, "We should have some samples by now in this bucket.")
|
||||
for (var key in s) {
|
||||
is(key, "0 - 10 tabs");
|
||||
let fewTabsSnapshot = s[key];
|
||||
ok(fewTabsSnapshot.sum > 0, "Zero difference between all the content processes is unlikely, what happened?");
|
||||
ok(fewTabsSnapshot.sum < 80, "20 percentage difference on average is unlikely, what happened?");
|
||||
let c = fewTabsSnapshot.counts;
|
||||
for (let i = 10; i < c.length; i++) {
|
||||
// If this check fails it means that one of the content processes uses at least 20% more or 20% less than the mean.
|
||||
is(c[i], 0, "All the buckets above 10 should be empty");
|
||||
}
|
||||
}
|
||||
|
||||
histogram.clear();
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab3);
|
||||
yield BrowserTestUtils.removeTab(tab2);
|
||||
yield BrowserTestUtils.removeTab(tab1);
|
||||
finish();
|
||||
});
|
@ -64,6 +64,14 @@ VP8TrackEncoder::VP8TrackEncoder(TrackRate aTrackRate)
|
||||
|
||||
VP8TrackEncoder::~VP8TrackEncoder()
|
||||
{
|
||||
Destroy();
|
||||
MOZ_COUNT_DTOR(VP8TrackEncoder);
|
||||
}
|
||||
|
||||
void
|
||||
VP8TrackEncoder::Destroy()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
if (mInitialized) {
|
||||
vpx_codec_destroy(mVPXContext);
|
||||
}
|
||||
@ -71,7 +79,7 @@ VP8TrackEncoder::~VP8TrackEncoder()
|
||||
if (mVPXImageWrapper) {
|
||||
vpx_img_free(mVPXImageWrapper);
|
||||
}
|
||||
MOZ_COUNT_DTOR(VP8TrackEncoder);
|
||||
mInitialized = false;
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -83,18 +91,15 @@ VP8TrackEncoder::Init(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
|
||||
}
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
|
||||
mFrameWidth = aWidth;
|
||||
mFrameHeight = aHeight;
|
||||
mDisplayWidth = aDisplayWidth;
|
||||
mDisplayHeight = aDisplayHeight;
|
||||
if (mInitialized) {
|
||||
MOZ_ASSERT(false);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Encoder configuration structure.
|
||||
vpx_codec_enc_cfg_t config;
|
||||
memset(&config, 0, sizeof(vpx_codec_enc_cfg_t));
|
||||
if (vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &config, 0)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsresult rv = SetConfigurationValues(aWidth, aHeight, aDisplayWidth, aDisplayHeight, config);
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
|
||||
|
||||
// Creating a wrapper to the image - setting image data to NULL. Actual
|
||||
// pointer will be set in encode. Setting align to 1, as it is meaningless
|
||||
@ -102,6 +107,71 @@ VP8TrackEncoder::Init(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
|
||||
vpx_img_wrap(mVPXImageWrapper, VPX_IMG_FMT_I420,
|
||||
mFrameWidth, mFrameHeight, 1, nullptr);
|
||||
|
||||
vpx_codec_flags_t flags = 0;
|
||||
flags |= VPX_CODEC_USE_OUTPUT_PARTITION;
|
||||
if (vpx_codec_enc_init(mVPXContext, vpx_codec_vp8_cx(), &config, flags)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
vpx_codec_control(mVPXContext, VP8E_SET_STATIC_THRESHOLD, 1);
|
||||
vpx_codec_control(mVPXContext, VP8E_SET_CPUUSED, -6);
|
||||
vpx_codec_control(mVPXContext, VP8E_SET_TOKEN_PARTITIONS,
|
||||
VP8_ONE_TOKENPARTITION);
|
||||
|
||||
mInitialized = true;
|
||||
mon.NotifyAll();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
VP8TrackEncoder::Reconfigure(int32_t aWidth, int32_t aHeight,
|
||||
int32_t aDisplayWidth, int32_t aDisplayHeight)
|
||||
{
|
||||
if(aWidth <= 0 || aHeight <= 0 || aDisplayWidth <= 0 || aDisplayHeight <= 0) {
|
||||
MOZ_ASSERT(false);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
if (!mInitialized) {
|
||||
MOZ_ASSERT(false);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mInitialized = false;
|
||||
// Recreate image wrapper
|
||||
vpx_img_free(mVPXImageWrapper);
|
||||
vpx_img_wrap(mVPXImageWrapper, VPX_IMG_FMT_I420, aWidth, aHeight, 1, nullptr);
|
||||
// Encoder configuration structure.
|
||||
vpx_codec_enc_cfg_t config;
|
||||
nsresult rv = SetConfigurationValues(aWidth, aHeight, aDisplayWidth, aDisplayHeight, config);
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
|
||||
// Set new configuration
|
||||
if (vpx_codec_enc_config_set(mVPXContext.get(), &config) != VPX_CODEC_OK) {
|
||||
VP8LOG(LogLevel::Error, "Failed to set new configuration");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mInitialized = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
VP8TrackEncoder::SetConfigurationValues(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
|
||||
int32_t aDisplayHeight, vpx_codec_enc_cfg_t& config)
|
||||
{
|
||||
mFrameWidth = aWidth;
|
||||
mFrameHeight = aHeight;
|
||||
mDisplayWidth = aDisplayWidth;
|
||||
mDisplayHeight = aDisplayHeight;
|
||||
|
||||
// Encoder configuration structure.
|
||||
memset(&config, 0, sizeof(vpx_codec_enc_cfg_t));
|
||||
if (vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &config, 0)) {
|
||||
VP8LOG(LogLevel::Error, "Failed to get default configuration");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
config.g_w = mFrameWidth;
|
||||
config.g_h = mFrameHeight;
|
||||
// TODO: Maybe we should have various aFrameRate bitrate pair for each devices?
|
||||
@ -144,20 +214,6 @@ VP8TrackEncoder::Init(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
|
||||
// Ensure that we can output one I-frame per second.
|
||||
config.kf_max_dist = 60;
|
||||
|
||||
vpx_codec_flags_t flags = 0;
|
||||
flags |= VPX_CODEC_USE_OUTPUT_PARTITION;
|
||||
if (vpx_codec_enc_init(mVPXContext, vpx_codec_vp8_cx(), &config, flags)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
vpx_codec_control(mVPXContext, VP8E_SET_STATIC_THRESHOLD, 1);
|
||||
vpx_codec_control(mVPXContext, VP8E_SET_CPUUSED, -6);
|
||||
vpx_codec_control(mVPXContext, VP8E_SET_TOKEN_PARTITIONS,
|
||||
VP8_ONE_TOKENPARTITION);
|
||||
|
||||
mInitialized = true;
|
||||
mon.NotifyAll();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -299,10 +355,31 @@ nsresult VP8TrackEncoder::PrepareRawFrame(VideoChunk &aChunk)
|
||||
}
|
||||
|
||||
if (img->GetSize() != IntSize(mFrameWidth, mFrameHeight)) {
|
||||
VP8LOG(LogLevel::Error,
|
||||
"Dynamic resolution changes (was %dx%d, now %dx%d) are unsupported",
|
||||
VP8LOG(LogLevel::Info,
|
||||
"Dynamic resolution change (was %dx%d, now %dx%d).",
|
||||
mFrameWidth, mFrameHeight, img->GetSize().width, img->GetSize().height);
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
|
||||
gfx::IntSize intrinsicSize = aChunk.mFrame.GetIntrinsicSize();
|
||||
gfx::IntSize imgSize = aChunk.mFrame.GetImage()->GetSize();
|
||||
if (imgSize <= IntSize(mFrameWidth, mFrameHeight) && // check buffer size instead
|
||||
// If the new size is less than or equal to old,
|
||||
// the existing encoder instance can continue.
|
||||
NS_SUCCEEDED(Reconfigure(imgSize.width,
|
||||
imgSize.height,
|
||||
intrinsicSize.width,
|
||||
intrinsicSize.height))) {
|
||||
VP8LOG(LogLevel::Info, "Reconfigured VP8 encoder.");
|
||||
} else {
|
||||
// New frame size is larger; re-create the encoder.
|
||||
Destroy();
|
||||
nsresult rv = Init(imgSize.width,
|
||||
imgSize.height,
|
||||
intrinsicSize.width,
|
||||
intrinsicSize.height);
|
||||
VP8LOG(LogLevel::Info, "Recreated VP8 encoder.");
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
}
|
||||
|
||||
ImageFormat format = img->GetFormat();
|
||||
@ -335,7 +412,7 @@ nsresult VP8TrackEncoder::PrepareRawFrame(VideoChunk &aChunk)
|
||||
uint32_t halfHeight = (mFrameHeight + 1) / 2;
|
||||
uint32_t uvPlaneSize = halfWidth * halfHeight;
|
||||
|
||||
if (mI420Frame.IsEmpty()) {
|
||||
if (mI420Frame.Length() != yPlaneSize + uvPlaneSize * 2) {
|
||||
mI420Frame.SetLength(yPlaneSize + uvPlaneSize * 2);
|
||||
}
|
||||
|
||||
@ -567,6 +644,7 @@ VP8TrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData)
|
||||
if (vpx_codec_encode(mVPXContext, mVPXImageWrapper, mEncodedTimestamp,
|
||||
(unsigned long)chunk.GetDuration(), flags,
|
||||
VPX_DL_REALTIME)) {
|
||||
VP8LOG(LogLevel::Error, "vpx_codec_encode failed to encode the frame.");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
// Get the encoded data from VP8 encoder.
|
||||
|
@ -52,6 +52,17 @@ private:
|
||||
// Prepare the input data to the mVPXImageWrapper for encoding.
|
||||
nsresult PrepareRawFrame(VideoChunk &aChunk);
|
||||
|
||||
// Re-configures an existing encoder with a new frame size.
|
||||
nsresult Reconfigure(int32_t aWidth, int32_t aHeight,
|
||||
int32_t aDisplayWidth, int32_t aDisplayHeight);
|
||||
|
||||
// Destroys the context and image wrapper. Does not de-allocate the structs.
|
||||
void Destroy();
|
||||
|
||||
// Helper method to set the values on a VPX configuration.
|
||||
nsresult SetConfigurationValues(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
|
||||
int32_t aDisplayHeight, vpx_codec_enc_cfg_t& config);
|
||||
|
||||
// Encoded timestamp.
|
||||
StreamTime mEncodedTimestamp;
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
#ifndef JavaCallbacksSupport_h_
|
||||
#define JavaCallbacksSupport_h_
|
||||
|
||||
#include "FennecJNINatives.h"
|
||||
#include "GeneratedJNINatives.h"
|
||||
#include "MediaResult.h"
|
||||
#include "MediaCodec.h"
|
||||
|
||||
|
@ -821,6 +821,12 @@ tags=msg
|
||||
[test_mediarecorder_record_changing_video_resolution.html]
|
||||
skip-if = android_version == '17' # android(bug 1232305)
|
||||
tags=msg
|
||||
[test_mediarecorder_record_upsize_resolution.html]
|
||||
skip-if = android_version == '17' # android(bug 1232305)
|
||||
tags=msg
|
||||
[test_mediarecorder_record_downsize_resolution.html]
|
||||
skip-if = android_version == '17' # android(bug 1232305)
|
||||
tags=msg
|
||||
[test_mediarecorder_record_gum_video_timeslice.html]
|
||||
skip-if = android_version == '17' # bug 1297298, android(bug 1232305)
|
||||
tags=msg
|
||||
|
@ -15,53 +15,148 @@ function startTest() {
|
||||
var content = document.getElementById("content");
|
||||
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = canvas.height = 100;
|
||||
const resolution_change = [
|
||||
{width: 100, height: 100, color: "red"},
|
||||
{width: 150, height: 150, color: "blue"},
|
||||
{width: 100, height: 100, color: "red"},
|
||||
];
|
||||
canvas.width = resolution_change[0].width;
|
||||
canvas.height = resolution_change[0].height;
|
||||
|
||||
var ctx = canvas.getContext("2d");
|
||||
ctx.fillStyle = "red";
|
||||
ctx.fillStyle = resolution_change[0].color;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
var stream = canvas.captureStream(0);
|
||||
// The recorded stream coming from canvas.
|
||||
var stream = canvas.captureStream();
|
||||
|
||||
var numErrorRaised = 0;
|
||||
// Check values for events
|
||||
var numDataAvailabledRaised = 0;
|
||||
var numResizeRaised = 0;
|
||||
// Recorded data that will be playback.
|
||||
var blob;
|
||||
|
||||
mediaRecorder = new MediaRecorder(stream);
|
||||
// Media recorder for VP8 and canvas stream.
|
||||
var mediaRecorder = new MediaRecorder(stream);
|
||||
is(mediaRecorder.stream, stream,
|
||||
"Media recorder stream = canvas stream at the start of recording");
|
||||
|
||||
mediaRecorder.onwarning = () => ok(false, "onwarning unexpectedly fired");
|
||||
|
||||
// Not expected events.
|
||||
mediaRecorder.onwarning = () => ok(false, "MediaRecorder: onwarning unexpectedly fired");
|
||||
mediaRecorder.onerror = err => {
|
||||
info("Got 'error' event, " + err.name + " (" + err.message + ")");
|
||||
++numErrorRaised;
|
||||
ok(false, "MediaRecorder: onerror unexpectedly fired. Code " + err.name);
|
||||
SimpleTest.finish();
|
||||
};
|
||||
|
||||
// When recorder is stopped get recorded data.
|
||||
mediaRecorder.ondataavailable = ev => {
|
||||
info("Got 'dataavailable' event");
|
||||
++numDataAvailabledRaised;
|
||||
is(blob, undefined, "Should only get one dataavailable event");
|
||||
// Save recorded data for playback
|
||||
blob = ev.data;
|
||||
};
|
||||
|
||||
mediaRecorder.onstart = () => {
|
||||
canvas.width = canvas.height = canvas.width * 1.1;
|
||||
ctx.fillStyle = "blue";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
stream.requestFrame();
|
||||
info('onstart fired successfully');
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
info("Got 'stop' event");
|
||||
is(numErrorRaised, 1, "Should have gotten 1 error event");
|
||||
is(numDataAvailabledRaised, 1, "Should have gotten 1 dataavailable event");
|
||||
SimpleTest.finish();
|
||||
// Playback stream and verify resolution changes.
|
||||
ok(blob, "Should have gotten a data blob");
|
||||
|
||||
var video = document.createElement("video");
|
||||
video.id = "recorded-video";
|
||||
video.src = URL.createObjectURL(blob);
|
||||
video.preload = "metadata";
|
||||
|
||||
video.onerror = err => {
|
||||
ok(false, "Should be able to play the recording. Got error. code=" + video.error.code);
|
||||
SimpleTest.finish();
|
||||
};
|
||||
|
||||
video.onloadedmetadata = function(){
|
||||
info("onloadedmetadata");
|
||||
video.seekToNextFrame()
|
||||
.then(()=>video.seekToNextFrame())
|
||||
.catch((reason)=>{
|
||||
info("seekToNextFrame rejected: " + reason)
|
||||
callSeekToNextFrame();
|
||||
});
|
||||
}
|
||||
|
||||
// Check that resize is correct for playback stream.
|
||||
video.onresize = function() {
|
||||
if (numResizeRaised < resolution_change.length) {
|
||||
is(video.videoWidth, resolution_change[numResizeRaised].width,
|
||||
"onresize width should be as expected");
|
||||
is(video.videoHeight, resolution_change[numResizeRaised].height,
|
||||
"onresize height should be as expected");
|
||||
if (numResizeRaised > 0) {
|
||||
callSeekToNextFrame();
|
||||
}
|
||||
} else {
|
||||
ok(false, "Got more resize events than expected");
|
||||
}
|
||||
++numResizeRaised;
|
||||
};
|
||||
|
||||
video.onended = function() {
|
||||
is(numResizeRaised, resolution_change.length, "Expected number of resize events");
|
||||
SimpleTest.finish();
|
||||
};
|
||||
|
||||
document.getElementById("content").appendChild(video);
|
||||
|
||||
function callSeekToNextFrame() {
|
||||
video.seekToNextFrame()
|
||||
.then()
|
||||
.catch((reason)=>{
|
||||
info("seekToNextFrame rejected: " + reason)
|
||||
callSeekToNextFrame();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// Start here by stream recorder.
|
||||
mediaRecorder.start();
|
||||
is(mediaRecorder.state, "recording", "Media recorder should be recording");
|
||||
requestAnimationFrame(draw);
|
||||
|
||||
// Change resolution in every frame
|
||||
// Stop recorder on last frame
|
||||
var countFrames=0;
|
||||
var previous_time = performance.now();
|
||||
function draw(timestamp) {
|
||||
if (timestamp - previous_time < 100) {
|
||||
requestAnimationFrame(draw);
|
||||
return;
|
||||
}
|
||||
previous_time = timestamp;
|
||||
|
||||
if (countFrames < resolution_change.length) {
|
||||
canvas.width = resolution_change[countFrames].width;
|
||||
canvas.height = resolution_change[countFrames].height;
|
||||
ctx.fillStyle = resolution_change[countFrames].color;
|
||||
// Resize and draw canvas
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
// Register draw to be called on next rendering
|
||||
requestAnimationFrame(draw);
|
||||
countFrames++;
|
||||
} else {
|
||||
mediaRecorder.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
startTest();
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{
|
||||
"set": [["media.seekToNextFrame.enabled", true ]],
|
||||
"set": [["media.video-queue.send-to-compositor-size", 1]]
|
||||
}, startTest);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
|
@ -0,0 +1,146 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test MediaRecorder Recording canvas dynamically changes to greater resolution</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/dom/canvas/test/captureStream_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<div id="content">
|
||||
</div>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
function startTest() {
|
||||
var canvas = document.createElement("canvas");
|
||||
var canvas_size = 100;
|
||||
var new_canvas_size = 50;
|
||||
canvas.width = canvas.height = canvas_size;
|
||||
|
||||
var helper = new CaptureStreamTestHelper2D(canvas_size, canvas_size);
|
||||
helper.drawColor(canvas, helper.red);
|
||||
|
||||
// The recorded stream coming from canvas.
|
||||
var stream = canvas.captureStream();
|
||||
|
||||
// Check values for events
|
||||
var numDataAvailabledRaised = 0;
|
||||
var numResizeRaised = 0;
|
||||
// Recorded data that will be playback.
|
||||
var blob;
|
||||
|
||||
// Media recorder for VP8 and canvas stream.
|
||||
var mediaRecorder = new MediaRecorder(stream);
|
||||
is(mediaRecorder.stream, stream,
|
||||
"Media recorder stream = canvas stream at the beginning of recording");
|
||||
|
||||
// Not expected events.
|
||||
mediaRecorder.onwarning = () => ok(false, "MediaRecorder: onwarning unexpectedly fired");
|
||||
mediaRecorder.onerror = err => {
|
||||
ok(false, "MediaRecorder: onerror unexpectedly fired. Code " + err.name);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
// When recorder is stopped get recorded data.
|
||||
mediaRecorder.ondataavailable = ev => {
|
||||
info("Got 'dataavailable' event");
|
||||
++numDataAvailabledRaised;
|
||||
is(blob, undefined, "On dataavailable event blob is undefined");
|
||||
// Save recorded data for playback
|
||||
blob = ev.data;
|
||||
};
|
||||
|
||||
mediaRecorder.onstart = () => {
|
||||
info('onstart fired successfully');
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
info("Got 'stop' event");
|
||||
is(numDataAvailabledRaised, 1, "Expected 1 dataavailable event");
|
||||
|
||||
// Playback stream and verify resolution changes.
|
||||
ok(blob, "Should have gotten a data blob");
|
||||
var video = document.createElement("video");
|
||||
video.id = "recorded-video";
|
||||
video.src = URL.createObjectURL(blob);
|
||||
video.onerror = err => {
|
||||
ok(false, "Should be able to play the recording. Got error. code=" + video.error.code);
|
||||
SimpleTest.finish();
|
||||
};
|
||||
|
||||
// Check here that resize is correct in the playback stream.
|
||||
video.onresize = function() {
|
||||
++numResizeRaised;
|
||||
if (numResizeRaised == 1) {
|
||||
is(this.videoWidth, canvas_size, "1st resize event original width");
|
||||
is(this.videoHeight, canvas_size, "1st resize event original height ");
|
||||
} else if (numResizeRaised == 2) {
|
||||
is(this.videoWidth, new_canvas_size, "2nd resize event new width");
|
||||
is(this.videoHeight, new_canvas_size, "2nd resize event new height");
|
||||
} else {
|
||||
ok(false, "Only 2 numResizeRaised events are expected");
|
||||
}
|
||||
};
|
||||
|
||||
video.onended = () => {
|
||||
is(numResizeRaised, 2, "Expected 2 resize event");
|
||||
};
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.play();
|
||||
|
||||
// Check last color
|
||||
helper.waitForPixelColor(video, helper.red, 128, "Should become red")
|
||||
.then(() => {
|
||||
video.onresize = {};
|
||||
video.onended = {};
|
||||
SimpleTest.finish();
|
||||
});
|
||||
};
|
||||
|
||||
// Start here by stream recorder.
|
||||
mediaRecorder.start();
|
||||
is(mediaRecorder.state, "recording", "Media recorder started");
|
||||
requestAnimationFrame(draw);
|
||||
|
||||
// Change resolution every 100ms
|
||||
var countFrames=0;
|
||||
var previous_time = performance.now();
|
||||
function draw(timestamp) {
|
||||
if (timestamp - previous_time < 100) {
|
||||
requestAnimationFrame(draw);
|
||||
return;
|
||||
}
|
||||
previous_time = timestamp;
|
||||
|
||||
var size = 0;
|
||||
var color = "";
|
||||
if (countFrames < 1) {
|
||||
// Initial size
|
||||
size = canvas_size;
|
||||
color = helper.blue;
|
||||
} else if (countFrames < 2) {
|
||||
// upsize
|
||||
size = new_canvas_size;
|
||||
color = helper.red;
|
||||
} else {
|
||||
// Stop recoredr on last frame
|
||||
mediaRecorder.stop();
|
||||
return;
|
||||
}
|
||||
// Resize and draw canvas
|
||||
canvas.width = canvas.height = size;
|
||||
helper.drawColor(canvas, color);
|
||||
// Register next draw on every call
|
||||
requestAnimationFrame(draw);
|
||||
countFrames++;
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
startTest();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
146
dom/media/test/test_mediarecorder_record_upsize_resolution.html
Normal file
146
dom/media/test/test_mediarecorder_record_upsize_resolution.html
Normal file
@ -0,0 +1,146 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test MediaRecorder Recording canvas dynamically changes to greater resolution</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/dom/canvas/test/captureStream_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<div id="content">
|
||||
</div>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
function startTest() {
|
||||
var canvas = document.createElement("canvas");
|
||||
var canvas_size = 100;
|
||||
var new_canvas_size = 150;
|
||||
canvas.width = canvas.height = canvas_size;
|
||||
|
||||
var helper = new CaptureStreamTestHelper2D(canvas_size, canvas_size);
|
||||
helper.drawColor(canvas, helper.red);
|
||||
|
||||
// The recorded stream coming from canvas.
|
||||
var stream = canvas.captureStream();
|
||||
|
||||
// Check values for events
|
||||
var numDataAvailabledRaised = 0;
|
||||
var numResizeRaised = 0;
|
||||
// Recorded data that will be playback.
|
||||
var blob;
|
||||
|
||||
// Media recorder for VP8 and canvas stream.
|
||||
var mediaRecorder = new MediaRecorder(stream);
|
||||
is(mediaRecorder.stream, stream,
|
||||
"Media recorder stream = canvas stream at the beginning of recording");
|
||||
|
||||
// Not expected events.
|
||||
mediaRecorder.onwarning = () => ok(false, "MediaRecorder: onwarning unexpectedly fired");
|
||||
mediaRecorder.onerror = err => {
|
||||
ok(false, "MediaRecorder: onerror unexpectedly fired. Code " + err.name);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
// When recorder is stopped get recorded data.
|
||||
mediaRecorder.ondataavailable = ev => {
|
||||
info("Got 'dataavailable' event");
|
||||
++numDataAvailabledRaised;
|
||||
is(blob, undefined, "On dataavailable event blob is undefined");
|
||||
// Save recorded data for playback
|
||||
blob = ev.data;
|
||||
};
|
||||
|
||||
mediaRecorder.onstart = () => {
|
||||
info('onstart fired successfully');
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
info("Got 'stop' event");
|
||||
is(numDataAvailabledRaised, 1, "Expected 1 dataavailable event");
|
||||
|
||||
// Playback stream and verify resolution changes.
|
||||
ok(blob, "Should have gotten a data blob");
|
||||
var video = document.createElement("video");
|
||||
video.id = "recorded-video";
|
||||
video.src = URL.createObjectURL(blob);
|
||||
video.onerror = err => {
|
||||
ok(false, "Should be able to play the recording. Got error. code=" + video.error.code);
|
||||
SimpleTest.finish();
|
||||
};
|
||||
|
||||
// Check here that resize is correct in the playback stream.
|
||||
video.onresize = function() {
|
||||
++numResizeRaised;
|
||||
if (numResizeRaised == 1) {
|
||||
is(this.videoWidth, canvas_size, "1st resize event original width");
|
||||
is(this.videoHeight, canvas_size, "1st resize event original height ");
|
||||
} else if (numResizeRaised == 2) {
|
||||
is(this.videoWidth, new_canvas_size, "2nd resize event new width");
|
||||
is(this.videoHeight, new_canvas_size, "2nd resize event new height");
|
||||
} else {
|
||||
ok(false, "Only 2 numResizeRaised events are expected");
|
||||
}
|
||||
};
|
||||
|
||||
video.onended = () => {
|
||||
is(numResizeRaised, 2, "Expected 2 resize event");
|
||||
};
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.play();
|
||||
|
||||
// Check last color
|
||||
helper.waitForPixelColor(video, helper.red, 128, "Should become red")
|
||||
.then(() => {
|
||||
video.onresize = {};
|
||||
video.onended = {};
|
||||
SimpleTest.finish();
|
||||
});
|
||||
};
|
||||
|
||||
// Start here by stream recorder.
|
||||
mediaRecorder.start();
|
||||
is(mediaRecorder.state, "recording", "Media recorder started");
|
||||
requestAnimationFrame(draw);
|
||||
|
||||
// Change resolution every 100 ms
|
||||
var countFrames=0;
|
||||
var previous_time = performance.now();
|
||||
function draw(timestamp) {
|
||||
if (timestamp - previous_time < 100) {
|
||||
requestAnimationFrame(draw);
|
||||
return;
|
||||
}
|
||||
previous_time = timestamp;
|
||||
|
||||
var size = 0;
|
||||
var color = "";
|
||||
if (countFrames < 1) {
|
||||
// Initial size
|
||||
size = canvas_size;
|
||||
color = helper.blue;
|
||||
} else if (countFrames < 2) {
|
||||
// upsize
|
||||
size = new_canvas_size;
|
||||
color = helper.red;
|
||||
}else {
|
||||
// Stop recoredr on last frame
|
||||
mediaRecorder.stop();
|
||||
return;
|
||||
}
|
||||
// Resize and draw canvas
|
||||
canvas.width = canvas.height = size;
|
||||
helper.drawColor(canvas, color);
|
||||
// Register next draw on every call
|
||||
requestAnimationFrame(draw);
|
||||
countFrames++;
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
startTest();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -85,7 +85,7 @@ public:
|
||||
|
||||
RefPtr<TransferBuffer> transfer =
|
||||
new TransferBuffer(aStream, aInput.AsAudioChunk());
|
||||
NS_DispatchToMainThread(transfer);
|
||||
mAbstractMainThread->Dispatch(transfer.forget());
|
||||
}
|
||||
|
||||
virtual bool IsActive() const override
|
||||
|
@ -827,7 +827,7 @@ AudioBufferSourceNode::NotifyMainThreadStreamFinished()
|
||||
RefPtr<AudioBufferSourceNode> mNode;
|
||||
};
|
||||
|
||||
NS_DispatchToMainThread(new EndedEventDispatcher(this));
|
||||
Context()->Dispatch(do_AddRef(new EndedEventDispatcher(this)));
|
||||
|
||||
// Drop the playing reference
|
||||
// Warning: The below line might delete this.
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/OwningNonNull.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
#include "mozilla/dom/AnalyserNode.h"
|
||||
#include "mozilla/dom/AnalyserNodeBinding.h"
|
||||
@ -762,6 +763,24 @@ private:
|
||||
RefPtr<AudioContext> mAudioContext;
|
||||
};
|
||||
|
||||
|
||||
void
|
||||
AudioContext::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
nsCOMPtr<nsIGlobalObject> parentObject =
|
||||
do_QueryInterface(GetParentObject());
|
||||
// It can happen that this runnable took a long time to reach the main thread,
|
||||
// and the global is not valid anymore.
|
||||
if (parentObject) {
|
||||
parentObject->AbstractMainThreadFor(TaskCategory::Other)
|
||||
->Dispatch(std::move(aRunnable));
|
||||
} else {
|
||||
RefPtr<nsIRunnable> runnable(aRunnable);
|
||||
runnable = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState)
|
||||
{
|
||||
@ -826,9 +845,8 @@ AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState)
|
||||
}
|
||||
|
||||
if (mAudioContextState != aNewState) {
|
||||
RefPtr<OnStateChangeTask> onStateChangeTask =
|
||||
new OnStateChangeTask(this);
|
||||
NS_DispatchToMainThread(onStateChangeTask);
|
||||
RefPtr<OnStateChangeTask> task = new OnStateChangeTask(this);
|
||||
Dispatch(task.forget());
|
||||
}
|
||||
|
||||
mAudioContextState = aNewState;
|
||||
|
@ -328,6 +328,8 @@ public:
|
||||
|
||||
bool CheckClosed(ErrorResult& aRv);
|
||||
|
||||
void Dispatch(already_AddRefed<nsIRunnable>&& aRunnable);
|
||||
|
||||
private:
|
||||
void DisconnectFromWindow();
|
||||
void RemoveFromDecodeQueue(WebAudioDecodeJob* aDecodeJob);
|
||||
|
@ -167,9 +167,8 @@ public:
|
||||
|
||||
aNode->ResolvePromise(renderedBuffer);
|
||||
|
||||
RefPtr<OnCompleteTask> onCompleteTask =
|
||||
new OnCompleteTask(context, renderedBuffer);
|
||||
NS_DispatchToMainThread(onCompleteTask);
|
||||
mAbstractMainThread->Dispatch(do_AddRef(new OnCompleteTask(context,
|
||||
renderedBuffer)));
|
||||
|
||||
context->OnStateChanged(nullptr, AudioContextState::Closed);
|
||||
}
|
||||
|
@ -275,7 +275,7 @@ ConstantSourceNode::NotifyMainThreadStreamFinished()
|
||||
RefPtr<ConstantSourceNode> mNode;
|
||||
};
|
||||
|
||||
NS_DispatchToMainThread(new EndedEventDispatcher(this));
|
||||
Context()->Dispatch(do_AddRef(new EndedEventDispatcher(this)));
|
||||
|
||||
// Drop the playing reference
|
||||
// Warning: The below line might delete this.
|
||||
|
@ -169,7 +169,7 @@ private:
|
||||
float mReduction;
|
||||
};
|
||||
|
||||
NS_DispatchToMainThread(new Command(aStream, aReduction));
|
||||
mAbstractMainThread->Dispatch(do_AddRef(new Command(aStream, aReduction)));
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -104,11 +104,12 @@ private:
|
||||
mDecodeJob.OnFailure(aErrorCode);
|
||||
} else {
|
||||
// Take extra care to cleanup on the main thread
|
||||
NS_DispatchToMainThread(NewRunnableMethod(this, &MediaDecodeTask::Cleanup));
|
||||
mMainThread->Dispatch(NewRunnableMethod(this, &MediaDecodeTask::Cleanup));
|
||||
|
||||
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
new ReportResultTask(mDecodeJob, &WebAudioDecodeJob::OnFailure, aErrorCode);
|
||||
NS_DispatchToMainThread(event);
|
||||
mMainThread->Dispatch(event.forget());
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,6 +143,7 @@ private:
|
||||
RefPtr<MediaDecoderReader> mDecoderReader;
|
||||
MediaInfo mMediaInfo;
|
||||
MediaQueue<AudioData> mAudioQueue;
|
||||
RefPtr<AbstractThread> mMainThread;
|
||||
bool mFirstFrameDecoded;
|
||||
};
|
||||
|
||||
@ -201,9 +203,9 @@ MediaDecodeTask::CreateReader()
|
||||
mLength, principal, mContainerType);
|
||||
|
||||
MOZ_ASSERT(!mBufferDecoder);
|
||||
RefPtr<AbstractThread> mainThread =
|
||||
mMainThread =
|
||||
mDecodeJob.mContext->GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other);
|
||||
mBufferDecoder = new BufferDecoder(resource, mainThread,
|
||||
mBufferDecoder = new BufferDecoder(resource, mMainThread,
|
||||
new BufferDecoderGMPCrashHelper(parent));
|
||||
|
||||
// If you change this list to add support for new decoders, please consider
|
||||
@ -441,7 +443,7 @@ MediaDecodeTask::FinishDecode()
|
||||
}
|
||||
|
||||
mPhase = PhaseEnum::AllocateBuffer;
|
||||
NS_DispatchToMainThread(this);
|
||||
mMainThread->Dispatch(do_AddRef(this));
|
||||
}
|
||||
|
||||
void
|
||||
@ -499,7 +501,7 @@ AsyncDecodeWebAudio(const char* aContentType, uint8_t* aBuffer,
|
||||
&WebAudioDecodeJob::OnFailure,
|
||||
WebAudioDecodeJob::UnknownContent);
|
||||
JS_free(nullptr, aBuffer);
|
||||
NS_DispatchToMainThread(event);
|
||||
aDecodeJob.mContext->Dispatch(event.forget());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -510,7 +512,7 @@ AsyncDecodeWebAudio(const char* aContentType, uint8_t* aBuffer,
|
||||
new ReportResultTask(aDecodeJob,
|
||||
&WebAudioDecodeJob::OnFailure,
|
||||
WebAudioDecodeJob::UnknownError);
|
||||
NS_DispatchToMainThread(event);
|
||||
aDecodeJob.mContext->Dispatch(event.forget());
|
||||
} else {
|
||||
// If we did this without a temporary:
|
||||
// task->Reader()->OwnerThread()->Dispatch(task.forget())
|
||||
|
@ -598,7 +598,7 @@ OscillatorNode::NotifyMainThreadStreamFinished()
|
||||
RefPtr<OscillatorNode> mNode;
|
||||
};
|
||||
|
||||
NS_DispatchToMainThread(new EndedEventDispatcher(this));
|
||||
Context()->Dispatch(do_AddRef(new EndedEventDispatcher(this)));
|
||||
|
||||
// Drop the playing reference
|
||||
// Warning: The below line might delete this.
|
||||
|
@ -468,8 +468,9 @@ private:
|
||||
double mPlaybackTime;
|
||||
};
|
||||
|
||||
NS_DispatchToMainThread(new Command(aStream, mInputBuffer.forget(),
|
||||
playbackTime));
|
||||
RefPtr<Command> command = new Command(aStream, mInputBuffer.forget(),
|
||||
playbackTime);
|
||||
mAbstractMainThread->Dispatch(command.forget());
|
||||
}
|
||||
|
||||
friend class ScriptProcessorNode;
|
||||
|
@ -232,6 +232,12 @@ SVGFEConvolveMatrixElement::GetPrimitiveDescription(nsSVGFilterInstance* aInstan
|
||||
Size kernelUnitLength =
|
||||
GetKernelUnitLength(aInstance, &mNumberPairAttributes[KERNEL_UNIT_LENGTH]);
|
||||
|
||||
if (kernelUnitLength.width <= 0 || kernelUnitLength.height <= 0) {
|
||||
// According to spec, A negative or zero value is an error. See link below for details.
|
||||
// https://www.w3.org/TR/SVG/filters.html#feConvolveMatrixElementKernelUnitLengthAttribute
|
||||
return failureDescription;
|
||||
}
|
||||
|
||||
FilterPrimitiveDescription descr(PrimitiveType::ConvolveMatrix);
|
||||
AttributeMap& atts = descr.Attributes();
|
||||
atts.Set(eConvolveMatrixKernelSize, IntSize(orderX, orderY));
|
||||
|
@ -596,9 +596,9 @@ var interfaceNamesInGlobalScope =
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"InstallTrigger",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"IntersectionObserver",
|
||||
{name: "IntersectionObserver", disabled: true},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"IntersectionObserverEntry",
|
||||
{name: "IntersectionObserverEntry", disabled: true},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"KeyEvent",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
|
43
gfx/2d/2D.h
43
gfx/2d/2D.h
@ -25,6 +25,7 @@
|
||||
// outparams using the &-operator. But it will have to do as there's no easy
|
||||
// solution.
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/WeakPtr.h"
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
|
||||
@ -696,6 +697,27 @@ struct GlyphMetrics
|
||||
Float mHeight;
|
||||
};
|
||||
|
||||
class UnscaledFont
|
||||
: public external::AtomicRefCounted<UnscaledFont>
|
||||
, public SupportsWeakPtr<UnscaledFont>
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFont)
|
||||
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(UnscaledFont)
|
||||
|
||||
virtual ~UnscaledFont();
|
||||
|
||||
virtual FontType GetType() const = 0;
|
||||
|
||||
static uint32_t DeletionCounter() { return sDeletionCounter; }
|
||||
|
||||
protected:
|
||||
UnscaledFont() {}
|
||||
|
||||
private:
|
||||
static uint32_t sDeletionCounter;
|
||||
};
|
||||
|
||||
/** This class is an abstraction of a backend/platform specific font object
|
||||
* at a particular size. It is passed into text drawing calls to describe
|
||||
* the font used for the drawing call.
|
||||
@ -718,6 +740,7 @@ public:
|
||||
typedef void (*FontDescriptorOutput)(const uint8_t* aData, uint32_t aLength, Float aFontSize, void* aBaton);
|
||||
|
||||
virtual FontType GetType() const = 0;
|
||||
virtual Float GetSize() const = 0;
|
||||
virtual AntialiasMode GetDefaultAAMode();
|
||||
|
||||
/** This allows getting a path that describes the outline of a set of glyphs.
|
||||
@ -753,10 +776,15 @@ public:
|
||||
return mUserData.Get(key);
|
||||
}
|
||||
|
||||
const RefPtr<UnscaledFont>& GetUnscaledFont() const { return mUnscaledFont; }
|
||||
|
||||
protected:
|
||||
ScaledFont() {}
|
||||
explicit ScaledFont(const RefPtr<UnscaledFont>& aUnscaledFont)
|
||||
: mUnscaledFont(aUnscaledFont)
|
||||
{}
|
||||
|
||||
UserData mUserData;
|
||||
RefPtr<UnscaledFont> mUnscaledFont;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1426,11 +1454,14 @@ public:
|
||||
CreateDrawTargetForData(BackendType aBackend, unsigned char* aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat, bool aUninitialized = false);
|
||||
|
||||
static already_AddRefed<ScaledFont>
|
||||
CreateScaledFontForNativeFont(const NativeFont &aNativeFont, Float aSize);
|
||||
CreateScaledFontForNativeFont(const NativeFont &aNativeFont,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize);
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
static already_AddRefed<ScaledFont>
|
||||
CreateScaledFontForFontconfigFont(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern, Float aSize);
|
||||
CreateScaledFontForFontconfigFont(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize);
|
||||
#endif
|
||||
|
||||
/**
|
||||
@ -1462,7 +1493,10 @@ public:
|
||||
* cairo_scaled_font_t* parameters must correspond to the same font.
|
||||
*/
|
||||
static already_AddRefed<ScaledFont>
|
||||
CreateScaledFontWithCairo(const NativeFont &aNativeFont, Float aSize, cairo_scaled_font_t* aScaledFont);
|
||||
CreateScaledFontWithCairo(const NativeFont &aNativeFont,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize,
|
||||
cairo_scaled_font_t* aScaledFont);
|
||||
|
||||
/**
|
||||
* This creates a simple data source surface for a certain size. It allocates
|
||||
@ -1588,6 +1622,7 @@ public:
|
||||
static already_AddRefed<ScaledFont>
|
||||
CreateScaledFontForDWriteFont(IDWriteFontFace* aFontFace,
|
||||
const gfxFontStyle* aStyle,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize,
|
||||
bool aUseEmbeddedBitmap,
|
||||
bool aForceGDIMode);
|
||||
|
@ -469,31 +469,33 @@ Factory::GetMaxSurfaceSize(BackendType aType)
|
||||
}
|
||||
|
||||
already_AddRefed<ScaledFont>
|
||||
Factory::CreateScaledFontForNativeFont(const NativeFont &aNativeFont, Float aSize)
|
||||
Factory::CreateScaledFontForNativeFont(const NativeFont &aNativeFont,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize)
|
||||
{
|
||||
switch (aNativeFont.mType) {
|
||||
#ifdef WIN32
|
||||
case NativeFontType::DWRITE_FONT_FACE:
|
||||
{
|
||||
return MakeAndAddRef<ScaledFontDWrite>(static_cast<IDWriteFontFace*>(aNativeFont.mFont), aSize);
|
||||
return MakeAndAddRef<ScaledFontDWrite>(static_cast<IDWriteFontFace*>(aNativeFont.mFont), aUnscaledFont, aSize);
|
||||
}
|
||||
#if defined(USE_CAIRO) || defined(USE_SKIA)
|
||||
case NativeFontType::GDI_FONT_FACE:
|
||||
{
|
||||
return MakeAndAddRef<ScaledFontWin>(static_cast<LOGFONT*>(aNativeFont.mFont), aSize);
|
||||
return MakeAndAddRef<ScaledFontWin>(static_cast<LOGFONT*>(aNativeFont.mFont), aUnscaledFont, aSize);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#ifdef XP_DARWIN
|
||||
case NativeFontType::MAC_FONT_FACE:
|
||||
{
|
||||
return MakeAndAddRef<ScaledFontMac>(static_cast<CGFontRef>(aNativeFont.mFont), aSize);
|
||||
return MakeAndAddRef<ScaledFontMac>(static_cast<CGFontRef>(aNativeFont.mFont), aUnscaledFont, aSize);
|
||||
}
|
||||
#endif
|
||||
#if defined(USE_CAIRO) || defined(USE_SKIA_FREETYPE)
|
||||
case NativeFontType::CAIRO_FONT_FACE:
|
||||
{
|
||||
return MakeAndAddRef<ScaledFontCairo>(static_cast<cairo_scaled_font_t*>(aNativeFont.mFont), aSize);
|
||||
return MakeAndAddRef<ScaledFontCairo>(static_cast<cairo_scaled_font_t*>(aNativeFont.mFont), aUnscaledFont, aSize);
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
@ -564,14 +566,17 @@ Factory::CreateScaledFontFromFontDescriptor(FontType aType, const uint8_t* aData
|
||||
}
|
||||
|
||||
already_AddRefed<ScaledFont>
|
||||
Factory::CreateScaledFontWithCairo(const NativeFont& aNativeFont, Float aSize, cairo_scaled_font_t* aScaledFont)
|
||||
Factory::CreateScaledFontWithCairo(const NativeFont& aNativeFont,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize,
|
||||
cairo_scaled_font_t* aScaledFont)
|
||||
{
|
||||
#ifdef USE_CAIRO
|
||||
// In theory, we could pull the NativeFont out of the cairo_scaled_font_t*,
|
||||
// but that would require a lot of code that would be otherwise repeated in
|
||||
// various backends.
|
||||
// Therefore, we just reuse CreateScaledFontForNativeFont's implementation.
|
||||
RefPtr<ScaledFont> font = CreateScaledFontForNativeFont(aNativeFont, aSize);
|
||||
RefPtr<ScaledFont> font = CreateScaledFontForNativeFont(aNativeFont, aUnscaledFont, aSize);
|
||||
static_cast<ScaledFontBase*>(font.get())->SetCairoScaledFont(aScaledFont);
|
||||
return font.forget();
|
||||
#else
|
||||
@ -581,9 +586,10 @@ Factory::CreateScaledFontWithCairo(const NativeFont& aNativeFont, Float aSize, c
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
already_AddRefed<ScaledFont>
|
||||
Factory::CreateScaledFontForFontconfigFont(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern, Float aSize)
|
||||
Factory::CreateScaledFontForFontconfigFont(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize)
|
||||
{
|
||||
return MakeAndAddRef<ScaledFontFontconfig>(aScaledFont, aPattern, aSize);
|
||||
return MakeAndAddRef<ScaledFontFontconfig>(aScaledFont, aPattern, aUnscaledFont, aSize);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -745,11 +751,12 @@ Factory::D2DCleanup()
|
||||
already_AddRefed<ScaledFont>
|
||||
Factory::CreateScaledFontForDWriteFont(IDWriteFontFace* aFontFace,
|
||||
const gfxFontStyle* aStyle,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
float aSize,
|
||||
bool aUseEmbeddedBitmap,
|
||||
bool aForceGDIMode)
|
||||
{
|
||||
return MakeAndAddRef<ScaledFontDWrite>(aFontFace, aSize,
|
||||
return MakeAndAddRef<ScaledFontDWrite>(aFontFace, aUnscaledFont, aSize,
|
||||
aUseEmbeddedBitmap, aForceGDIMode,
|
||||
aStyle);
|
||||
}
|
||||
|
@ -275,7 +275,7 @@ NativeFontResourceDWrite::CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<ScaledFontBase> scaledFont = new ScaledFontDWrite(fontFace, aGlyphSize);
|
||||
RefPtr<ScaledFontBase> scaledFont = new ScaledFontDWrite(fontFace, nullptr, aGlyphSize);
|
||||
if (mNeedsCairo && !scaledFont->PopulateCairoScaledFont()) {
|
||||
gfxWarning() << "Unable to create cairo scaled font DWrite font.";
|
||||
return nullptr;
|
||||
|
@ -50,7 +50,7 @@ NativeFontResourceGDI::CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
|
||||
|
||||
// Constructor for ScaledFontWin dereferences and copies the LOGFONT, so we
|
||||
// are safe to pass this reference.
|
||||
RefPtr<ScaledFontBase> scaledFont = new ScaledFontWin(logFont, aGlyphSize);
|
||||
RefPtr<ScaledFontBase> scaledFont = new ScaledFontWin(logFont, nullptr, aGlyphSize);
|
||||
if (mNeedsCairo && !scaledFont->PopulateCairoScaledFont()) {
|
||||
gfxWarning() << "Unable to create cairo scaled font GDI font.";
|
||||
return nullptr;
|
||||
|
@ -209,7 +209,7 @@ already_AddRefed<ScaledFont>
|
||||
NativeFontResourceMac::CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
|
||||
const uint8_t* aInstanceData, uint32_t aInstanceDataLength)
|
||||
{
|
||||
RefPtr<ScaledFontBase> scaledFont = new ScaledFontMac(mFontRef, aGlyphSize);
|
||||
RefPtr<ScaledFontBase> scaledFont = new ScaledFontMac(mFontRef, nullptr, aGlyphSize);
|
||||
|
||||
if (!scaledFont->PopulateCairoScaledFont()) {
|
||||
gfxWarning() << "Unable to create cairo scaled Mac font.";
|
||||
|
@ -26,6 +26,13 @@ using namespace std;
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
uint32_t UnscaledFont::sDeletionCounter = 0;
|
||||
|
||||
UnscaledFont::~UnscaledFont()
|
||||
{
|
||||
sDeletionCounter++;
|
||||
}
|
||||
|
||||
AntialiasMode
|
||||
ScaledFont::GetDefaultAAMode()
|
||||
{
|
||||
@ -46,8 +53,10 @@ ScaledFontBase::~ScaledFontBase()
|
||||
#endif
|
||||
}
|
||||
|
||||
ScaledFontBase::ScaledFontBase(Float aSize)
|
||||
: mSize(aSize)
|
||||
ScaledFontBase::ScaledFontBase(const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize)
|
||||
: ScaledFont(aUnscaledFont)
|
||||
, mSize(aSize)
|
||||
{
|
||||
#ifdef USE_SKIA
|
||||
mTypeface = nullptr;
|
||||
|
@ -28,7 +28,7 @@ class ScaledFontBase : public ScaledFont
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontBase)
|
||||
explicit ScaledFontBase(Float aSize);
|
||||
ScaledFontBase(const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize);
|
||||
virtual ~ScaledFontBase();
|
||||
|
||||
virtual already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget);
|
||||
@ -37,7 +37,7 @@ public:
|
||||
|
||||
virtual void GetGlyphDesignMetrics(const uint16_t* aGlyphIndices, uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics);
|
||||
|
||||
float GetSize() { return mSize; }
|
||||
virtual Float GetSize() const { return mSize; }
|
||||
|
||||
#ifdef USE_SKIA
|
||||
virtual SkTypeface* GetSkTypeface() { return mTypeface; }
|
||||
|
@ -17,9 +17,11 @@ namespace gfx {
|
||||
// an SkFontHost implementation that allows Skia to render using this.
|
||||
// This is mainly because FT_Face is not good for sharing between libraries, which
|
||||
// is a requirement when we consider runtime switchable backends and so on
|
||||
ScaledFontCairo::ScaledFontCairo(cairo_scaled_font_t* aScaledFont, Float aSize)
|
||||
: ScaledFontBase(aSize)
|
||||
{
|
||||
ScaledFontCairo::ScaledFontCairo(cairo_scaled_font_t* aScaledFont,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize)
|
||||
: ScaledFontBase(aUnscaledFont, aSize)
|
||||
{
|
||||
SetCairoScaledFont(aScaledFont);
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,9 @@ class ScaledFontCairo : public ScaledFontBase
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontCairo)
|
||||
|
||||
ScaledFontCairo(cairo_scaled_font_t* aScaledFont, Float aSize);
|
||||
ScaledFontCairo(cairo_scaled_font_t* aScaledFont,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize);
|
||||
|
||||
#if defined(USE_SKIA) && defined(MOZ_ENABLE_FREETYPE)
|
||||
virtual SkTypeface* GetSkTypeface();
|
||||
|
@ -102,10 +102,13 @@ DWriteFontStretchFromStretch(int16_t aStretch)
|
||||
}
|
||||
}
|
||||
|
||||
ScaledFontDWrite::ScaledFontDWrite(IDWriteFontFace *aFontFace, Float aSize,
|
||||
bool aUseEmbeddedBitmap, bool aForceGDIMode,
|
||||
ScaledFontDWrite::ScaledFontDWrite(IDWriteFontFace *aFontFace,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize,
|
||||
bool aUseEmbeddedBitmap,
|
||||
bool aForceGDIMode,
|
||||
const gfxFontStyle* aStyle)
|
||||
: ScaledFontBase(aSize)
|
||||
: ScaledFontBase(aUnscaledFont, aSize)
|
||||
, mFontFace(aFontFace)
|
||||
, mUseEmbeddedBitmap(aUseEmbeddedBitmap)
|
||||
, mForceGDIMode(aForceGDIMode)
|
||||
|
@ -19,15 +19,21 @@ class ScaledFontDWrite final : public ScaledFontBase
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontDwrite)
|
||||
ScaledFontDWrite(IDWriteFontFace *aFont, Float aSize)
|
||||
: ScaledFontBase(aSize)
|
||||
ScaledFontDWrite(IDWriteFontFace *aFont,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize)
|
||||
: ScaledFontBase(aUnscaledFont, aSize)
|
||||
, mFontFace(aFont)
|
||||
, mUseEmbeddedBitmap(false)
|
||||
, mForceGDIMode(false)
|
||||
{}
|
||||
|
||||
ScaledFontDWrite(IDWriteFontFace *aFontFace, Float aSize, bool aUseEmbeddedBitmap,
|
||||
bool aForceGDIMode, const gfxFontStyle* aStyle);
|
||||
ScaledFontDWrite(IDWriteFontFace *aFontFace,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize,
|
||||
bool aUseEmbeddedBitmap,
|
||||
bool aForceGDIMode,
|
||||
const gfxFontStyle* aStyle);
|
||||
|
||||
virtual FontType GetType() const { return FontType::DWRITE; }
|
||||
|
||||
|
@ -23,8 +23,9 @@ namespace gfx {
|
||||
// is a requirement when we consider runtime switchable backends and so on
|
||||
ScaledFontFontconfig::ScaledFontFontconfig(cairo_scaled_font_t* aScaledFont,
|
||||
FcPattern* aPattern,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize)
|
||||
: ScaledFontBase(aSize),
|
||||
: ScaledFontBase(aUnscaledFont, aSize),
|
||||
mPattern(aPattern)
|
||||
{
|
||||
SetCairoScaledFont(aScaledFont);
|
||||
@ -331,7 +332,7 @@ ScaledFontFontconfig::CreateFromInstanceData(const InstanceData& aInstanceData,
|
||||
}
|
||||
|
||||
RefPtr<ScaledFontFontconfig> scaledFont =
|
||||
new ScaledFontFontconfig(cairoScaledFont, pattern, aSize);
|
||||
new ScaledFontFontconfig(cairoScaledFont, pattern, nullptr, aSize);
|
||||
|
||||
FcPatternDestroy(pattern);
|
||||
|
||||
|
@ -19,7 +19,8 @@ class ScaledFontFontconfig : public ScaledFontBase
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontFontconfig, override)
|
||||
ScaledFontFontconfig(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern, Float aSize);
|
||||
ScaledFontFontconfig(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize);
|
||||
~ScaledFontFontconfig();
|
||||
|
||||
FontType GetType() const override { return FontType::FONTCONFIG; }
|
||||
@ -28,6 +29,8 @@ public:
|
||||
SkTypeface* GetSkTypeface() override;
|
||||
#endif
|
||||
|
||||
bool CanSerialize() override { return true; }
|
||||
|
||||
bool GetFontFileData(FontFileDataOutput aDataCallback, void* aBaton) override;
|
||||
|
||||
bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override;
|
||||
|
@ -67,8 +67,10 @@ CreateCTFontFromCGFontWithVariations(CGFontRef aCGFont, CGFloat aSize)
|
||||
return ctFont;
|
||||
}
|
||||
|
||||
ScaledFontMac::ScaledFontMac(CGFontRef aFont, Float aSize)
|
||||
: ScaledFontBase(aSize)
|
||||
ScaledFontMac::ScaledFontMac(CGFontRef aFont,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize)
|
||||
: ScaledFontBase(aUnscaledFont, aSize)
|
||||
{
|
||||
if (!sSymbolLookupDone) {
|
||||
CTFontDrawGlyphsPtr =
|
||||
|
@ -41,7 +41,7 @@ class ScaledFontMac : public ScaledFontBase
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontMac)
|
||||
ScaledFontMac(CGFontRef aFont, Float aSize);
|
||||
ScaledFontMac(CGFontRef aFont, const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize);
|
||||
virtual ~ScaledFontMac();
|
||||
|
||||
virtual FontType GetType() const { return FontType::MAC; }
|
||||
|
@ -22,8 +22,10 @@
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
ScaledFontWin::ScaledFontWin(const LOGFONT* aFont, Float aSize)
|
||||
: ScaledFontBase(aSize)
|
||||
ScaledFontWin::ScaledFontWin(const LOGFONT* aFont,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize)
|
||||
: ScaledFontBase(aUnscaledFont, aSize)
|
||||
, mLogFont(*aFont)
|
||||
{
|
||||
}
|
||||
@ -80,7 +82,7 @@ ScaledFontWin::CreateFromFontDescriptor(const uint8_t* aData, uint32_t aDataLeng
|
||||
nativeFont.mFont = (void*)aData;
|
||||
|
||||
RefPtr<ScaledFont> font =
|
||||
Factory::CreateScaledFontForNativeFont(nativeFont, aSize);
|
||||
Factory::CreateScaledFontForNativeFont(nativeFont, nullptr, aSize);
|
||||
|
||||
#ifdef USE_CAIRO_SCALED_FONT
|
||||
static_cast<ScaledFontBase*>(font.get())->PopulateCairoScaledFont();
|
||||
|
@ -16,7 +16,9 @@ class ScaledFontWin : public ScaledFontBase
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontWin)
|
||||
ScaledFontWin(const LOGFONT* aFont, Float aSize);
|
||||
ScaledFontWin(const LOGFONT* aFont,
|
||||
const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
Float aSize);
|
||||
|
||||
virtual FontType GetType() const { return FontType::GDI; }
|
||||
|
||||
|
@ -150,7 +150,8 @@ enum class FontType : int8_t {
|
||||
SKIA,
|
||||
CAIRO,
|
||||
COREGRAPHICS,
|
||||
FONTCONFIG
|
||||
FONTCONFIG,
|
||||
FREETYPE
|
||||
};
|
||||
|
||||
enum class NativeSurfaceType : int8_t {
|
||||
|
41
gfx/2d/UnscaledFontDWrite.h
Normal file
41
gfx/2d/UnscaledFontDWrite.h
Normal file
@ -0,0 +1,41 @@
|
||||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZILLA_GFX_UNSCALEDFONTDWRITE_H_
|
||||
#define MOZILLA_GFX_UNSCALEDFONTDWRITE_H_
|
||||
|
||||
#include <dwrite.h>
|
||||
|
||||
#include "2D.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class UnscaledFontDWrite final : public UnscaledFont
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontDWrite, override)
|
||||
explicit UnscaledFontDWrite(const RefPtr<IDWriteFontFace>& aFontFace,
|
||||
DWRITE_FONT_SIMULATIONS aSimulations =
|
||||
DWRITE_FONT_SIMULATIONS_NONE)
|
||||
: mFontFace(aFontFace),
|
||||
mSimulations(aSimulations)
|
||||
{}
|
||||
|
||||
FontType GetType() const override { return FontType::DWRITE; }
|
||||
|
||||
const RefPtr<IDWriteFontFace> GetFontFace() const { return mFontFace; }
|
||||
DWRITE_FONT_SIMULATIONS GetSimulations() const { return mSimulations; }
|
||||
|
||||
private:
|
||||
RefPtr<IDWriteFontFace> mFontFace;
|
||||
DWRITE_FONT_SIMULATIONS mSimulations;
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* MOZILLA_GFX_UNSCALEDFONTDWRITE_H_ */
|
||||
|
64
gfx/2d/UnscaledFontFreeType.h
Normal file
64
gfx/2d/UnscaledFontFreeType.h
Normal file
@ -0,0 +1,64 @@
|
||||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZILLA_GFX_UNSCALEDFONTFREETYPE_H_
|
||||
#define MOZILLA_GFX_UNSCALEDFONTFREETYPE_H_
|
||||
|
||||
#include <cairo-ft.h>
|
||||
|
||||
#include "2D.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class UnscaledFontFreeType : public UnscaledFont
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontFreeType, override)
|
||||
explicit UnscaledFontFreeType(FT_Face aFace)
|
||||
: mFace(aFace)
|
||||
, mIndex(0)
|
||||
{}
|
||||
explicit UnscaledFontFreeType(const char* aFile,
|
||||
uint32_t aIndex = 0)
|
||||
: mFace(nullptr)
|
||||
, mFile(aFile)
|
||||
, mIndex(aIndex)
|
||||
{}
|
||||
|
||||
FontType GetType() const override { return FontType::FREETYPE; }
|
||||
|
||||
FT_Face GetFace() const { return mFace; }
|
||||
const char* GetFile() const { return mFile.c_str(); }
|
||||
uint32_t GetIndex() const { return mIndex; }
|
||||
|
||||
private:
|
||||
FT_Face mFace;
|
||||
std::string mFile;
|
||||
uint32_t mIndex;
|
||||
};
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
class UnscaledFontFontconfig : public UnscaledFontFreeType
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontFontconfig, override)
|
||||
explicit UnscaledFontFontconfig(FT_Face aFace)
|
||||
: UnscaledFontFreeType(aFace)
|
||||
{}
|
||||
explicit UnscaledFontFontconfig(const char* aFile,
|
||||
uint32_t aIndex = 0)
|
||||
: UnscaledFontFreeType(aFile, aIndex)
|
||||
{}
|
||||
|
||||
FontType GetType() const override { return FontType::FONTCONFIG; }
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* MOZILLA_GFX_UNSCALEDFONTFREETYPE_H_ */
|
||||
|
35
gfx/2d/UnscaledFontGDI.h
Normal file
35
gfx/2d/UnscaledFontGDI.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZILLA_GFX_UNSCALEDFONTGDI_H_
|
||||
#define MOZILLA_GFX_UNSCALEDFONTGDI_H_
|
||||
|
||||
#include "2D.h"
|
||||
#include <windows.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class UnscaledFontGDI final : public UnscaledFont
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontGDI, override)
|
||||
explicit UnscaledFontGDI(const LOGFONT& aLogFont)
|
||||
: mLogFont(aLogFont)
|
||||
{}
|
||||
|
||||
FontType GetType() const override { return FontType::GDI; }
|
||||
|
||||
const LOGFONT& GetLogFont() const { return mLogFont; }
|
||||
|
||||
private:
|
||||
LOGFONT mLogFont;
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* MOZILLA_GFX_UNSCALEDFONTGDI_H_ */
|
||||
|
47
gfx/2d/UnscaledFontMac.h
Normal file
47
gfx/2d/UnscaledFontMac.h
Normal file
@ -0,0 +1,47 @@
|
||||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZILLA_GFX_UNSCALEDFONTMAC_H_
|
||||
#define MOZILLA_GFX_UNSCALEDFONTMAC_H_
|
||||
|
||||
#ifdef MOZ_WIDGET_COCOA
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#else
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <CoreText/CoreText.h>
|
||||
#endif
|
||||
|
||||
#include "2D.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class UnscaledFontMac final : public UnscaledFont
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontMac, override)
|
||||
explicit UnscaledFontMac(CGFontRef aFont)
|
||||
: mFont(aFont)
|
||||
{
|
||||
CFRetain(mFont);
|
||||
}
|
||||
~UnscaledFontMac()
|
||||
{
|
||||
CFRelease(mFont);
|
||||
}
|
||||
|
||||
FontType GetType() const override { return FontType::MAC; }
|
||||
|
||||
CGFontRef GetFont() const { return mFont; }
|
||||
|
||||
private:
|
||||
CGFontRef mFont;
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* MOZILLA_GFX_UNSCALEDFONTMAC_H_ */
|
||||
|
@ -65,12 +65,17 @@ EXPORTS.mozilla.gfx += ['ssse3-scaler.h']
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'):
|
||||
EXPORTS.mozilla.gfx += [
|
||||
'MacIOSurface.h',
|
||||
'UnscaledFontMac.h',
|
||||
]
|
||||
UNIFIED_SOURCES += [
|
||||
'NativeFontResourceMac.cpp',
|
||||
'ScaledFontMac.cpp',
|
||||
]
|
||||
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
||||
EXPORTS.mozilla.gfx += [
|
||||
'UnscaledFontDWrite.h',
|
||||
'UnscaledFontGDI.h',
|
||||
]
|
||||
SOURCES += [
|
||||
'DrawTargetD2D1.cpp',
|
||||
'ExtendInputEffectD2D1.cpp',
|
||||
@ -91,6 +96,11 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'windows':
|
||||
'JobScheduler_posix.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gtk2', 'gtk3'):
|
||||
EXPORTS.mozilla.gfx += [
|
||||
'UnscaledFontFreeType.h',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
|
||||
SOURCES += [
|
||||
'NativeFontResourceFontconfig.cpp',
|
||||
|
@ -1368,14 +1368,6 @@ nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
|
||||
MOZ_ASSERT(mFrameMetrics.IsRootContent());
|
||||
MOZ_ASSERT(mFrameMetrics.GetZoom().AreScalesSame());
|
||||
|
||||
float prevSpan = aEvent.mPreviousSpan;
|
||||
if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) {
|
||||
// We're still handling it; we've just decided to throw this event away.
|
||||
return nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
|
||||
float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
|
||||
|
||||
{
|
||||
ReentrantMonitorAutoEnter lock(mMonitor);
|
||||
|
||||
@ -1384,12 +1376,29 @@ nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
|
||||
CSSPoint cssFocusPoint = focusPoint / mFrameMetrics.GetZoom();
|
||||
|
||||
ParentLayerPoint focusChange = mLastZoomFocus - focusPoint;
|
||||
mLastZoomFocus = focusPoint;
|
||||
// If displacing by the change in focus point will take us off page bounds,
|
||||
// then reduce the displacement such that it doesn't.
|
||||
focusChange.x -= mX.DisplacementWillOverscrollAmount(focusChange.x);
|
||||
focusChange.y -= mY.DisplacementWillOverscrollAmount(focusChange.y);
|
||||
ScrollBy(focusChange / userZoom);
|
||||
|
||||
// If the span is zero or close to it, we don't want to process this zoom
|
||||
// change because we're going to get wonky numbers for the spanRatio. So
|
||||
// let's bail out here. Note that we do this after the focus-change-scroll
|
||||
// above, so that if we have a pinch with zero span but changing focus,
|
||||
// such as generated by some Synaptics touchpads on Windows, we still
|
||||
// scroll properly.
|
||||
float prevSpan = aEvent.mPreviousSpan;
|
||||
if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) {
|
||||
// We might have done a nonzero ScrollBy above, so update metrics and
|
||||
// repaint/recomposite
|
||||
ScheduleCompositeAndMaybeRepaint();
|
||||
UpdateSharedCompositorFrameMetrics();
|
||||
return nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
|
||||
|
||||
// When we zoom in with focus, we can zoom too much towards the boundaries
|
||||
// that we actually go over them. These are the needed displacements along
|
||||
// either axis such that we don't overscroll the boundaries when zooming.
|
||||
@ -1448,8 +1457,6 @@ nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
|
||||
// We did a ScrollBy call above even if we didn't do a scale, so we
|
||||
// should composite for that.
|
||||
ScheduleComposite();
|
||||
|
||||
mLastZoomFocus = focusPoint;
|
||||
}
|
||||
|
||||
return nsEventStatus_eConsumeNoDefault;
|
||||
|
@ -237,3 +237,58 @@ TEST_F(APZCPinchGestureDetectorTester, Pinch_APZZoom_Disabled) {
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
||||
TEST_F(APZCPinchGestureDetectorTester, Pinch_NoSpan) {
|
||||
SCOPED_GFX_PREF(APZAllowZooming, bool, false);
|
||||
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
|
||||
|
||||
FrameMetrics originalMetrics = GetPinchableFrameMetrics();
|
||||
apzc->SetFrameMetrics(originalMetrics);
|
||||
|
||||
// When APZAllowZooming is false, the ZoomConstraintsClient produces
|
||||
// ZoomConstraints with mAllowZoom set to false.
|
||||
MakeApzcUnzoomable();
|
||||
|
||||
// With APZAllowZooming false, we expect the NotifyPinchGesture function to
|
||||
// get called as the pinch progresses, but the metrics shouldn't change.
|
||||
EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_START, apzc->GetGuid(), LayoutDeviceCoord(0), _)).Times(1);
|
||||
EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_SCALE, apzc->GetGuid(), _, _)).Times(AtLeast(1));
|
||||
EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_END, apzc->GetGuid(), LayoutDeviceCoord(0), _)).Times(1);
|
||||
|
||||
int inputId = 0;
|
||||
ScreenIntPoint focus(250, 300);
|
||||
|
||||
// Do a pinch holding a zero span and moving the focus by y=100
|
||||
|
||||
MultiTouchInput mtiStart = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
|
||||
mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, focus));
|
||||
mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus));
|
||||
apzc->ReceiveInputEvent(mtiStart, nullptr);
|
||||
|
||||
focus.y -= 35 + 1; // this is to get over the PINCH_START_THRESHOLD in GestureEventListener.cpp
|
||||
MultiTouchInput mtiMove1 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, focus));
|
||||
mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus));
|
||||
apzc->ReceiveInputEvent(mtiMove1, nullptr);
|
||||
|
||||
focus.y -= 100; // do a two-finger scroll of 100 screen pixels
|
||||
MultiTouchInput mtiMove2 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
|
||||
mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, focus));
|
||||
mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus));
|
||||
apzc->ReceiveInputEvent(mtiMove2, nullptr);
|
||||
|
||||
MultiTouchInput mtiEnd = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
|
||||
mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, focus));
|
||||
mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus));
|
||||
apzc->ReceiveInputEvent(mtiEnd, nullptr);
|
||||
|
||||
// Done, check the metrics to make sure we scrolled by 100 screen pixels,
|
||||
// which is 50 CSS pixels for the pinchable frame metrics.
|
||||
|
||||
FrameMetrics fm = apzc->GetFrameMetrics();
|
||||
EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom());
|
||||
EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x);
|
||||
EXPECT_EQ(originalMetrics.GetScrollOffset().y + 50, fm.GetScrollOffset().y);
|
||||
|
||||
apzc->AssertStateIsReset();
|
||||
}
|
||||
|
@ -180,8 +180,10 @@ TextureClientRecycleAllocator::CreateOrRecycle(ITextureClientAllocationHelper& a
|
||||
if (!mPooledClients.empty()) {
|
||||
textureHolder = mPooledClients.top();
|
||||
mPooledClients.pop();
|
||||
// If a pooled TextureClient is not compatible, release it.
|
||||
if (!aHelper.IsCompatible(textureHolder->GetTextureClient())) {
|
||||
// If the texture's allocator is not open or a pooled TextureClient is
|
||||
// not compatible, release it.
|
||||
if (!textureHolder->GetTextureClient()->GetAllocator()->IPCOpen() ||
|
||||
!aHelper.IsCompatible(textureHolder->GetTextureClient())) {
|
||||
// Release TextureClient.
|
||||
RefPtr<Runnable> task = new TextureClientReleaseTask(textureHolder->GetTextureClient());
|
||||
textureHolder->ClearTextureClient();
|
||||
|
@ -46,6 +46,7 @@ parent:
|
||||
SurfaceFormat aFormat, ByteBuffer aBytes);
|
||||
sync DeleteImage(ImageKey aImageKey);
|
||||
async AddRawFont(FontKey aFontKey, ByteBuffer aBytes, uint32_t aFontIndex);
|
||||
async DeleteFont(FontKey aFontKey);
|
||||
async DPBegin(IntSize aSize);
|
||||
async DPEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
|
||||
ByteBuffer aDL, WrBuiltDisplayListDescriptor aDLDesc, ByteBuffer aAux, WrAuxiliaryListsDescriptor aAuxDesc);
|
||||
|
@ -24,6 +24,7 @@ WebRenderBridgeChild::WebRenderBridgeChild(const wr::PipelineId& aPipelineId)
|
||||
, mPipelineId(aPipelineId)
|
||||
, mIPCOpen(false)
|
||||
, mDestroyed(false)
|
||||
, mFontKeysDeleted(0)
|
||||
{
|
||||
}
|
||||
|
||||
@ -151,6 +152,112 @@ WebRenderBridgeChild::DeallocExternalImageId(uint64_t aImageId)
|
||||
SendRemoveExternalImageId(aImageId);
|
||||
}
|
||||
|
||||
struct FontFileData
|
||||
{
|
||||
wr::ByteBuffer mFontBuffer;
|
||||
uint32_t mFontIndex;
|
||||
float mGlyphSize;
|
||||
};
|
||||
|
||||
static void
|
||||
WriteFontFileData(const uint8_t* aData, uint32_t aLength, uint32_t aIndex,
|
||||
float aGlyphSize, uint32_t aVariationCount,
|
||||
const ScaledFont::VariationSetting* aVariations, void* aBaton)
|
||||
{
|
||||
FontFileData* data = static_cast<FontFileData*>(aBaton);
|
||||
|
||||
if (!data->mFontBuffer.Allocate(aLength)) {
|
||||
return;
|
||||
}
|
||||
memcpy(data->mFontBuffer.mData, aData, aLength);
|
||||
|
||||
data->mFontIndex = aIndex;
|
||||
data->mGlyphSize = aGlyphSize;
|
||||
}
|
||||
|
||||
void
|
||||
WebRenderBridgeChild::PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<GlyphArray>& aGlyphs,
|
||||
gfx::ScaledFont* aFont, const gfx::Point& aOffset, const gfx::Rect& aBounds,
|
||||
const gfx::Rect& aClip)
|
||||
{
|
||||
MOZ_ASSERT(aFont);
|
||||
MOZ_ASSERT(!aGlyphs.IsEmpty());
|
||||
|
||||
WrFontKey key = GetFontKeyForScaledFont(aFont);
|
||||
MOZ_ASSERT(key.mNamespace && key.mHandle);
|
||||
|
||||
WrClipRegion clipRegion = aBuilder.BuildClipRegion(wr::ToWrRect(aClip));
|
||||
|
||||
for (size_t i = 0; i < aGlyphs.Length(); i++) {
|
||||
GlyphArray glyph_array = aGlyphs[i];
|
||||
nsTArray<gfx::Glyph>& glyphs = glyph_array.glyphs();
|
||||
|
||||
nsTArray<WrGlyphInstance> wr_glyph_instances;
|
||||
wr_glyph_instances.SetLength(glyphs.Length());
|
||||
|
||||
for (size_t j = 0; j < glyphs.Length(); j++) {
|
||||
wr_glyph_instances[j].index = glyphs[j].mIndex;
|
||||
wr_glyph_instances[j].x = glyphs[j].mPosition.x - aOffset.x;
|
||||
wr_glyph_instances[j].y = glyphs[j].mPosition.y - aOffset.y;
|
||||
}
|
||||
aBuilder.PushText(wr::ToWrRect(aBounds),
|
||||
clipRegion,
|
||||
glyph_array.color().value(),
|
||||
key,
|
||||
Range<const WrGlyphInstance>(wr_glyph_instances.Elements(), wr_glyph_instances.Length()),
|
||||
aFont->GetSize());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
wr::FontKey
|
||||
WebRenderBridgeChild::GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont)
|
||||
{
|
||||
MOZ_ASSERT(!mDestroyed);
|
||||
MOZ_ASSERT(aScaledFont);
|
||||
MOZ_ASSERT((aScaledFont->GetType() == gfx::FontType::DWRITE) ||
|
||||
(aScaledFont->GetType() == gfx::FontType::MAC) ||
|
||||
(aScaledFont->GetType() == gfx::FontType::FONTCONFIG));
|
||||
|
||||
RefPtr<UnscaledFont> unscaled = aScaledFont->GetUnscaledFont();
|
||||
MOZ_ASSERT(unscaled);
|
||||
|
||||
wr::FontKey key = {0, 0};
|
||||
if (mFontKeys.Get(unscaled, &key)) {
|
||||
return key;
|
||||
}
|
||||
|
||||
FontFileData data;
|
||||
if (!aScaledFont->GetFontFileData(WriteFontFileData, &data) ||
|
||||
!data.mFontBuffer.mData) {
|
||||
return key;
|
||||
}
|
||||
|
||||
key.mNamespace = GetNamespace();
|
||||
key.mHandle = GetNextResourceId();
|
||||
|
||||
SendAddRawFont(key, data.mFontBuffer, data.mFontIndex);
|
||||
|
||||
mFontKeys.Put(unscaled, key);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
void
|
||||
WebRenderBridgeChild::RemoveExpiredFontKeys()
|
||||
{
|
||||
uint32_t counter = UnscaledFont::DeletionCounter();
|
||||
if (mFontKeysDeleted != counter) {
|
||||
mFontKeysDeleted = counter;
|
||||
for (auto iter = mFontKeys.Iter(); !iter.Done(); iter.Next()) {
|
||||
if (!iter.Key()) {
|
||||
SendDeleteFont(iter.Data());
|
||||
iter.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CompositorBridgeChild*
|
||||
WebRenderBridgeChild::GetCompositorBridgeChild()
|
||||
{
|
||||
|
@ -26,6 +26,28 @@ class CompositableClient;
|
||||
class CompositorBridgeChild;
|
||||
class TextureForwarder;
|
||||
|
||||
class UnscaledFontHashKey : public PLDHashEntryHdr
|
||||
{
|
||||
public:
|
||||
typedef gfx::UnscaledFont* KeyType;
|
||||
typedef const gfx::UnscaledFont* KeyTypePointer;
|
||||
|
||||
explicit UnscaledFontHashKey(KeyTypePointer aKey) : mKey(const_cast<KeyType>(aKey)) {}
|
||||
|
||||
KeyType GetKey() const { return mKey; }
|
||||
bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; }
|
||||
|
||||
static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
|
||||
static PLDHashNumber HashKey(KeyTypePointer aKey)
|
||||
{
|
||||
return NS_PTR_TO_UINT32(aKey) >> 2;
|
||||
}
|
||||
enum { ALLOW_MEMMOVE = true };
|
||||
|
||||
private:
|
||||
WeakPtr<gfx::UnscaledFont> mKey;
|
||||
};
|
||||
|
||||
class WebRenderBridgeChild final : public PWebRenderBridgeChild
|
||||
, public CompositableForwarder
|
||||
{
|
||||
@ -67,6 +89,14 @@ public:
|
||||
mIdNamespace = aIdNamespace;
|
||||
}
|
||||
|
||||
void PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<GlyphArray>& aGlyphs,
|
||||
gfx::ScaledFont* aFont, const gfx::Point& aOffset, const gfx::Rect& aBounds,
|
||||
const gfx::Rect& aClip);
|
||||
|
||||
wr::FontKey GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont);
|
||||
|
||||
void RemoveExpiredFontKeys();
|
||||
|
||||
private:
|
||||
friend class CompositorBridgeChild;
|
||||
|
||||
@ -123,6 +153,9 @@ private:
|
||||
|
||||
bool mIPCOpen;
|
||||
bool mDestroyed;
|
||||
|
||||
uint32_t mFontKeysDeleted;
|
||||
nsDataHashtable<UnscaledFontHashKey, wr::FontKey> mFontKeys;
|
||||
};
|
||||
|
||||
} // namespace layers
|
||||
|
@ -226,7 +226,16 @@ WebRenderBridgeParent::RecvAddRawFont(const wr::FontKey& aFontKey,
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
WebRenderBridgeParent::RecvDeleteFont(const wr::FontKey& aFontKey)
|
||||
{
|
||||
if (mDestroyed) {
|
||||
return IPC_OK();
|
||||
}
|
||||
MOZ_ASSERT(mApi);
|
||||
mApi->DeleteFont(aFontKey);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
WebRenderBridgeParent::RecvUpdateImage(const wr::ImageKey& aImageKey,
|
||||
|
@ -73,14 +73,15 @@ public:
|
||||
const uint32_t& aStride,
|
||||
const gfx::SurfaceFormat& aFormat,
|
||||
const ByteBuffer& aBuffer) override;
|
||||
mozilla::ipc::IPCResult RecvAddRawFont(const wr::FontKey& aFontKey,
|
||||
const ByteBuffer& aBuffer,
|
||||
const uint32_t& aFontIndex) override;
|
||||
mozilla::ipc::IPCResult RecvUpdateImage(const wr::ImageKey& aImageKey,
|
||||
const gfx::IntSize& aSize,
|
||||
const gfx::SurfaceFormat& aFormat,
|
||||
const ByteBuffer& aBuffer) override;
|
||||
mozilla::ipc::IPCResult RecvDeleteImage(const wr::ImageKey& a1) override;
|
||||
mozilla::ipc::IPCResult RecvAddRawFont(const wr::FontKey& aFontKey,
|
||||
const ByteBuffer& aBuffer,
|
||||
const uint32_t& aFontIndex) override;
|
||||
mozilla::ipc::IPCResult RecvDeleteFont(const wr::FontKey& aFontKey) override;
|
||||
mozilla::ipc::IPCResult RecvDPBegin(const gfx::IntSize& aSize) override;
|
||||
mozilla::ipc::IPCResult RecvDPEnd(const gfx::IntSize& aSize,
|
||||
InfallibleTArray<WebRenderParentCommand>&& aCommands,
|
||||
|
@ -288,6 +288,7 @@ WebRenderLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback,
|
||||
EndTransactionFlags aFlags)
|
||||
{
|
||||
DiscardImages();
|
||||
WrBridge()->RemoveExpiredFontKeys();
|
||||
|
||||
mPaintedLayerCallback = aCallback;
|
||||
mPaintedLayerCallbackData = aCallbackData;
|
||||
|
@ -39,8 +39,7 @@ WebRenderTextLayer::RenderLayer(wr::DisplayListBuilder& aBuilder)
|
||||
Stringify(clip).c_str());
|
||||
}
|
||||
|
||||
mGlyphHelper.BuildWebRenderCommands(WrBridge(), aBuilder, mGlyphs, mFont,
|
||||
GetOffsetToParent(), rect, clip);
|
||||
WrBridge()->PushGlyphs(aBuilder, mGlyphs, mFont, GetOffsetToParent(), rect, clip);
|
||||
}
|
||||
|
||||
} // namespace layers
|
||||
|
@ -33,8 +33,6 @@ public:
|
||||
Layer* GetLayer() override { return this; }
|
||||
void RenderLayer(wr::DisplayListBuilder& aBuilder) override;
|
||||
|
||||
protected:
|
||||
gfx::WebRenderGlyphHelper mGlyphHelper;
|
||||
};
|
||||
|
||||
} // namespace layers
|
||||
|
@ -6,7 +6,6 @@
|
||||
#ifndef GFX_PLATFORM_ANDROID_H
|
||||
#define GFX_PLATFORM_ANDROID_H
|
||||
|
||||
#include "gfxFT2Fonts.h"
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxUserFontSet.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "harfbuzz/hb.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using mozilla::intl::OSPreferences;
|
||||
|
||||
#define LOG_FONTLIST(args) MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), \
|
||||
@ -579,7 +580,26 @@ gfxFont *
|
||||
gfxDWriteFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle,
|
||||
bool aNeedsBold)
|
||||
{
|
||||
return new gfxDWriteFont(this, aFontStyle, aNeedsBold);
|
||||
WeakPtr<UnscaledFont>& unscaledFontPtr =
|
||||
aNeedsBold ? mUnscaledFontBold : mUnscaledFont;
|
||||
RefPtr<UnscaledFontDWrite> unscaledFont =
|
||||
static_cast<UnscaledFontDWrite*>(unscaledFontPtr.get());
|
||||
if (!unscaledFont) {
|
||||
DWRITE_FONT_SIMULATIONS sims = DWRITE_FONT_SIMULATIONS_NONE;
|
||||
if (aNeedsBold) {
|
||||
sims |= DWRITE_FONT_SIMULATIONS_BOLD;
|
||||
}
|
||||
RefPtr<IDWriteFontFace> fontFace;
|
||||
nsresult rv = CreateFontFace(getter_AddRefs(fontFace), sims);
|
||||
if (NS_FAILED(rv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unscaledFont = new UnscaledFontDWrite(fontFace, sims);
|
||||
unscaledFontPtr = unscaledFont;
|
||||
}
|
||||
|
||||
return new gfxDWriteFont(unscaledFont, this, aFontStyle, aNeedsBold);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -17,6 +17,8 @@
|
||||
#include "gfxPlatform.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include "mozilla/gfx/UnscaledFontDWrite.h"
|
||||
|
||||
|
||||
/**
|
||||
* gfxDWriteFontFamily is a class that describes one of the fonts on the
|
||||
@ -201,6 +203,9 @@ protected:
|
||||
|
||||
int8_t mIsCJK;
|
||||
bool mForceGDIClassic;
|
||||
|
||||
mozilla::WeakPtr<mozilla::gfx::UnscaledFont> mUnscaledFont;
|
||||
mozilla::WeakPtr<mozilla::gfx::UnscaledFont> mUnscaledFontBold;
|
||||
};
|
||||
|
||||
// custom text renderer used to determine the fallback font for a given char
|
||||
|
@ -71,11 +71,12 @@ UsingClearType()
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// gfxDWriteFont
|
||||
gfxDWriteFont::gfxDWriteFont(gfxFontEntry *aFontEntry,
|
||||
gfxDWriteFont::gfxDWriteFont(const RefPtr<UnscaledFontDWrite>& aUnscaledFont,
|
||||
gfxFontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle,
|
||||
bool aNeedsBold,
|
||||
AntialiasOption anAAOption)
|
||||
: gfxFont(aFontEntry, aFontStyle, anAAOption)
|
||||
: gfxFont(aUnscaledFont, aFontEntry, aFontStyle, anAAOption)
|
||||
, mCairoFontFace(nullptr)
|
||||
, mMetrics(nullptr)
|
||||
, mSpaceGlyph(0)
|
||||
@ -84,27 +85,15 @@ gfxDWriteFont::gfxDWriteFont(gfxFontEntry *aFontEntry,
|
||||
, mUseSubpixelPositions(false)
|
||||
, mAllowManualShowGlyphs(true)
|
||||
{
|
||||
gfxDWriteFontEntry *fe =
|
||||
static_cast<gfxDWriteFontEntry*>(aFontEntry);
|
||||
nsresult rv;
|
||||
DWRITE_FONT_SIMULATIONS sims = DWRITE_FONT_SIMULATIONS_NONE;
|
||||
if ((GetStyle()->style != NS_FONT_STYLE_NORMAL) &&
|
||||
fe->IsUpright() &&
|
||||
aFontEntry->IsUpright() &&
|
||||
GetStyle()->allowSyntheticStyle) {
|
||||
// For this we always use the font_matrix for uniformity. Not the
|
||||
// DWrite simulation.
|
||||
mNeedsOblique = true;
|
||||
}
|
||||
if (aNeedsBold) {
|
||||
sims |= DWRITE_FONT_SIMULATIONS_BOLD;
|
||||
}
|
||||
|
||||
rv = fe->CreateFontFace(getter_AddRefs(mFontFace), sims);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
mIsValid = false;
|
||||
return;
|
||||
}
|
||||
mFontFace = aUnscaledFont->GetFontFace();
|
||||
|
||||
ComputeMetrics(anAAOption);
|
||||
}
|
||||
@ -131,7 +120,8 @@ UniquePtr<gfxFont>
|
||||
gfxDWriteFont::CopyWithAntialiasOption(AntialiasOption anAAOption)
|
||||
{
|
||||
auto entry = static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
|
||||
return MakeUnique<gfxDWriteFont>(entry, &mStyle, mNeedsBold, anAAOption);
|
||||
RefPtr<UnscaledFontDWrite> unscaledFont = static_cast<UnscaledFontDWrite*>(mUnscaledFont.get());
|
||||
return MakeUnique<gfxDWriteFont>(unscaledFont, entry, &mStyle, mNeedsBold, anAAOption);
|
||||
}
|
||||
|
||||
const gfxFont::Metrics&
|
||||
@ -694,6 +684,7 @@ gfxDWriteFont::GetScaledFont(mozilla::gfx::DrawTarget *aTarget)
|
||||
|
||||
if (wantCairo) {
|
||||
mAzureScaledFont = Factory::CreateScaledFontWithCairo(nativeFont,
|
||||
GetUnscaledFont(),
|
||||
GetAdjustedSize(),
|
||||
GetCairoScaledFont());
|
||||
} else if (aTarget->GetBackendType() == BackendType::SKIA) {
|
||||
@ -704,11 +695,13 @@ gfxDWriteFont::GetScaledFont(mozilla::gfx::DrawTarget *aTarget)
|
||||
const gfxFontStyle* fontStyle = GetStyle();
|
||||
mAzureScaledFont =
|
||||
Factory::CreateScaledFontForDWriteFont(mFontFace, fontStyle,
|
||||
GetUnscaledFont(),
|
||||
GetAdjustedSize(),
|
||||
useEmbeddedBitmap,
|
||||
GetForceGDIClassic());
|
||||
} else {
|
||||
mAzureScaledFont = Factory::CreateScaledFontForNativeFont(nativeFont,
|
||||
GetUnscaledFont(),
|
||||
GetAdjustedSize());
|
||||
}
|
||||
|
||||
|
@ -17,13 +17,16 @@
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsHashKeys.h"
|
||||
|
||||
#include "mozilla/gfx/UnscaledFontDWrite.h"
|
||||
|
||||
/**
|
||||
* \brief Class representing a font face for a font entry.
|
||||
*/
|
||||
class gfxDWriteFont : public gfxFont
|
||||
{
|
||||
public:
|
||||
gfxDWriteFont(gfxFontEntry *aFontEntry,
|
||||
gfxDWriteFont(const RefPtr<mozilla::gfx::UnscaledFontDWrite>& aUnscaledFont,
|
||||
gfxFontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle,
|
||||
bool aNeedsBold = false,
|
||||
AntialiasOption = kAntialiasDefault);
|
||||
|
@ -12,10 +12,11 @@
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
gfxFT2FontBase::gfxFT2FontBase(cairo_scaled_font_t *aScaledFont,
|
||||
gfxFT2FontBase::gfxFT2FontBase(const RefPtr<UnscaledFontFreeType>& aUnscaledFont,
|
||||
cairo_scaled_font_t *aScaledFont,
|
||||
gfxFontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle)
|
||||
: gfxFont(aFontEntry, aFontStyle, kAntialiasDefault, aScaledFont),
|
||||
: gfxFont(aUnscaledFont, aFontEntry, aFontStyle, kAntialiasDefault, aScaledFont),
|
||||
mSpaceGlyph(0),
|
||||
mHasMetrics(false)
|
||||
{
|
||||
|
@ -10,10 +10,12 @@
|
||||
#include "gfxContext.h"
|
||||
#include "gfxFont.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/gfx/UnscaledFontFreeType.h"
|
||||
|
||||
class gfxFT2FontBase : public gfxFont {
|
||||
public:
|
||||
gfxFT2FontBase(cairo_scaled_font_t *aScaledFont,
|
||||
gfxFT2FontBase(const RefPtr<mozilla::gfx::UnscaledFontFreeType>& aUnscaledFont,
|
||||
cairo_scaled_font_t *aScaledFont,
|
||||
gfxFontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle);
|
||||
virtual ~gfxFT2FontBase();
|
||||
|
@ -50,6 +50,7 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
static LazyLogModule sFontInfoLog("fontInfoLog");
|
||||
|
||||
@ -237,7 +238,20 @@ FT2FontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold
|
||||
if (!scaledFont) {
|
||||
return nullptr;
|
||||
}
|
||||
gfxFont *font = new gfxFT2Font(scaledFont, this, aFontStyle, aNeedsBold);
|
||||
|
||||
RefPtr<UnscaledFontFreeType> unscaledFont =
|
||||
static_cast<UnscaledFontFreeType*>(mUnscaledFont.get());
|
||||
if (!unscaledFont) {
|
||||
unscaledFont =
|
||||
mFilename.IsEmpty() ?
|
||||
new UnscaledFontFreeType(mFTFace) :
|
||||
new UnscaledFontFreeType(mFilename.BeginReading(),
|
||||
mFTFontIndex);
|
||||
mUnscaledFont = unscaledFont;
|
||||
}
|
||||
|
||||
gfxFont *font = new gfxFT2Font(unscaledFont, scaledFont, this,
|
||||
aFontStyle, aNeedsBold);
|
||||
cairo_scaled_font_destroy(scaledFont);
|
||||
return font;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "gfxPlatformFontList.h"
|
||||
#include "mozilla/gfx/UnscaledFontFreeType.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -94,6 +95,8 @@ public:
|
||||
|
||||
nsCString mFilename;
|
||||
uint8_t mFTFontIndex;
|
||||
|
||||
mozilla::WeakPtr<mozilla::gfx::UnscaledFont> mUnscaledFont;
|
||||
};
|
||||
|
||||
class FT2FontFamily : public gfxFontFamily
|
||||
|
@ -156,11 +156,12 @@ gfxFT2Font::AddRange(const char16_t *aText, uint32_t aOffset,
|
||||
}
|
||||
}
|
||||
|
||||
gfxFT2Font::gfxFT2Font(cairo_scaled_font_t *aCairoFont,
|
||||
gfxFT2Font::gfxFT2Font(const RefPtr<mozilla::gfx::UnscaledFontFreeType>& aUnscaledFont,
|
||||
cairo_scaled_font_t *aCairoFont,
|
||||
FT2FontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle,
|
||||
bool aNeedsBold)
|
||||
: gfxFT2FontBase(aCairoFont, aFontEntry, aFontStyle)
|
||||
: gfxFT2FontBase(aUnscaledFont, aCairoFont, aFontEntry, aFontStyle)
|
||||
, mCharGlyphCache(32)
|
||||
{
|
||||
NS_ASSERTION(mFontEntry, "Unable to find font entry for font. Something is whack.");
|
||||
|
@ -19,7 +19,8 @@ class FT2FontEntry;
|
||||
|
||||
class gfxFT2Font : public gfxFT2FontBase {
|
||||
public: // new functions
|
||||
gfxFT2Font(cairo_scaled_font_t *aCairoFont,
|
||||
gfxFT2Font(const RefPtr<mozilla::gfx::UnscaledFontFreeType>& aUnscaledFont,
|
||||
cairo_scaled_font_t *aCairoFont,
|
||||
FT2FontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle,
|
||||
bool aNeedsBold);
|
||||
|
@ -39,6 +39,7 @@
|
||||
#endif
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::unicode;
|
||||
|
||||
#ifndef FC_POSTSCRIPT_NAME
|
||||
@ -796,6 +797,33 @@ PreparePattern(FcPattern* aPattern, bool aIsPrinterFont)
|
||||
FcDefaultSubstitute(aPattern);
|
||||
}
|
||||
|
||||
void
|
||||
gfxFontconfigFontEntry::UnscaledFontCache::MoveToFront(size_t aIndex) {
|
||||
if (aIndex > 0) {
|
||||
WeakPtr<UnscaledFont> front =
|
||||
Move(mUnscaledFonts[aIndex]);
|
||||
for (size_t i = aIndex; i > 0; i--) {
|
||||
mUnscaledFonts[i] = Move(mUnscaledFonts[i-1]);
|
||||
}
|
||||
mUnscaledFonts[0] = Move(front);
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<UnscaledFontFontconfig>
|
||||
gfxFontconfigFontEntry::UnscaledFontCache::Lookup(const char* aFile, uint32_t aIndex) {
|
||||
for (size_t i = 0; i < kNumEntries; i++) {
|
||||
UnscaledFontFontconfig* entry =
|
||||
static_cast<UnscaledFontFontconfig*>(mUnscaledFonts[i].get());
|
||||
if (entry &&
|
||||
!strcmp(entry->GetFile(), aFile) &&
|
||||
entry->GetIndex() == aIndex) {
|
||||
MoveToFront(i);
|
||||
return do_AddRef(entry);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static inline gfxFloat
|
||||
SizeForStyle(gfxFontconfigFontEntry* aEntry, const gfxFontStyle& aStyle)
|
||||
{
|
||||
@ -848,8 +876,31 @@ gfxFontconfigFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle,
|
||||
|
||||
cairo_scaled_font_t* scaledFont =
|
||||
CreateScaledFont(renderPattern, size, aFontStyle, aNeedsBold);
|
||||
|
||||
const FcChar8* file = ToFcChar8Ptr("");
|
||||
int index = 0;
|
||||
if (!mFontData) {
|
||||
if (FcPatternGetString(renderPattern, FC_FILE, 0,
|
||||
const_cast<FcChar8**>(&file)) != FcResultMatch ||
|
||||
FcPatternGetInteger(renderPattern, FC_INDEX, 0, &index) != FcResultMatch) {
|
||||
NS_WARNING("No file in Fontconfig pattern for font instance");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<UnscaledFontFontconfig> unscaledFont =
|
||||
mUnscaledFontCache.Lookup(ToCharPtr(file), index);
|
||||
if (!unscaledFont) {
|
||||
unscaledFont =
|
||||
mFontData ?
|
||||
new UnscaledFontFontconfig(mFTFace) :
|
||||
new UnscaledFontFontconfig(ToCharPtr(file), index);
|
||||
mUnscaledFontCache.Add(unscaledFont);
|
||||
}
|
||||
|
||||
gfxFont* newFont =
|
||||
new gfxFontconfigFont(scaledFont, renderPattern, size,
|
||||
new gfxFontconfigFont(unscaledFont, scaledFont,
|
||||
renderPattern, size,
|
||||
this, aFontStyle, aNeedsBold);
|
||||
cairo_scaled_font_destroy(scaledFont);
|
||||
|
||||
@ -1067,13 +1118,14 @@ gfxFontconfigFontFamily::FindAllFontsForStyle(const gfxFontStyle& aFontStyle,
|
||||
}
|
||||
}
|
||||
|
||||
gfxFontconfigFont::gfxFontconfigFont(cairo_scaled_font_t *aScaledFont,
|
||||
gfxFontconfigFont::gfxFontconfigFont(const RefPtr<UnscaledFontFontconfig>& aUnscaledFont,
|
||||
cairo_scaled_font_t *aScaledFont,
|
||||
FcPattern *aPattern,
|
||||
gfxFloat aAdjustedSize,
|
||||
gfxFontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle,
|
||||
bool aNeedsBold) :
|
||||
gfxFontconfigFontBase(aScaledFont, aPattern, aFontEntry, aFontStyle)
|
||||
gfxFontconfigFontBase(aUnscaledFont, aScaledFont, aPattern, aFontEntry, aFontStyle)
|
||||
{
|
||||
mAdjustedSize = aAdjustedSize;
|
||||
}
|
||||
|
@ -168,6 +168,26 @@ protected:
|
||||
|
||||
// data font
|
||||
const uint8_t* mFontData;
|
||||
|
||||
class UnscaledFontCache
|
||||
{
|
||||
public:
|
||||
already_AddRefed<mozilla::gfx::UnscaledFontFontconfig>
|
||||
Lookup(const char* aFile, uint32_t aIndex);
|
||||
|
||||
void Add(const RefPtr<mozilla::gfx::UnscaledFontFontconfig>& aUnscaledFont) {
|
||||
mUnscaledFonts[kNumEntries-1] = aUnscaledFont;
|
||||
MoveToFront(kNumEntries-1);
|
||||
}
|
||||
|
||||
private:
|
||||
void MoveToFront(size_t aIndex);
|
||||
|
||||
static const size_t kNumEntries = 3;
|
||||
mozilla::WeakPtr<mozilla::gfx::UnscaledFont> mUnscaledFonts[kNumEntries];
|
||||
};
|
||||
|
||||
UnscaledFontCache mUnscaledFontCache;
|
||||
};
|
||||
|
||||
class gfxFontconfigFontFamily : public gfxFontFamily {
|
||||
@ -205,7 +225,8 @@ protected:
|
||||
|
||||
class gfxFontconfigFont : public gfxFontconfigFontBase {
|
||||
public:
|
||||
gfxFontconfigFont(cairo_scaled_font_t *aScaledFont,
|
||||
gfxFontconfigFont(const RefPtr<mozilla::gfx::UnscaledFontFontconfig> &aUnscaledFont,
|
||||
cairo_scaled_font_t *aScaledFont,
|
||||
FcPattern *aPattern,
|
||||
gfxFloat aAdjustedSize,
|
||||
gfxFontEntry *aFontEntry,
|
||||
|
@ -835,7 +835,8 @@ gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft)
|
||||
mAdvanceWidth += aOther.mAdvanceWidth;
|
||||
}
|
||||
|
||||
gfxFont::gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
gfxFont::gfxFont(const RefPtr<UnscaledFont>& aUnscaledFont,
|
||||
gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
AntialiasOption anAAOption, cairo_scaled_font_t *aScaledFont) :
|
||||
mScaledFont(aScaledFont),
|
||||
mFontEntry(aFontEntry), mIsValid(true),
|
||||
@ -844,7 +845,8 @@ gfxFont::gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
mStyle(*aFontStyle),
|
||||
mAdjustedSize(0.0),
|
||||
mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized"
|
||||
mAntialiasOption(anAAOption)
|
||||
mAntialiasOption(anAAOption),
|
||||
mUnscaledFont(aUnscaledFont)
|
||||
{
|
||||
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
||||
++gFontCount;
|
||||
|
@ -1385,7 +1385,8 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
gfxFont(const RefPtr<mozilla::gfx::UnscaledFont>& aUnscaledFont,
|
||||
gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
AntialiasOption anAAOption = kAntialiasDefault,
|
||||
cairo_scaled_font_t *aScaledFont = nullptr);
|
||||
|
||||
@ -1801,8 +1802,14 @@ public:
|
||||
|
||||
virtual FontType GetType() const = 0;
|
||||
|
||||
const RefPtr<mozilla::gfx::UnscaledFont>& GetUnscaledFont() const {
|
||||
return mUnscaledFont;
|
||||
}
|
||||
|
||||
virtual already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(DrawTarget* aTarget)
|
||||
{ return gfxPlatform::GetPlatform()->GetScaledFontForFont(aTarget, this); }
|
||||
{
|
||||
return gfxPlatform::GetPlatform()->GetScaledFontForFont(aTarget, this);
|
||||
}
|
||||
|
||||
bool KerningDisabled() {
|
||||
return mKerningSet && !mKerningEnabled;
|
||||
@ -2120,6 +2127,7 @@ protected:
|
||||
// ranges supported by font
|
||||
RefPtr<gfxCharacterMap> mUnicodeRangeMap;
|
||||
|
||||
RefPtr<mozilla::gfx::UnscaledFont> mUnscaledFont;
|
||||
RefPtr<mozilla::gfx::ScaledFont> mAzureScaledFont;
|
||||
|
||||
// For vertical metrics, created on demand.
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "harfbuzz/hb.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/WeakPtr.h"
|
||||
|
||||
typedef struct gr_face gr_face;
|
||||
|
||||
|
@ -38,11 +38,12 @@ public:
|
||||
|
||||
class gfxFontconfigFontBase : public gfxFT2FontBase {
|
||||
public:
|
||||
gfxFontconfigFontBase(cairo_scaled_font_t *aScaledFont,
|
||||
gfxFontconfigFontBase(const RefPtr<mozilla::gfx::UnscaledFontFontconfig>& aUnscaledFont,
|
||||
cairo_scaled_font_t *aScaledFont,
|
||||
FcPattern *aPattern,
|
||||
gfxFontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle)
|
||||
: gfxFT2FontBase(aScaledFont, aFontEntry, aFontStyle)
|
||||
: gfxFT2FontBase(aUnscaledFont, aScaledFont, aFontEntry, aFontStyle)
|
||||
, mPattern(aPattern) { }
|
||||
|
||||
virtual FontType GetType() const override { return FONT_TYPE_FONTCONFIG; }
|
||||
|
@ -44,7 +44,7 @@ gfxGDIFont::gfxGDIFont(GDIFontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle,
|
||||
bool aNeedsBold,
|
||||
AntialiasOption anAAOption)
|
||||
: gfxFont(aFontEntry, aFontStyle, anAAOption),
|
||||
: gfxFont(nullptr, aFontEntry, aFontStyle, anAAOption),
|
||||
mFont(nullptr),
|
||||
mFontFace(nullptr),
|
||||
mMetrics(nullptr),
|
||||
@ -53,6 +53,10 @@ gfxGDIFont::gfxGDIFont(GDIFontEntry *aFontEntry,
|
||||
mScriptCache(nullptr)
|
||||
{
|
||||
Initialize();
|
||||
|
||||
if (mFont) {
|
||||
mUnscaledFont = aFontEntry->LookupUnscaledFont(mFont);
|
||||
}
|
||||
}
|
||||
|
||||
gfxGDIFont::~gfxGDIFont()
|
||||
@ -224,6 +228,12 @@ gfxGDIFont::Initialize()
|
||||
mMetrics = new gfxFont::Metrics;
|
||||
::memset(mMetrics, 0, sizeof(*mMetrics));
|
||||
|
||||
if (!mFont) {
|
||||
NS_WARNING("Failed creating GDI font");
|
||||
mIsValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
AutoDC dc;
|
||||
SetGraphicsMode(dc.GetDC(), GM_ADVANCED);
|
||||
AutoSelectFont selectFont(dc.GetDC(), mFont);
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <usp10.h>
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
#define ROUND(x) floor((x) + 0.5)
|
||||
|
||||
@ -246,6 +247,21 @@ GDIFontEntry::CopyFontTable(uint32_t aTableTag, nsTArray<uint8_t>& aBuffer)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
already_AddRefed<UnscaledFontGDI>
|
||||
GDIFontEntry::LookupUnscaledFont(HFONT aFont)
|
||||
{
|
||||
RefPtr<UnscaledFontGDI> unscaledFont =
|
||||
static_cast<UnscaledFontGDI*>(mUnscaledFont.get());
|
||||
if (!unscaledFont) {
|
||||
LOGFONT lf;
|
||||
GetObject(aFont, sizeof(LOGFONT), &lf);
|
||||
unscaledFont = new UnscaledFontGDI(lf);
|
||||
mUnscaledFont = unscaledFont;
|
||||
}
|
||||
|
||||
return unscaledFont.forget();
|
||||
}
|
||||
|
||||
void
|
||||
GDIFontEntry::FillLogFont(LOGFONTW *aLogFont,
|
||||
uint16_t aWeight, gfxFloat aSize)
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "gfxWindowsPlatform.h"
|
||||
#include "gfxPlatformFontList.h"
|
||||
#include "nsGkAtoms.h"
|
||||
#include "mozilla/gfx/UnscaledFontGDI.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@ -262,7 +263,7 @@ public:
|
||||
gfxSparseBitSet mUnicodeRanges;
|
||||
|
||||
protected:
|
||||
friend class gfxWindowsFont;
|
||||
friend class gfxGDIFont;
|
||||
|
||||
GDIFontEntry(const nsAString& aFaceName, gfxWindowsFontType aFontType,
|
||||
uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
|
||||
@ -275,7 +276,11 @@ protected:
|
||||
virtual nsresult CopyFontTable(uint32_t aTableTag,
|
||||
nsTArray<uint8_t>& aBuffer) override;
|
||||
|
||||
already_AddRefed<mozilla::gfx::UnscaledFontGDI> LookupUnscaledFont(HFONT aFont);
|
||||
|
||||
LOGFONTW mLogFont;
|
||||
|
||||
mozilla::WeakPtr<mozilla::gfx::UnscaledFont> mUnscaledFont;
|
||||
};
|
||||
|
||||
// a single font family, referencing one or more faces
|
||||
|
@ -159,9 +159,11 @@ CreateVariationDictionaryOrNull(CGFontRef aCGFont,
|
||||
return dict.forget();
|
||||
}
|
||||
|
||||
gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
gfxMacFont::gfxMacFont(const RefPtr<UnscaledFontMac>& aUnscaledFont,
|
||||
MacOSFontEntry *aFontEntry,
|
||||
const gfxFontStyle *aFontStyle,
|
||||
bool aNeedsBold)
|
||||
: gfxFont(aFontEntry, aFontStyle),
|
||||
: gfxFont(aUnscaledFont, aFontEntry, aFontStyle),
|
||||
mCGFont(nullptr),
|
||||
mCTFont(nullptr),
|
||||
mFontFace(nullptr),
|
||||
@ -170,7 +172,7 @@ gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyl
|
||||
mApplySyntheticBold = aNeedsBold;
|
||||
|
||||
if (mVariationFont && aFontStyle->variationSettings.Length() > 0) {
|
||||
CGFontRef baseFont = aFontEntry->GetFontRef();
|
||||
CGFontRef baseFont = aUnscaledFont->GetFont();
|
||||
if (!baseFont) {
|
||||
mIsValid = false;
|
||||
return;
|
||||
@ -185,7 +187,7 @@ gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyl
|
||||
mCGFont = baseFont;
|
||||
}
|
||||
} else {
|
||||
mCGFont = aFontEntry->GetFontRef();
|
||||
mCGFont = aUnscaledFont->GetFont();
|
||||
if (!mCGFont) {
|
||||
mIsValid = false;
|
||||
return;
|
||||
@ -636,18 +638,22 @@ gfxMacFont::GetScaledFont(DrawTarget *aTarget)
|
||||
NativeFont nativeFont;
|
||||
nativeFont.mType = NativeFontType::MAC_FONT_FACE;
|
||||
nativeFont.mFont = GetCGFontRef();
|
||||
mAzureScaledFont = mozilla::gfx::Factory::CreateScaledFontWithCairo(nativeFont, GetAdjustedSize(), mScaledFont);
|
||||
mAzureScaledFont =
|
||||
Factory::CreateScaledFontWithCairo(nativeFont,
|
||||
GetUnscaledFont(),
|
||||
GetAdjustedSize(),
|
||||
mScaledFont);
|
||||
}
|
||||
|
||||
RefPtr<ScaledFont> scaledFont(mAzureScaledFont);
|
||||
return scaledFont.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<mozilla::gfx::GlyphRenderingOptions>
|
||||
already_AddRefed<GlyphRenderingOptions>
|
||||
gfxMacFont::GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams)
|
||||
{
|
||||
if (aRunParams) {
|
||||
return mozilla::gfx::Factory::CreateCGGlyphRenderingOptions(aRunParams->fontSmoothingBGColor);
|
||||
return Factory::CreateCGGlyphRenderingOptions(aRunParams->fontSmoothingBGColor);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -11,12 +11,15 @@
|
||||
#include "cairo.h"
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
|
||||
#include "mozilla/gfx/UnscaledFontMac.h"
|
||||
|
||||
class MacOSFontEntry;
|
||||
|
||||
class gfxMacFont : public gfxFont
|
||||
{
|
||||
public:
|
||||
gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
gfxMacFont(const RefPtr<mozilla::gfx::UnscaledFontMac>& aUnscaledFont,
|
||||
MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
||||
bool aNeedsBold);
|
||||
|
||||
virtual ~gfxMacFont();
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/LookAndFeel.h"
|
||||
|
||||
#include "mozilla/gfx/UnscaledFontMac.h"
|
||||
|
||||
class gfxMacPlatformFontList;
|
||||
|
||||
// a single member of a font family (i.e. a single face, such as Times Italic)
|
||||
@ -76,6 +78,8 @@ protected:
|
||||
bool mHasVariations;
|
||||
bool mHasVariationsInitialized;
|
||||
nsTHashtable<nsUint32HashKey> mAvailableTables;
|
||||
|
||||
mozilla::WeakPtr<mozilla::gfx::UnscaledFont> mUnscaledFont;
|
||||
};
|
||||
|
||||
class gfxMacPlatformFontList : public gfxPlatformFontList {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user