merge mozilla-inbound to mozilla-central. r=merge a=merge

MozReview-Commit-ID: ES9rKhiQo10
This commit is contained in:
Sebastian Hengst 2017-09-01 10:38:51 +02:00
commit 43bc951ac7
276 changed files with 6293 additions and 3614 deletions

View File

@ -848,8 +848,10 @@ Accessible::HandleAccEvent(AccEvent* aEvent)
if (profiler_is_active()) {
nsAutoCString strEventType;
GetAccService()->GetStringEventType(aEvent->GetEventType(), strEventType);
profiler_tracing("A11y Event", strEventType.get());
nsAutoCString strMarker;
strMarker.AppendLiteral("A11y Event - ");
strMarker.Append(strEventType);
profiler_add_marker(strMarker.get());
}
if (IPCAccessibilityActive() && Document()) {

View File

@ -327,6 +327,7 @@ toolbarpaletteitem > toolbaritem[sdkstylewidget="true"][cui-areatype="toolbar"]
}
.webextension-browser-action > .toolbarbutton-badge-stack > .toolbarbutton-icon {
height: 16px;
width: 16px;
}

View File

@ -1375,6 +1375,25 @@ var gBrowserInit = {
gRemoteControl.updateVisualCue(Marionette.running);
// If we are given a tab to swap in, take care of it before first paint to
// avoid an about:blank flash.
let tabToOpen = window.arguments && window.arguments[0];
if (tabToOpen instanceof XULElement) {
// Clear the reference to the tab from the arguments array.
window.arguments[0] = null;
// Stop the about:blank load
gBrowser.stop();
// make sure it has a docshell
gBrowser.docShell;
try {
gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToOpen);
} catch (e) {
Cu.reportError(e);
}
}
// Wait until chrome is painted before executing code not critical to making the window visible
this._boundDelayedStartup = this._delayedStartup.bind(this);
window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
@ -1601,6 +1620,8 @@ var gBrowserInit = {
return;
}
// We don't check if uriToLoad is a XULElement because this case has
// already been handled before first paint, and the argument cleared.
if (uriToLoad instanceof Ci.nsIArray) {
let count = uriToLoad.length;
let specs = [];
@ -1618,39 +1639,6 @@ var gBrowserInit = {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
} catch (e) {}
} else if (uriToLoad instanceof XULElement) {
// swap the given tab with the default about:blank tab and then close
// the original tab in the other window.
let tabToOpen = uriToLoad;
// If this tab was passed as a window argument, clear the
// reference to it from the arguments array.
if (window.arguments[0] == tabToOpen) {
window.arguments[0] = null;
}
// Stop the about:blank load
gBrowser.stop();
// make sure it has a docshell
gBrowser.docShell;
// We must set usercontextid before updateBrowserRemoteness()
// so that the newly created remote tab child has correct usercontextid
if (tabToOpen.hasAttribute("usercontextid")) {
let usercontextid = tabToOpen.getAttribute("usercontextid");
gBrowser.selectedBrowser.setAttribute("usercontextid", usercontextid);
}
try {
// Make sure selectedBrowser has the same remote settings as the one
// we are swapping in.
gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser,
tabToOpen.linkedBrowser.isRemoteBrowser,
{ remoteType: tabToOpen.linkedBrowser.remoteType });
gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToOpen);
} catch (e) {
Cu.reportError(e);
}
} else if (window.arguments.length >= 3) {
// window.arguments[2]: referrer (nsIURI | string)
// [3]: postData (nsIInputStream)

View File

@ -3009,6 +3009,16 @@
newTab = true;
}
aTab._endRemoveArgs = [closeWindow, newTab];
// swapBrowsersAndCloseOther will take care of closing the window without animation.
if (closeWindow && aAdoptedByTab) {
// Remove the tab's filter to avoid leaking.
if (aTab.linkedPanel) {
this._tabFilters.delete(aTab);
}
return true;
}
if (!aTab._fullyOpen) {
// If the opening tab animation hasn't finished before we start closing the
@ -3079,7 +3089,6 @@
tab.owner = null;
}
aTab._endRemoveArgs = [closeWindow, newTab];
return true;
]]>
</body>
@ -3297,6 +3306,25 @@
if (!remoteBrowser._beginRemoveTab(aOtherTab, aOurTab, true))
return;
// If this is the last tab of the window, hide the window
// immediately without animation before the docshell swap, to avoid
// about:blank being painted.
let [closeWindow] = aOtherTab._endRemoveArgs;
if (closeWindow) {
let win = aOtherTab.ownerGlobal;
let dwu = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
dwu.suppressAnimation(true);
// Only suppressing window animations isn't enough to avoid
// an empty content area being painted.
let baseWin = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIDocShellTreeItem)
.treeOwner
.QueryInterface(Ci.nsIBaseWindow);
baseWin.visibility = false;
}
let modifiedAttrs = [];
if (aOtherTab.hasAttribute("muted")) {
aOurTab.setAttribute("muted", "true");
@ -3363,7 +3391,11 @@
}
// Finish tearing down the tab that's going away.
remoteBrowser._endRemoveTab(aOtherTab);
if (closeWindow) {
aOtherTab.ownerGlobal.close();
} else {
remoteBrowser._endRemoveTab(aOtherTab);
}
this.setTabTitle(aOurTab);
@ -3729,6 +3761,14 @@
for (var name in aOptions)
options += "," + name + "=" + aOptions[name];
// Play the tab closing animation to give immediate feedback while
// waiting for the new window to appear.
// content area when the docshells are swapped.
if (this.animationsEnabled) {
aTab.style.maxWidth = ""; // ensure that fade-out transition happens
aTab.removeAttribute("fadein");
}
// tell a new window to take the "dropped" tab
return window.openDialog(getBrowserURL(), "_blank", options, aTab);
]]>
@ -3851,7 +3891,8 @@
let linkedBrowser = aTab.linkedBrowser;
let params = { eventDetail: { adoptedTab: aTab },
preferredRemoteType: linkedBrowser.remoteType,
sameProcessAsFrameLoader: linkedBrowser.frameLoader };
sameProcessAsFrameLoader: linkedBrowser.frameLoader,
skipAnimation: true };
if (aTab.hasAttribute("usercontextid")) {
// new tab must have the same usercontextid as the old one
params.userContextId = aTab.getAttribute("usercontextid");
@ -7399,7 +7440,7 @@
window.moveTo(left, top);
window.focus();
} else {
let props = { screenX: left, screenY: top };
let props = { screenX: left, screenY: top, suppressanimation: 1 };
if (AppConstants.platform != "win") {
props.outerWidth = winWidth;
props.outerHeight = winHeight;

View File

@ -67,7 +67,7 @@ const EXPECTED_REFLOWS_FIRST_OPEN = [
"_invalidate@chrome://global/content/bindings/autocomplete.xml",
"invalidate@chrome://global/content/bindings/autocomplete.xml"
],
times: 1584, // This number should only ever go down - never up.
times: 1344, // This number should only ever go down - never up.
},
{

View File

@ -67,7 +67,7 @@ const EXPECTED_REFLOWS_FIRST_OPEN = [
"_invalidate@chrome://global/content/bindings/autocomplete.xml",
"invalidate@chrome://global/content/bindings/autocomplete.xml"
],
times: 390, // This number should only ever go down - never up.
times: 330, // This number should only ever go down - never up.
},
{
@ -119,7 +119,7 @@ const EXPECTED_REFLOWS_SECOND_OPEN = [
"_invalidate@chrome://global/content/bindings/autocomplete.xml",
"invalidate@chrome://global/content/bindings/autocomplete.xml"
],
times: 444, // This number should only ever go down - never up.
times: 384, // This number should only ever go down - never up.
},
// Bug 1384256

View File

@ -318,15 +318,15 @@ const PanelUI = {
*
* @return a Promise that resolves once the panel is ready to roll.
*/
ensureReady() {
if (this._readyPromise) {
return this._readyPromise;
async ensureReady() {
if (this._isReady) {
return;
}
await window.delayedStartupPromise;
this._ensureEventListenersAdded();
this.panel.hidden = false;
this._readyPromise = Promise.resolve();
this._isReady = true;
return this._readyPromise;
},
/**

View File

@ -41,6 +41,7 @@ skip-if = os == 'linux'
[browser_ext_browserAction_pageAction_icon.js]
[browser_ext_browserAction_pageAction_icon_permissions.js]
[browser_ext_browserAction_popup.js]
skip-if = debug && (os == 'linux' && bits == 32) # Bug 1313372
[browser_ext_browserAction_popup_preload.js]
skip-if = (os == 'win' && !debug) # bug 1352668
[browser_ext_browserAction_popup_resize.js]

View File

@ -372,12 +372,10 @@ var FormAutofillContent = {
*
* @param {Object} profile Submitted form's address/creditcard guid and record.
* @param {Object} domWin Current content window.
* @param {int} timeStartedFillingMS Time of form filling started.
*/
_onFormSubmit(profile, domWin, timeStartedFillingMS) {
_onFormSubmit(profile, domWin) {
let mm = this._messageManagerFromWindow(domWin);
mm.sendAsyncMessage("FormAutofill:OnFormSubmit",
{profile, timeStartedFillingMS});
mm.sendAsyncMessage("FormAutofill:OnFormSubmit", profile);
},
/**
@ -409,7 +407,7 @@ var FormAutofillContent = {
return true;
}
this._onFormSubmit(records, domWin, handler.timeStartedFillingMS);
this._onFormSubmit(records, domWin);
return true;
},

View File

@ -114,11 +114,6 @@ FormAutofillHandler.prototype = {
return this._formFieldCount != this.form.elements.length;
},
/**
* Time in milliseconds since epoch when a user started filling in the form.
*/
timeStartedFillingMS: null,
/**
* Set fieldDetails from the form about fields that can be autofilled.
*
@ -132,7 +127,6 @@ FormAutofillHandler.prototype = {
this._formFieldCount = this.form.elements.length;
let fieldDetails = FormAutofillHeuristics.getFormInfo(this.form, allowDuplicates);
this.fieldDetails = fieldDetails ? fieldDetails : [];
this.form.rootElement.addEventListener("input", this);
log.debug("Collected details on", this.fieldDetails.length, "fields");
this.address.fieldDetails = this.fieldDetails.filter(
@ -588,43 +582,4 @@ FormAutofillHandler.prototype = {
Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth});
});
},
/**
* Find the fieldDetail by HTML element (assume all details were collected in collectFormFields).
*
* @param {HTMLElement} element
*
* @returns {Object|null}
* Return fieldDetail if fieldDetail's element ref could match the target.
* (or return null if the element could not match any fieldDetail).
*/
getFieldDetailsForElement(element) {
for (let detail of this.fieldDetails) {
if (detail.elementWeakRef.get() == element) {
return detail;
}
}
return null;
},
handleEvent(event) {
switch (event.type) {
case "input":
if (!event.isTrusted) {
return;
}
if (!FormAutofillUtils.isFieldEligibleForAutofill(event.target)) {
return;
}
if (!this.getFieldDetailsForElement(event.target)) {
return;
}
this.form.rootElement.removeEventListener("input", this);
this.timeStartedFillingMS = Date.now();
break;
}
},
};

View File

@ -349,7 +349,7 @@ FormAutofillParent.prototype = {
this._updateStatus();
},
_onAddressSubmit(address, target, timeStartedFillingMS) {
_onAddressSubmit(address, target) {
if (address.guid) {
// Avoid updating the fields that users don't modify.
let originalAddress = this.profileStorage.addresses.get(address.guid);
@ -360,8 +360,6 @@ FormAutofillParent.prototype = {
}
if (!this.profileStorage.addresses.mergeIfPossible(address.guid, address.record)) {
this._recordFormFillingTime("address", "autofill-update", timeStartedFillingMS);
FormAutofillDoorhanger.show(target, "update").then((state) => {
let changedGUIDs = this.profileStorage.addresses.mergeToStorage(address.record);
switch (state) {
@ -385,7 +383,6 @@ FormAutofillParent.prototype = {
Services.telemetry.scalarAdd("formautofill.addresses.fill_type_autofill_update", 1);
return;
}
this._recordFormFillingTime("address", "autofill", timeStartedFillingMS);
this.profileStorage.addresses.notifyUsed(address.guid);
// Address is merged successfully
Services.telemetry.scalarAdd("formautofill.addresses.fill_type_autofill", 1);
@ -395,7 +392,6 @@ FormAutofillParent.prototype = {
changedGUIDs.push(this.profileStorage.addresses.add(address.record));
}
changedGUIDs.forEach(guid => this.profileStorage.addresses.notifyUsed(guid));
this._recordFormFillingTime("address", "manual", timeStartedFillingMS);
// Show first time use doorhanger
if (Services.prefs.getBoolPref("extensions.formautofill.firstTimeUse")) {
@ -439,28 +435,13 @@ FormAutofillParent.prototype = {
},
_onFormSubmit(data, target) {
let {profile: {address, creditCard}, timeStartedFillingMS} = data;
let {address, creditCard} = data;
if (address) {
this._onAddressSubmit(address, target, timeStartedFillingMS);
this._onAddressSubmit(address, target);
}
if (creditCard) {
this._onCreditCardSubmit(creditCard, target);
}
},
/**
* Set the probes for the filling time with specific filling type and form type.
*
* @private
* @param {string} formType
* 3 type of form (address/creditcard/address-creditcard).
* @param {string} fillingType
* 3 filling type (manual/autofill/autofill-update).
* @param {int} startedFillingMS
* Time that form started to filling in ms.
*/
_recordFormFillingTime(formType, fillingType, startedFillingMS) {
let histogram = Services.telemetry.getKeyedHistogramById("FORM_FILLING_REQUIRED_TIME_MS");
histogram.add(`${formType}-${fillingType}`, Date.now() - startedFillingMS);
},
};

View File

@ -1,5 +1,5 @@
This is the PDF.js project output, https://github.com/mozilla/pdf.js
Current extension version is: 1.9.489
Current extension version is: 1.9.512
Taken from upstream commit: b7fcaff0
Taken from upstream commit: 066fea9c

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1323,7 +1323,9 @@ let PDFViewerApplication = {
}
}
}
console.error(message + '\n' + moreInfoText);
Promise.all(moreInfoText).then(parts => {
console.error(message + '\n' + parts.join('\n'));
});
this.fallback();
},
progress(level) {
@ -4916,7 +4918,6 @@ class PDFPageView {
this.reset();
if (this.pdfPage) {
this.pdfPage.cleanup();
this.pdfPage = null;
}
}
_resetZoomLayer(removeFromDOM = false) {

View File

@ -128,6 +128,12 @@ def main():
continue
filename = m.group(1)
# mozalloc contains calls to memalign. These are ok, so we whitelist
# them.
if "mozalloc" in filename:
continue
fn = m.group(2)
if filename == 'jsutil.o':
jsutil_cpp.add(fn)
@ -188,4 +194,3 @@ def main():
if __name__ == '__main__':
main()

View File

@ -63,6 +63,7 @@ button {
.target-icon {
height: 24px;
width: 24px;
margin-inline-end: 5px;
}

View File

@ -26,8 +26,9 @@ add_task(function* () {
// Detach the tab with RDM open.
let newWindow = gBrowser.replaceTabWithWindow(tab);
// Waiting the tab is detached.
// Wait until the tab is detached and the new window is fully initialized.
yield waitTabIsDetached;
yield newWindow.delayedStartupPromise;
// Get the new tab instance.
tab = newWindow.gBrowser.tabs[0];

View File

@ -8,7 +8,7 @@
:root {
font: message-box;
--tab-line-selected-color: highlight;
--tab-line-selected-color: var(--blue-50);
}
:root.theme-light {

View File

@ -106,6 +106,7 @@
}
.devtools-tab-label {
font-size: 12px;
mask-image: linear-gradient(to left, transparent 0, black 6px);
/* Set the end padding on the label to make sure the label gets faded out properly */
padding-inline-end: 10px;

View File

@ -0,0 +1,538 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Cc, Ci, Cu } = require("chrome");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const Services = require("Services");
const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
const defer = require("devtools/shared/defer");
const events = require("devtools/shared/event-emitter");
const {
accessibleSpec,
accessibleWalkerSpec,
accessibilitySpec
} = require("devtools/shared/specs/accessibility");
const nsIAccessibleEvent = Ci.nsIAccessibleEvent;
const nsIAccessibleStateChangeEvent = Ci.nsIAccessibleStateChangeEvent;
const nsIPropertyElement = Ci.nsIPropertyElement;
const {
EVENT_TEXT_CHANGED,
EVENT_TEXT_INSERTED,
EVENT_TEXT_REMOVED,
EVENT_ACCELERATOR_CHANGE,
EVENT_ACTION_CHANGE,
EVENT_DEFACTION_CHANGE,
EVENT_DESCRIPTION_CHANGE,
EVENT_DOCUMENT_ATTRIBUTES_CHANGED,
EVENT_HELP_CHANGE,
EVENT_HIDE,
EVENT_NAME_CHANGE,
EVENT_OBJECT_ATTRIBUTE_CHANGED,
EVENT_REORDER,
EVENT_STATE_CHANGE,
EVENT_TEXT_ATTRIBUTE_CHANGED,
EVENT_VALUE_CHANGE
} = nsIAccessibleEvent;
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
/**
* Set of actors that expose accessibility tree information to the
* devtools protocol clients.
*
* The |Accessibility| actor is the main entry point. It is used to request
* an AccessibleWalker actor that caches the tree of Accessible actors.
*
* The |AccessibleWalker| actor is used to cache all seen Accessible actors as
* well as observe all relevant accesible events.
*
* The |Accessible| actor provides information about a particular accessible
* object, its properties, , attributes, states, relations, etc.
*/
/**
* The AccessibleActor provides information about a given accessible object: its
* role, name, states, etc.
*/
const AccessibleActor = ActorClassWithSpec(accessibleSpec, {
initialize(walker, rawAccessible) {
Actor.prototype.initialize.call(this, walker.conn);
this.walker = walker;
this.rawAccessible = rawAccessible;
/**
* Indicates if the raw accessible is no longer alive.
*
* @return Boolean
*/
Object.defineProperty(this, "isDefunct", {
get() {
let defunct = false;
try {
let extState = {};
this.rawAccessible.getState({}, extState);
// extState.value is a bitmask. We are applying bitwise AND to mask out
// irrelelvant states.
defunct = !!(extState.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT);
} catch (e) {
defunct = true;
}
if (defunct) {
delete this.isDefunct;
this.isDefunct = true;
return this.isDefunct;
}
return defunct;
},
configurable: true
});
},
/**
* Items returned by this actor should belong to the parent walker.
*/
marshallPool() {
return this.walker;
},
destroy() {
Actor.prototype.destroy.call(this);
this.walker = null;
this.rawAccessible = null;
},
get role() {
if (this.isDefunct) {
return null;
}
return this.walker.a11yService.getStringRole(this.rawAccessible.role);
},
get name() {
if (this.isDefunct) {
return null;
}
return this.rawAccessible.name;
},
get value() {
if (this.isDefunct) {
return null;
}
return this.rawAccessible.value;
},
get description() {
if (this.isDefunct) {
return null;
}
return this.rawAccessible.description;
},
get help() {
if (this.isDefunct) {
return null;
}
return this.rawAccessible.help;
},
get keyboardShortcut() {
if (this.isDefunct) {
return null;
}
return this.rawAccessible.keyboardShortcut;
},
get childCount() {
if (this.isDefunct) {
return 0;
}
return this.rawAccessible.childCount;
},
get domNodeType() {
if (this.isDefunct) {
return 0;
}
return this.rawAccessible.DOMNode ? this.rawAccessible.DOMNode.nodeType : 0;
},
children() {
let children = [];
if (this.isDefunct) {
return children;
}
for (let child = this.rawAccessible.firstChild; child; child = child.nextSibling) {
children.push(this.walker.addRef(child));
}
return children;
},
getIndexInParent() {
if (this.isDefunct) {
return -1;
}
return this.rawAccessible.indexInParent;
},
getActions() {
let actions = [];
if (this.isDefunct) {
return actions;
}
for (let i = 0; i < this.rawAccessible.actionCount; i++) {
actions.push(this.rawAccessible.getActionDescription(i));
}
return actions;
},
getState() {
if (this.isDefunct) {
return [];
}
let state = {};
let extState = {};
this.rawAccessible.getState(state, extState);
return [
...this.walker.a11yService.getStringStates(state.value, extState.value)
];
},
getAttributes() {
if (this.isDefunct || !this.rawAccessible.attributes) {
return {};
}
let attributes = {};
let attrsEnum = this.rawAccessible.attributes.enumerate();
while (attrsEnum.hasMoreElements()) {
let { key, value } = attrsEnum.getNext().QueryInterface(
nsIPropertyElement);
attributes[key] = value;
}
return attributes;
},
form() {
return {
actor: this.actorID,
role: this.role,
name: this.name,
value: this.value,
description: this.description,
help: this.help,
keyboardShortcut: this.keyboardShortcut,
childCount: this.childCount,
domNodeType: this.domNodeType,
walker: this.walker.form()
};
}
});
/**
* The AccessibleWalkerActor stores a cache of AccessibleActors that represent
* accessible objects in a given document.
*
* It is also responsible for implicitely initializing and shutting down
* accessibility engine by storing a reference to the XPCOM accessibility
* service.
*/
const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, {
initialize(conn, tabActor) {
Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
this.rootWin = tabActor.window;
this.rootDoc = tabActor.window.document;
this.refMap = new Map();
// Accessibility Walker should only be considered ready, when raw accessible
// object for root document is fully initialized (e.g. does not have a
// 'busy' state)
this.readyDeferred = defer();
DevToolsUtils.defineLazyGetter(this, "a11yService", () => {
Services.obs.addObserver(this, "accessible-event");
return Cc["@mozilla.org/accessibilityService;1"].getService(
Ci.nsIAccessibilityService);
});
this.onLoad = this.onLoad.bind(this);
this.onUnload = this.onUnload.bind(this);
events.on(tabActor, "will-navigate", this.onUnload);
events.on(tabActor, "window-ready", this.onLoad);
},
onUnload({ window }) {
let doc = window.document;
let actor = this.getRef(doc);
// If an accessible actor was never created for document, then there's
// nothing to clean up.
if (!actor) {
return;
}
// Purge document's subtree from accessible actors cache.
this.purgeSubtree(this.a11yService.getAccessibleFor(this.doc));
// If document is a root document, clear it's reference and cache.
if (this.rootDoc === doc) {
this.rootDoc = null;
this.refMap.clear();
this.readyDeferred = defer();
}
},
onLoad({ window, isTopLevel }) {
if (isTopLevel) {
// If root document is dead, unload it and clean up.
if (this.rootDoc && !Cu.isDeadWrapper(this.rootDoc) &&
this.rootDoc.defaultView) {
this.onUnload({ window: this.rootDoc.defaultView });
}
this.rootWin = window;
this.rootDoc = window.document;
}
},
destroy() {
if (this._destroyed) {
return;
}
this._destroyed = true;
try {
Services.obs.removeObserver(this, "accessible-event");
} catch (e) {
// Accessible event observer might not have been initialized if a11y
// service was never used.
}
// Clean up accessible actors cache.
if (this.refMap.size > 0) {
this.purgeSubtree(this.a11yService.getAccessibleFor(this.rootDoc));
this.refMap.clear();
}
events.off(this.tabActor, "will-navigate", this.onUnload);
events.off(this.tabActor, "window-ready", this.onLoad);
this.onLoad = null;
this.onUnload = null;
delete this.a11yService;
this.tabActor = null;
this.rootDoc = null;
this.refMap = null;
Actor.prototype.destroy.call(this);
},
getRef(rawAccessible) {
return this.refMap.get(rawAccessible);
},
addRef(rawAccessible) {
let actor = this.refMap.get(rawAccessible);
if (actor) {
return actor;
}
actor = new AccessibleActor(this, rawAccessible);
this.manage(actor);
this.refMap.set(rawAccessible, actor);
return actor;
},
/**
* Clean up accessible actors cache for a given accessible's subtree.
*
* @param {nsIAccessible} rawAccessible
*/
purgeSubtree(rawAccessible) {
let actor = this.getRef(rawAccessible);
if (actor && rawAccessible && !actor.isDefunct) {
for (let child = rawAccessible.firstChild; child; child = child.nextSibling) {
this.purgeSubtree(child);
}
}
this.refMap.delete(rawAccessible);
if (actor) {
events.emit(this, "accessible-destroy", actor);
actor.destroy();
}
},
/**
* A helper method. Accessibility walker is assumed to have only 1 child which
* is the top level document.
*/
children() {
return Promise.all([this.getDocument()]);
},
/**
* A promise for a root document accessible actor that only resolves when its
* corresponding document accessible object is fully loaded.
*
* @return {Promise}
*/
getDocument() {
let doc = this.addRef(this.a11yService.getAccessibleFor(this.rootDoc));
let states = doc.getState();
if (states.includes("busy")) {
return this.readyDeferred.promise.then(() => doc);
}
this.readyDeferred.resolve();
return Promise.resolve(doc);
},
getAccessibleFor(domNode) {
// We need to make sure that the document is loaded processed by a11y first.
return this.getDocument().then(() =>
this.addRef(this.a11yService.getAccessibleFor(domNode.rawNode)));
},
/**
* Accessible event observer function.
*
* @param {nsIAccessibleEvent} subject
* accessible event object.
*/
observe(subject) {
let event = subject.QueryInterface(nsIAccessibleEvent);
let rawAccessible = event.accessible;
let accessible = this.getRef(rawAccessible);
switch (event.eventType) {
case EVENT_STATE_CHANGE:
let { state, isEnabled } = event.QueryInterface(nsIAccessibleStateChangeEvent);
let states = [...this.a11yService.getStringStates(state, 0)];
if (states.includes("busy") && !isEnabled) {
let { DOMNode } = event;
// If debugging chrome, wait for top level content document loaded,
// otherwise wait for root document loaded.
if (DOMNode == this.rootDoc || (
this.rootDoc.documentElement.namespaceURI === XUL_NS &&
this.rootWin.gBrowser.selectedBrowser.contentDocument == DOMNode)) {
this.readyDeferred.resolve();
}
}
if (accessible) {
// Only propagate state change events for active accessibles.
if (states.includes("busy") && isEnabled) {
return;
}
events.emit(accessible, "state-change", accessible.getState());
}
break;
case EVENT_NAME_CHANGE:
if (accessible) {
events.emit(accessible, "name-change", rawAccessible.name,
event.DOMNode == this.rootDoc ?
undefined : this.getRef(rawAccessible.parent));
}
break;
case EVENT_VALUE_CHANGE:
if (accessible) {
events.emit(accessible, "value-change", rawAccessible.value);
}
break;
case EVENT_DESCRIPTION_CHANGE:
if (accessible) {
events.emit(accessible, "description-change", rawAccessible.description);
}
break;
case EVENT_HELP_CHANGE:
if (accessible) {
events.emit(accessible, "help-change", rawAccessible.help);
}
break;
case EVENT_REORDER:
if (accessible) {
events.emit(accessible, "reorder", rawAccessible.childCount);
}
break;
case EVENT_HIDE:
this.purgeSubtree(rawAccessible);
break;
case EVENT_DEFACTION_CHANGE:
case EVENT_ACTION_CHANGE:
if (accessible) {
events.emit(accessible, "actions-change", accessible.getActions());
}
break;
case EVENT_TEXT_CHANGED:
case EVENT_TEXT_INSERTED:
case EVENT_TEXT_REMOVED:
if (accessible) {
events.emit(accessible, "text-change");
}
break;
case EVENT_DOCUMENT_ATTRIBUTES_CHANGED:
case EVENT_OBJECT_ATTRIBUTE_CHANGED:
case EVENT_TEXT_ATTRIBUTE_CHANGED:
if (accessible) {
events.emit(accessible, "attributes-change", accessible.getAttributes());
}
break;
case EVENT_ACCELERATOR_CHANGE:
if (accessible) {
events.emit(accessible, "shortcut-change", rawAccessible.keyboardShortcut);
}
break;
default:
break;
}
}
});
/**
* The AccessibilityActor is a top level container actor that initializes
* accessible walker and is the top-most point of interaction for accessibility
* tools UI.
*/
const AccessibilityActor = ActorClassWithSpec(accessibilitySpec, {
initialize(conn, tabActor) {
Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
},
getWalker() {
if (!this.walker) {
this.walker = new AccessibleWalkerActor(this.conn, this.tabActor);
}
return this.walker;
},
destroy() {
Actor.prototype.destroy.call(this);
this.walker.destroy();
this.walker = null;
this.tabActor = null;
}
});
exports.AccessibleActor = AccessibleActor;
exports.AccessibleWalkerActor = AccessibleWalkerActor;
exports.AccessibilityActor = AccessibilityActor;

View File

@ -11,6 +11,7 @@ DIRS += [
]
DevToolsModules(
'accessibility.js',
'actor-registry.js',
'addon.js',
'addons.js',

View File

@ -585,6 +585,11 @@ var DebuggerServer = {
constructor: "WebExtensionInspectedWindowActor",
type: { tab: true }
});
this.registerModule("devtools/server/actors/accessibility", {
prefix: "accessibility",
constructor: "AccessibilityActor",
type: { tab: true }
});
},
/**

View File

@ -4,6 +4,7 @@ subsuite = devtools
support-files =
head.js
animation.html
doc_accessibility.html
doc_allocations.html
doc_force_cc.html
doc_force_gc.html
@ -25,6 +26,10 @@ support-files =
storage-helpers.js
!/devtools/server/tests/mochitest/hello-actor.js
[browser_accessibility_node_events.js]
[browser_accessibility_node.js]
[browser_accessibility_simple.js]
[browser_accessibility_walker.js]
[browser_animation_emitMutations.js]
[browser_animation_getFrames.js]
[browser_animation_getProperties.js]

View File

@ -0,0 +1,74 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Checks for the AccessibleActor
add_task(function* () {
let {client, walker, accessibility} =
yield initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
let a11yWalker = yield accessibility.getWalker(walker);
let buttonNode = yield walker.querySelector(walker.rootNode, "#button");
let accessibleFront = yield a11yWalker.getAccessibleFor(buttonNode);
checkA11yFront(accessibleFront, {
name: "Accessible Button",
role: "pushbutton",
value: "",
description: "Accessibility Test",
help: "",
keyboardShortcut: "",
childCount: 1,
domNodeType: 1
});
info("Actions");
let actions = yield accessibleFront.getActions();
is(actions.length, 1, "Accessible Front has correct number of actions");
is(actions[0], "Press", "Accessible Front default action is correct");
info("Index in parent");
let index = yield accessibleFront.getIndexInParent();
is(index, 1, "Accessible Front has correct index in parent");
info("State");
let state = yield accessibleFront.getState();
SimpleTest.isDeeply(state,
["focusable", "selectable text", "opaque", "enabled", "sensitive"],
"Accessible Front has correct states");
info("Attributes");
let attributes = yield accessibleFront.getAttributes();
SimpleTest.isDeeply(attributes, {
"margin-top": "0px",
display: "inline-block",
"text-align": "center",
"text-indent": "0px",
"margin-left": "0px",
tag: "button",
"margin-right": "0px",
id: "button",
"margin-bottom": "0px"
}, "Accessible Front has correct attributes");
info("Children");
let children = yield accessibleFront.children();
is(children.length, 1, "Accessible Front has correct number of children");
checkA11yFront(children[0], {
name: "Accessible Button",
role: "text leaf"
});
info("DOM Node");
let node = yield accessibleFront.getDOMNode(walker);
is(node, buttonNode, "Accessible Front has correct DOM node");
let a11yShutdown = waitForA11yShutdown();
yield client.close();
forceCollections();
yield a11yShutdown;
gBrowser.removeCurrentTab();
});

View File

@ -0,0 +1,93 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Checks for the AccessibleActor events
add_task(function* () {
let {client, walker, accessibility} =
yield initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
let a11yWalker = yield accessibility.getWalker(walker);
let a11yDoc = yield a11yWalker.getDocument();
let buttonNode = yield walker.querySelector(walker.rootNode, "#button");
let accessibleFront = yield a11yWalker.getAccessibleFor(buttonNode);
let sliderNode = yield walker.querySelector(walker.rootNode, "#slider");
let accessibleSliderFront = yield a11yWalker.getAccessibleFor(sliderNode);
let browser = gBrowser.selectedBrowser;
checkA11yFront(accessibleFront, {
name: "Accessible Button",
role: "pushbutton",
value: "",
description: "Accessibility Test",
help: "",
keyboardShortcut: "",
childCount: 1,
domNodeType: 1
});
info("Name change event");
yield emitA11yEvent(accessibleFront, "name-change",
(name, parent) => {
checkA11yFront(accessibleFront, { name: "Renamed" });
checkA11yFront(parent, { }, a11yDoc);
}, () => ContentTask.spawn(browser, null, () =>
content.document.getElementById("button").setAttribute(
"aria-label", "Renamed")));
info("Description change event");
yield emitA11yEvent(accessibleFront, "description-change",
() => checkA11yFront(accessibleFront, { description: "" }),
() => ContentTask.spawn(browser, null, () =>
content.document.getElementById("button").removeAttribute("aria-describedby")));
info("State change event");
let states = yield accessibleFront.getState();
let expectedStates = ["unavailable", "selectable text", "opaque"];
SimpleTest.isDeeply(states, ["focusable", "selectable text", "opaque",
"enabled", "sensitive"], "States are correct");
yield emitA11yEvent(accessibleFront, "state-change",
newStates => SimpleTest.isDeeply(newStates, expectedStates,
"States are updated"),
() => ContentTask.spawn(browser, null, () =>
content.document.getElementById("button").setAttribute("disabled", true)));
states = yield accessibleFront.getState();
SimpleTest.isDeeply(states, expectedStates, "States are updated");
info("Attributes change event");
let attrs = yield accessibleFront.getAttributes();
ok(!attrs.live, "Attribute is not present");
yield emitA11yEvent(accessibleFront, "attributes-change",
newAttrs => is(newAttrs.live, "polite", "Attributes are updated"),
() => ContentTask.spawn(browser, null, () =>
content.document.getElementById("button").setAttribute("aria-live", "polite")));
attrs = yield accessibleFront.getAttributes();
is(attrs.live, "polite", "Attributes are updated");
info("Value change event");
checkA11yFront(accessibleSliderFront, { value: "5" });
yield emitA11yEvent(accessibleSliderFront, "value-change",
() => checkA11yFront(accessibleSliderFront, { value: "6" }),
() => ContentTask.spawn(browser, null, () =>
content.document.getElementById("slider").setAttribute("aria-valuenow", "6")));
info("Reorder event");
is(accessibleSliderFront.childCount, 1, "Slider has only 1 child");
yield emitA11yEvent(accessibleSliderFront, "reorder",
childCount => is(childCount, 2, "Child count is updated"),
() => ContentTask.spawn(browser, null, () => {
let button = content.document.createElement("button");
button.innerText = "Slider button";
content.document.getElementById("slider").appendChild(button);
}));
is(accessibleSliderFront.childCount, 2, "Child count is updated");
let a11yShutdown = waitForA11yShutdown();
yield client.close();
forceCollections();
yield a11yShutdown;
gBrowser.removeCurrentTab();
});

View File

@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Simple checks for the AccessibilityActor and AccessibleWalkerActor
add_task(function* () {
let {client, accessibility} = yield initAccessibilityFrontForUrl(
"data:text/html;charset=utf-8,<title>test</title><div></div>");
ok(accessibility, "The AccessibilityFront was created");
ok(accessibility.getWalker, "The getWalker method exists");
let a11yWalker = yield accessibility.getWalker();
ok(a11yWalker, "The AccessibleWalkerFront was returned");
yield client.close();
gBrowser.removeCurrentTab();
});

View File

@ -0,0 +1,75 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Checks for the AccessibleWalkerActor
add_task(function* () {
let {client, walker, accessibility} =
yield initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility.html");
let a11yWalker = yield accessibility.getWalker(walker);
ok(a11yWalker, "The AccessibleWalkerFront was returned");
let a11yDoc = yield a11yWalker.getDocument();
ok(a11yDoc, "The AccessibleFront for root doc is created");
let children = yield a11yWalker.children();
is(children.length, 1,
"AccessibleWalker only has 1 child - root doc accessible");
is(a11yDoc, children[0],
"Root accessible must be AccessibleWalker's only child");
let buttonNode = yield walker.querySelector(walker.rootNode, "#button");
let accessibleFront = yield a11yWalker.getAccessibleFor(buttonNode);
checkA11yFront(accessibleFront, {
name: "Accessible Button",
role: "pushbutton"
});
let browser = gBrowser.selectedBrowser;
// Ensure name-change event is emitted by walker when cached accessible's name
// gets updated (via DOM manipularion).
yield emitA11yEvent(a11yWalker, "name-change",
(front, parent) => {
checkA11yFront(front, { name: "Renamed" }, accessibleFront);
checkA11yFront(parent, { }, a11yDoc);
},
() => ContentTask.spawn(browser, null, () =>
content.document.getElementById("button").setAttribute(
"aria-label", "Renamed")));
// Ensure reorder event is emitted by walker when DOM tree changes.
let docChildren = yield a11yDoc.children();
is(docChildren.length, 3, "Root doc should have correct number of children");
yield emitA11yEvent(a11yWalker, "reorder",
front => checkA11yFront(front, { }, a11yDoc),
() => ContentTask.spawn(browser, null, () => {
let input = content.document.createElement("input");
input.type = "text";
input.title = "This is a tooltip";
input.value = "New input";
content.document.body.appendChild(input);
}));
docChildren = yield a11yDoc.children();
is(docChildren.length, 4, "Root doc should have correct number of children");
// Ensure destory event is emitted by walker when cached accessible's raw
// accessible gets destroyed.
yield emitA11yEvent(a11yWalker, "accessible-destroy",
destroyedFront => checkA11yFront(destroyedFront, { }, accessibleFront),
() => ContentTask.spawn(browser, null, () =>
content.document.getElementById("button").remove()));
let a11yShutdown = waitForA11yShutdown();
yield client.close();
forceCollections();
yield a11yShutdown;
gBrowser.removeCurrentTab();
});

View File

@ -0,0 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1 id="h1">Accessibility Test</h1>
<button id="button" aria-describedby="h1">Accessible Button</button>
<div id="slider" role="slider" aria-valuenow="5"
aria-valuemin="0" aria-valuemax="7">slider</div>
</body>
</html>

View File

@ -80,6 +80,22 @@ function* initLayoutFrontForUrl(url) {
return {inspector, walker, layout, client};
}
function* initAccessibilityFrontForUrl(url) {
const {AccessibilityFront} = require("devtools/shared/fronts/accessibility");
const {InspectorFront} = require("devtools/shared/fronts/inspector");
yield addTab(url);
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());
let form = yield connectDebuggerClient(client);
let inspector = InspectorFront(client, form);
let walker = yield inspector.getWalker();
let accessibility = AccessibilityFront(client, form);
return {inspector, walker, accessibility, client};
}
function initDebuggerServer() {
try {
// Sometimes debugger server does not get destroyed correctly by previous
@ -236,3 +252,53 @@ function waitForMarkerType(front, types, predicate,
function getCookieId(name, domain, path) {
return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`;
}
/**
* Trigger DOM activity and wait for the corresponding accessibility event.
* @param {Object} emitter Devtools event emitter, usually a front.
* @param {Sting} name Accessibility event in question.
* @param {Function} handler Accessibility event handler function with checks.
* @param {Promise} task A promise that resolves when DOM activity is done.
*/
async function emitA11yEvent(emitter, name, handler, task) {
let promise = emitter.once(name, handler);
await task();
await promise;
}
/**
* Check that accessibilty front is correct and its attributes are also
* up-to-date.
* @param {Object} front Accessibility front to be tested.
* @param {Object} expected A map of a11y front properties to be verified.
* @param {Object} expectedFront Expected accessibility front.
*/
function checkA11yFront(front, expected, expectedFront) {
ok(front, "The accessibility front is created");
if (expectedFront) {
is(front, expectedFront, "Matching accessibility front");
}
for (let key in expected) {
is(front[key], expected[key], `accessibility front has correct ${key}`);
}
}
/**
* Wait for accessibility service to shut down. We consider it shut down when
* an "a11y-init-or-shutdown" event is received with a value of "0".
*/
async function waitForA11yShutdown() {
await ContentTask.spawn(gBrowser.selectedBrowser, {}, () =>
new Promise(resolve => {
let observe = (subject, topic, data) => {
Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
if (data === "0") {
resolve();
}
};
Services.obs.addObserver(observe, "a11y-init-or-shutdown");
}));
}

View File

@ -0,0 +1,136 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const {
Front,
FrontClassWithSpec,
preEvent,
types
} = require("devtools/shared/protocol.js");
const {
accessibleSpec,
accessibleWalkerSpec,
accessibilitySpec
} = require("devtools/shared/specs/accessibility");
const events = require("devtools/shared/event-emitter");
const ACCESSIBLE_PROPERTIES = [
"role",
"name",
"value",
"description",
"help",
"keyboardShortcut",
"childCount",
"domNodeType"
];
const AccessibleFront = FrontClassWithSpec(accessibleSpec, {
initialize(client, form) {
Front.prototype.initialize.call(this, client, form);
// Define getters for accesible properties that are received from the actor.
// Note: we would like accessible properties to be iterable for a11y
// clients.
for (let key of ACCESSIBLE_PROPERTIES) {
Object.defineProperty(this, key, {
get() {
return this._form[key];
},
enumerable: true
});
}
},
marshallPool() {
return this.walker;
},
form(form, detail) {
if (detail === "actorid") {
this.actorID = form;
return;
}
this.actorID = form.actor;
this._form = form;
DevToolsUtils.defineLazyGetter(this, "walker", () =>
types.getType("accessiblewalker").read(this._form.walker, this));
},
/**
* Get a dom node front from accessible actor's raw accessible object's
* DONNode property.
*/
getDOMNode(domWalker) {
return domWalker.getNodeFromActor(this.actorID,
["rawAccessible", "DOMNode"]);
},
nameChange: preEvent("name-change", function (name, parent) {
this._form.name = name;
// Name change event affects the tree rendering, we fire this event on
// accessibility walker as the point of interaction for UI.
if (this.walker) {
events.emit(this.walker, "name-change", this, parent);
}
}),
valueChange: preEvent("value-change", function (value) {
this._form.value = value;
}),
descriptionChange: preEvent("description-change", function (description) {
this._form.description = description;
}),
helpChange: preEvent("help-change", function (help) {
this._form.help = help;
}),
shortcutChange: preEvent("shortcut-change", function (keyboardShortcut) {
this._form.keyboardShortcut = keyboardShortcut;
}),
reorder: preEvent("reorder", function (childCount) {
this._form.childCount = childCount;
// Reorder event affects the tree rendering, we fire this event on
// accessibility walker as the point of interaction for UI.
if (this.walker) {
events.emit(this.walker, "reorder", this);
}
}),
textChange: preEvent("text-change", function () {
// Text event affects the tree rendering, we fire this event on
// accessibility walker as the point of interaction for UI.
if (this.walker) {
events.emit(this.walker, "text-change", this);
}
})
});
const AccessibleWalkerFront = FrontClassWithSpec(accessibleWalkerSpec, {
accessibleDestroy: preEvent("accessible-destroy", function (accessible) {
accessible.destroy();
}),
form(json) {
this.actorID = json.actor;
}
});
const AccessibilityFront = FrontClassWithSpec(accessibilitySpec, {
initialize(client, form) {
Front.prototype.initialize.call(this, client, form);
this.actorID = form.accessibilityActor;
this.manage(this);
}
});
exports.AccessibleFront = AccessibleFront;
exports.AccessibleWalkerFront = AccessibleWalkerFront;
exports.AccessibilityFront = AccessibilityFront;

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'accessibility.js',
'actor-registry.js',
'addons.js',
'animation.js',

View File

@ -0,0 +1,141 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const protocol = require("devtools/shared/protocol");
const { Arg, generateActorSpec, RetVal, types } = protocol;
// eslint-disable-next-line no-unused-vars
const { nodeSpec } = require("devtools/shared/specs/inspector");
types.addActorType("accessible");
const accessibleSpec = generateActorSpec({
typeName: "accessible",
events: {
"actions-change": {
type: "actionsChange",
actions: Arg(0, "array:string")
},
"name-change": {
type: "nameChange",
name: Arg(0, "string"),
parent: Arg(1, "nullable:accessible")
},
"value-change": {
type: "valueChange",
value: Arg(0, "string")
},
"description-change": {
type: "descriptionChange",
description: Arg(0, "string")
},
"state-change": {
type: "stateChange",
states: Arg(0, "array:string")
},
"attributes-change": {
type: "attributesChange",
states: Arg(0, "json")
},
"help-change": {
type: "helpChange",
help: Arg(0, "string")
},
"shortcut-change": {
type: "shortcutChange",
shortcut: Arg(0, "string")
},
"reorder": {
type: "reorder",
childCount: Arg(0, "number")
},
"text-change": {
type: "textChange"
}
},
methods: {
getActions: {
request: {},
response: {
actions: RetVal("array:string")
}
},
getIndexInParent: {
request: {},
response: {
indexInParent: RetVal("number")
}
},
getState: {
request: {},
response: {
states: RetVal("array:string")
}
},
getAttributes: {
request: {},
response: {
attributes: RetVal("json")
}
},
children: {
request: {},
response: {
children: RetVal("array:accessible")
}
}
}
});
const accessibleWalkerSpec = generateActorSpec({
typeName: "accessiblewalker",
events: {
"accessible-destroy": {
type: "accessibleDestroy",
accessible: Arg(0, "accessible")
}
},
methods: {
children: {
request: {},
response: {
children: RetVal("array:accessible")
}
},
getDocument: {
request: {},
response: {
document: RetVal("accessible")
}
},
getAccessibleFor: {
request: { node: Arg(0, "domnode") },
response: {
accessible: RetVal("accessible")
}
}
}
});
const accessibilitySpec = generateActorSpec({
typeName: "accessibility",
methods: {
getWalker: {
request: {},
response: {
walker: RetVal("accessiblewalker")
}
}
}
});
exports.accessibleSpec = accessibleSpec;
exports.accessibleWalkerSpec = accessibleWalkerSpec;
exports.accessibilitySpec = accessibilitySpec;

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'accessibility.js',
'actor-registry.js',
'addons.js',
'animation.js',

View File

@ -58,7 +58,7 @@ SubtleCrypto::RecordTelemetryOnce() {
RefPtr<WebCryptoTask> task = \
WebCryptoTask::Create ## Operation ## Task(__VA_ARGS__); \
if (!task) { \
aRv.Throw(NS_ERROR_OUT_OF_MEMORY); \
aRv.Throw(NS_ERROR_NULL_POINTER); \
return nullptr; \
} \
task->DispatchWithPromise(p); \

View File

@ -305,6 +305,7 @@ bool nsContentUtils::sGetBoxQuadsEnabled = false;
bool nsContentUtils::sSkipCursorMoveForSameValueSet = false;
bool nsContentUtils::sRequestIdleCallbackEnabled = false;
bool nsContentUtils::sLowerNetworkPriority = false;
bool nsContentUtils::sTailingEnabled = false;
bool nsContentUtils::sShowInputPlaceholderOnFocus = true;
bool nsContentUtils::sAutoFocusEnabled = true;
#ifndef RELEASE_OR_BETA
@ -769,6 +770,9 @@ nsContentUtils::Init()
Preferences::AddBoolVarCache(&sLowerNetworkPriority,
"privacy.trackingprotection.lower_network_priority", false);
Preferences::AddBoolVarCache(&sTailingEnabled,
"network.http.tailing.enabled", true);
Preferences::AddBoolVarCache(&sShowInputPlaceholderOnFocus,
"dom.placeholder.show_on_focus", true);
@ -3085,22 +3089,15 @@ nsContentUtils::GenerateStateKey(nsIContent* aContent,
/* aDeep = */ true,
/* aLiveList = */ false);
}
RefPtr<nsContentList> htmlFormControls = htmlDoc->GetExistingFormControls();
if (!htmlFormControls) {
// If the document doesn't have an existing form controls content list,
// create a new one, but avoid creating a live list since we only need to
// use the list here and it doesn't need to listen to mutation events.
htmlFormControls = new nsContentList(aDocument,
nsHTMLDocument::MatchFormControls,
nullptr, nullptr,
/* aDeep = */ true,
/* aMatchAtom = */ nullptr,
/* aMatchNameSpaceId = */ kNameSpaceID_None,
/* aFuncMayDependOnAttr = */ true,
/* aLiveList = */ false);
}
NS_ENSURE_TRUE(htmlForms && htmlFormControls, NS_ERROR_OUT_OF_MEMORY);
RefPtr<nsContentList> htmlFormControls =
new nsContentList(aDocument,
nsHTMLDocument::MatchFormControls,
nullptr, nullptr,
/* aDeep = */ true,
/* aMatchAtom = */ nullptr,
/* aMatchNameSpaceId = */ kNameSpaceID_None,
/* aFuncMayDependOnAttr = */ true,
/* aLiveList = */ false);
// If we have a form control and can calculate form information, use that
// as the key - it is more reliable than just recording position in the
@ -3117,7 +3114,7 @@ nsContentUtils::GenerateStateKey(nsIContent* aContent,
// XXXbz We don't? Why not? I don't follow.
//
nsCOMPtr<nsIFormControl> control(do_QueryInterface(aContent));
if (control && htmlFormControls && htmlForms) {
if (control) {
// Append the control type
KeyAppendInt(control->ControlType(), aKey);

View File

@ -3110,6 +3110,9 @@ public:
// if we want to lower the priority of the channel.
static bool IsLowerNetworkPriority() { return sLowerNetworkPriority; }
// Whether tracker tailing is turned on - "network.http.tailing.enabled".
static bool IsTailingEnabled() { return sTailingEnabled; }
// Check pref "dom.placeholder.show_on_focus" to see
// if we want to show the placeholder inside input elements
// when they have focus.
@ -3301,6 +3304,7 @@ private:
static bool sSkipCursorMoveForSameValueSet;
static bool sRequestIdleCallbackEnabled;
static bool sLowerNetworkPriority;
static bool sTailingEnabled;
static bool sShowInputPlaceholderOnFocus;
static bool sAutoFocusEnabled;
#ifndef RELEASE_OR_BETA

View File

@ -69,7 +69,7 @@ add_task(async function test_swap_frameloader_pagevisibility_events() {
// We have to wait for the window to load so we can get the selected browser
// to listen to.
await BrowserTestUtils.waitForEvent(newWindow, "load");
await BrowserTestUtils.waitForEvent(newWindow, "DOMContentLoaded");
let newWindowBrowser = newWindow.gBrowser.selectedBrowser;
// Wait for the expected pagehide and pageshow events on the initial browser

View File

@ -14957,9 +14957,15 @@ class CGBindingImplClass(CGClass):
if m.isMethod():
if m.isIdentifierLess():
continue
if m.isMaplikeOrSetlikeOrIterableMethod():
# Handled by generated code already
continue
if not m.isStatic() or not skipStaticMethods:
appendMethod(m)
elif m.isAttr():
if m.isMaplikeOrSetlikeAttr():
# Handled by generated code already
continue
self.methodDecls.append(cgGetter(descriptor, m))
if not m.readonly:
self.methodDecls.append(cgSetter(descriptor, m))
@ -15213,8 +15219,9 @@ class CGExampleRoot(CGThing):
continue
if member.isStatic():
builder.addInMozillaDom("GlobalObject")
if member.isAttr() and not member.isMaplikeOrSetlikeAttr():
builder.forwardDeclareForType(member.type, config)
if member.isAttr():
if not member.isMaplikeOrSetlikeAttr():
builder.forwardDeclareForType(member.type, config)
else:
assert member.isMethod()
if not member.isMaplikeOrSetlikeOrIterableMethod():
@ -15563,6 +15570,17 @@ class CGJSImplClass(CGBindingImplClass):
static=True,
body=self.getCreateFromExistingBody()))
if (descriptor.interface.isJSImplemented() and
descriptor.interface.maplikeOrSetlikeOrIterable and
descriptor.interface.maplikeOrSetlikeOrIterable.isMaplike()):
self.methodDecls.append(
ClassMethod("__OnGet",
"void",
[Argument("JS::Handle<JS::Value>", "aKey"),
Argument("JS::Handle<JS::Value>", "aValue"),
Argument("ErrorResult&", "aRv")],
body="mImpl->__OnGet(aKey, aValue, aRv);\n"))
CGClass.__init__(self, descriptor.name,
bases=baseClasses,
constructors=[constructor],
@ -15909,25 +15927,46 @@ class CGFastCallback(CGClass):
class CGCallbackInterface(CGCallback):
def __init__(self, descriptor, spiderMonkeyInterfacesAreStructs=False):
iface = descriptor.interface
attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()]
attrs = [m for m in iface.members
if (m.isAttr() and not m.isStatic() and
(not m.isMaplikeOrSetlikeAttr() or
not iface.isJSImplemented()))]
getters = [CallbackGetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
for a in attrs]
setters = [CallbackSetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
for a in attrs if not a.readonly]
methods = [m for m in iface.members
if m.isMethod() and not m.isStatic() and not m.isIdentifierLess()]
if (m.isMethod() and not m.isStatic() and
not m.isIdentifierLess() and
(not m.isMaplikeOrSetlikeOrIterableMethod() or
not iface.isJSImplemented()))]
methods = [CallbackOperation(m, sig, descriptor, spiderMonkeyInterfacesAreStructs)
for m in methods for sig in m.signatures()]
needInitId = False
if iface.isJSImplemented() and iface.ctor():
sigs = descriptor.interface.ctor().signatures()
if len(sigs) != 1:
raise TypeError("We only handle one constructor. See bug 869268.")
methods.append(CGJSImplInitOperation(sigs[0], descriptor))
if any(m.isAttr() or m.isMethod() for m in iface.members) or (iface.isJSImplemented() and iface.ctor()):
methods.append(initIdsClassMethod([descriptor.binaryNameFor(m.identifier.name)
for m in iface.members
if m.isAttr() or m.isMethod()] +
(["__init"] if iface.isJSImplemented() and iface.ctor() else []),
needInitId = True
needOnGetId = False
if (iface.isJSImplemented() and
iface.maplikeOrSetlikeOrIterable and
iface.maplikeOrSetlikeOrIterable.isMaplike()):
methods.append(CGJSImplOnGetOperation(descriptor))
needOnGetId = True
idlist = [descriptor.binaryNameFor(m.identifier.name)
for m in iface.members
if m.isAttr() or m.isMethod()]
if needInitId:
idlist.append("__init")
if needOnGetId:
idlist.append("__onget")
if len(idlist) != 0:
methods.append(initIdsClassMethod(idlist,
iface.identifier.name + "Atoms"))
CGCallback.__init__(self, iface, descriptor, "CallbackInterface",
methods, getters=getters, setters=setters)
@ -16446,6 +16485,32 @@ class CGJSImplInitOperation(CallbackOperationBase):
return "__init"
class CGJSImplOnGetOperation(CallbackOperationBase):
"""
Codegen the __OnGet() method used to notify the JS impl that a get() is
happening on a JS-implemented maplike. This method takes two arguments
(key and value) and returns nothing.
"""
def __init__(self, descriptor):
CallbackOperationBase.__init__(
self,
(BuiltinTypes[IDLBuiltinType.Types.void],
[FakeArgument(BuiltinTypes[IDLBuiltinType.Types.any],
None,
"key"),
FakeArgument(BuiltinTypes[IDLBuiltinType.Types.any],
None,
"value")]),
"__onget", "__OnGet",
descriptor,
singleOperation=False,
rethrowContentException=True,
spiderMonkeyInterfacesAreStructs=True)
def getPrettyName(self):
return "__onget"
def getMaplikeOrSetlikeErrorReturn(helperImpl):
"""
Generate return values based on whether a maplike or setlike generated
@ -16728,7 +16793,21 @@ class CGMaplikeOrSetlikeMethodGenerator(CGThing):
JS::Rooted<JS::Value> result(cx);
"""))]
arguments = ["&result"]
return self.mergeTuples(r, (code, arguments, []))
if self.descriptor.interface.isJSImplemented():
callOnGet = [CGGeneric(dedent(
"""
{
JS::ExposeValueToActiveJS(result);
ErrorResult onGetResult;
self->__OnGet(arg0Val, result, onGetResult);
if (onGetResult.MaybeSetPendingException(cx)) {
return false;
}
}
"""))]
else:
callOnGet = []
return self.mergeTuples(r, (code, arguments, callOnGet))
def has(self):
"""
@ -17028,6 +17107,11 @@ class GlobalGenRoots():
if d.interface.isJSImplemented() and d.interface.ctor():
# We'll have an __init() method.
members.append(FakeMember('__init'))
if (d.interface.isJSImplemented() and
d.interface.maplikeOrSetlikeOrIterable and
d.interface.maplikeOrSetlikeOrIterable.isMaplike()):
# We'll have an __onget() method.
members.append(FakeMember('__onget'))
if len(members) == 0:
continue

View File

@ -32,7 +32,11 @@ TestInterfaceJSMaplike.prototype = {
clearInternal: function() {
return this.__DOM_IMPL__.__clear();
}
},
__onget: function(key, value) {
/* no-op */
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestInterfaceJSMaplike])

View File

@ -378,10 +378,12 @@ FetchDriver::HttpFetch()
}
}
if (mIsTrackingFetch && nsContentUtils::IsLowerNetworkPriority()) {
if (mIsTrackingFetch && nsContentUtils::IsTailingEnabled()) {
cos->AddClassFlags(nsIClassOfService::Throttleable |
nsIClassOfService::Tail);
}
if (mIsTrackingFetch && nsContentUtils::IsLowerNetworkPriority()) {
nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(chan);
if (p) {
p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);

View File

@ -203,7 +203,6 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(nsHTMLDocument, nsDocument,
mAnchors,
mScripts,
mForms,
mFormControls,
mWyciwygChannel,
mMidasCommandManager)
@ -3719,7 +3718,6 @@ nsHTMLDocument::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const
// - mAnchors
// - mScripts
// - mForms
// - mFormControls
// - mWyciwygChannel
// - mMidasCommandManager
}

View File

@ -82,11 +82,6 @@ public:
return mForms;
}
nsContentList* GetExistingFormControls() const
{
return mFormControls;
}
// nsIDOMDocument interface
using nsDocument::CreateElement;
using nsDocument::CreateElementNS;
@ -324,7 +319,6 @@ protected:
RefPtr<nsContentList> mAnchors;
RefPtr<nsContentList> mScripts;
RefPtr<nsContentList> mForms;
RefPtr<nsContentList> mFormControls;
RefPtr<mozilla::dom::HTMLAllCollection> mAll;

View File

@ -327,6 +327,10 @@ class RTCStatsReport {
}
get mozPcid() { return this._pcid; }
__onget(key, value) {
/* Do whatever here */
}
}
setupPrototype(RTCStatsReport, {
classID: PC_STATS_CID,

View File

@ -280,10 +280,6 @@ RTCCertificate::GenerateCertificate(
RefPtr<WebCryptoTask> task =
new GenerateRTCCertificateTask(global, aGlobal.Context(),
aOptions, usages, expires);
if (!task) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
task->DispatchWithPromise(p);
return p.forget();
}

View File

@ -40,7 +40,6 @@
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/Mutex.h"
#include "mozilla/LazyIdleThread.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
@ -3593,11 +3592,10 @@ QuotaManager::Init(const nsAString& aBasePath)
return rv;
}
// Make a lazy thread for any IO we need (like clearing or enumerating the
// contents of storage directories).
mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
NS_LITERAL_CSTRING("Storage I/O"),
LazyIdleThread::ManualShutdown);
rv = NS_NewNamedThread("QuotaManager IO", getter_AddRefs(mIOThread));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Make a timer here to avoid potential failures later. We don't actually
// initialize the timer until shutdown.

View File

@ -1067,7 +1067,12 @@ ScriptLoader::StartLoad(ScriptLoadRequest* aRequest)
// synchronous head scripts block loading of most other non js/css
// content such as images, Leader implicitely disallows tailing
cos->AddClassFlags(nsIClassOfService::Leader);
} else if (defer && !async) {
} else if (defer && (!async || !nsContentUtils::IsTailingEnabled())) {
// Bug 1395525 and the !nsContentUtils::IsTailingEnabled() bit:
// We want to make sure that turing tailing off by the pref makes
// the browser behave exactly the same way as before landing
// the tailing patch, which has added the "&& !async" part.
// head/body deferred scripts are blocked by leaders but are not
// allowed tailing because they block DOMContentLoaded
cos->AddClassFlags(nsIClassOfService::TailForbidden);

View File

@ -283,41 +283,53 @@ struct Orientation
};
static Orientation
RotationVectorToOrientation(double aX, double aY, double aZ, double aW)
{
static const double kFuzzyOne = 1.0 - 1e-6;
static const double kCircleRad = 2.0 * M_PI;
RotationVectorToOrientation(double aX, double aY, double aZ, double aW) {
double mat[9];
Orientation orient = { 2.0 * std::atan2(aY, aW),
M_PI_2,
0.0 };
mat[0] = 1 - 2*aY*aY - 2*aZ*aZ;
mat[1] = 2*aX*aY - 2*aZ*aW;
mat[2] = 2*aX*aZ + 2*aY*aW;
const double sqX = aX * aX;
const double sqY = aY * aY;
const double sqZ = aZ * aZ;
const double sqW = aW * aW;
const double unitLength = sqX + sqY + sqZ + sqW;
const double xwyz = 2.0 * (aX * aW + aY * aZ) / unitLength;
mat[3] = 2*aX*aY + 2*aZ*aW;
mat[4] = 1 - 2*aX*aX - 2*aZ*aZ;
mat[5] = 2*aY*aZ - 2*aX*aW;
if (xwyz < -kFuzzyOne) {
orient.alpha *= -1.0;
orient.beta *= -1.0;
} else if (xwyz <= kFuzzyOne) {
const double gammaX = -sqX - sqY + sqZ + sqW;
const double gammaY = 2.0 * (aY * aW - aX * aZ);
const double alphaX = -sqX + sqY - sqZ + sqW;
const double alphaY = 2.0 * (aZ * aW - aX * aY);
const double fac = gammaX > 0 ? 1.0 : -1.0;
mat[6] = 2*aX*aZ - 2*aY*aW;
mat[7] = 2*aY*aZ + 2*aX*aW;
mat[8] = 1 - 2*aX*aX - 2*aY*aY;
orient.alpha = std::fmod(kCircleRad + std::atan2(fac * alphaY, fac * alphaX),
kCircleRad);
orient.beta = fac * std::asin(xwyz);
orient.gamma = std::atan2(fac * gammaY, fac * gammaX);
if (fac < 0.0) {
orient.beta = fmod(M_PI + orient.beta, M_PI);
Orientation orient;
if (mat[8] > 0) {
orient.alpha = atan2(-mat[1], mat[4]);
orient.beta = asin(mat[7]);
orient.gamma = atan2(-mat[6], mat[8]);
} else if (mat[8] < 0) {
orient.alpha = atan2(mat[1], -mat[4]);
orient.beta = -asin(mat[7]);
orient.beta += (orient.beta >= 0) ? -M_PI : M_PI;
orient.gamma = atan2(mat[6], -mat[8]);
} else {
if (mat[6] > 0) {
orient.alpha = atan2(-mat[1], mat[4]);
orient.beta = asin(mat[7]);
orient.gamma = -M_PI_2;
} else if (mat[6] < 0) {
orient.alpha = atan2(mat[1], -mat[4]);
orient.beta = -asin(mat[7]);
orient.beta += (orient.beta >= 0) ? -M_PI : M_PI;
orient.gamma = -M_PI_2;
} else {
orient.alpha = atan2(mat[3], mat[0]);
orient.beta = (mat[7] > 0) ? M_PI_2 : -M_PI_2;
orient.gamma = 0;
}
}
if (orient.alpha < 0) {
orient.alpha += 2*M_PI;
}
return Orientation::RadToDeg(orient);
}

View File

@ -0,0 +1,9 @@
[package]
name = "libudev-sys"
version = "0.1.3"
authors = ["Tim Taubert <ttaubert@mozilla.com>"]
description = "FFI bindings to libudev"
[dependencies]
lazy_static = "0.2"
libc = "0.2"

View File

@ -0,0 +1,180 @@
/* -*- Mode: rust; rust-indent-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/. */
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#[macro_use]
extern crate lazy_static;
extern crate libc;
use libc::{c_void,c_int,c_char,c_ulonglong,dev_t};
use libc::{RTLD_GLOBAL,RTLD_LAZY,RTLD_NOLOAD};
use libc::{dlopen,dlclose,dlsym};
use std::ffi::CString;
use std::{marker,mem,ops,ptr};
#[repr(C)]
pub struct udev {
__private: c_void
}
#[repr(C)]
pub struct udev_list_entry {
__private: c_void
}
#[repr(C)]
pub struct udev_device {
__private: c_void
}
#[repr(C)]
pub struct udev_monitor {
__private: c_void
}
#[repr(C)]
pub struct udev_enumerate {
__private: c_void
}
macro_rules! ifnull {
($a:expr, $b:expr) => {
if $a.is_null() { $b } else { $a }
}
}
struct Library(*mut c_void);
impl Library {
fn open(name: &'static str) -> Library {
let flags = RTLD_LAZY | RTLD_GLOBAL;
let flags_noload = flags | RTLD_NOLOAD;
let name = CString::new(name).unwrap();
let name = name.as_ptr();
Library(unsafe {
ifnull!(dlopen(name, flags_noload), dlopen(name, flags))
})
}
fn get(&self, name: &'static str) -> *mut c_void {
let name = CString::new(name).unwrap();
unsafe { dlsym(self.0, name.as_ptr()) }
}
}
impl Drop for Library {
fn drop(&mut self) {
unsafe { dlclose(self.0); }
}
}
unsafe impl Sync for Library {}
lazy_static! {
static ref LIBRARY: Library = {
Library::open("libudev.so.1")
};
}
pub struct Symbol<T> {
ptr: *mut c_void,
pd: marker::PhantomData<T>
}
impl<T> Symbol<T> {
fn new(ptr: *mut c_void) -> Self {
let default = Self::default as *mut c_void;
Self { ptr: ifnull!(ptr, default), pd: marker::PhantomData }
}
// This is the default symbol, used whenever dlopen() fails.
// Users of this library are expected to check whether udev_new() returns
// a nullptr, and if so they MUST NOT call any other exported functions.
extern "C" fn default() -> *mut c_void {
ptr::null_mut()
}
}
impl<T> ops::Deref for Symbol<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { mem::transmute(&self.ptr) }
}
}
unsafe impl<T: Sync> Sync for Symbol<T> {}
macro_rules! define {
($name:ident, $type:ty) => {
lazy_static! {
pub static ref $name : Symbol<$type> = {
Symbol::new(LIBRARY.get(stringify!($name)))
};
}
};
}
// udev
define!(udev_new, extern "C" fn () -> *mut udev);
define!(udev_unref, extern "C" fn (*mut udev) -> *mut udev);
// udev_list
define!(udev_list_entry_get_next, extern "C" fn (*mut udev_list_entry) -> *mut udev_list_entry);
define!(udev_list_entry_get_name, extern "C" fn (*mut udev_list_entry) -> *const c_char);
define!(udev_list_entry_get_value, extern "C" fn (*mut udev_list_entry) -> *const c_char);
// udev_device
define!(udev_device_ref, extern "C" fn (*mut udev_device) -> *mut udev_device);
define!(udev_device_unref, extern "C" fn (*mut udev_device) -> *mut udev_device);
define!(udev_device_new_from_syspath, extern "C" fn (*mut udev, *const c_char) -> *mut udev_device);
define!(udev_device_get_parent, extern "C" fn (*mut udev_device) -> *mut udev_device);
define!(udev_device_get_devpath, extern "C" fn (*mut udev_device) -> *const c_char);
define!(udev_device_get_subsystem, extern "C" fn (*mut udev_device) -> *const c_char);
define!(udev_device_get_devtype, extern "C" fn (*mut udev_device) -> *const c_char);
define!(udev_device_get_syspath, extern "C" fn (*mut udev_device) -> *const c_char);
define!(udev_device_get_sysname, extern "C" fn (*mut udev_device) -> *const c_char);
define!(udev_device_get_sysnum, extern "C" fn (*mut udev_device) -> *const c_char);
define!(udev_device_get_devnode, extern "C" fn (*mut udev_device) -> *const c_char);
define!(udev_device_get_is_initialized, extern "C" fn (*mut udev_device) -> c_int);
define!(udev_device_get_properties_list_entry, extern "C" fn (*mut udev_device) -> *mut udev_list_entry);
define!(udev_device_get_property_value, extern "C" fn (*mut udev_device, *const c_char) -> *const c_char);
define!(udev_device_get_driver, extern "C" fn (*mut udev_device) -> *const c_char);
define!(udev_device_get_devnum, extern "C" fn (*mut udev_device) -> dev_t);
define!(udev_device_get_action, extern "C" fn (*mut udev_device) -> *const c_char);
define!(udev_device_get_sysattr_value, extern "C" fn (*mut udev_device, *const c_char) -> *const c_char);
define!(udev_device_set_sysattr_value, extern "C" fn (*mut udev_device, *const c_char, *mut c_char) -> c_int);
define!(udev_device_get_sysattr_list_entry, extern "C" fn (*mut udev_device) -> *mut udev_list_entry);
define!(udev_device_get_seqnum, extern "C" fn (*mut udev_device) -> c_ulonglong);
// udev_monitor
define!(udev_monitor_ref, extern "C" fn (*mut udev_monitor) -> *mut udev_monitor);
define!(udev_monitor_unref, extern "C" fn (*mut udev_monitor) -> *mut udev_monitor);
define!(udev_monitor_new_from_netlink, extern "C" fn (*mut udev, *const c_char) -> *mut udev_monitor);
define!(udev_monitor_enable_receiving, extern "C" fn (*mut udev_monitor) -> c_int);
define!(udev_monitor_get_fd, extern "C" fn (*mut udev_monitor) -> c_int);
define!(udev_monitor_receive_device, extern "C" fn (*mut udev_monitor) -> *mut udev_device);
define!(udev_monitor_filter_add_match_subsystem_devtype, extern "C" fn (*mut udev_monitor, *const c_char, *const c_char) -> c_int);
define!(udev_monitor_filter_add_match_tag, extern "C" fn (*mut udev_monitor, *const c_char) -> c_int);
define!(udev_monitor_filter_remove, extern "C" fn (*mut udev_monitor) -> c_int);
// udev_enumerate
define!(udev_enumerate_unref, extern "C" fn (*mut udev_enumerate) -> *mut udev_enumerate);
define!(udev_enumerate_new, extern "C" fn (*mut udev) -> *mut udev_enumerate);
define!(udev_enumerate_add_match_subsystem, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
define!(udev_enumerate_add_nomatch_subsystem, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
define!(udev_enumerate_add_match_sysattr, extern "C" fn (*mut udev_enumerate, *const c_char, *const c_char) -> c_int);
define!(udev_enumerate_add_nomatch_sysattr, extern "C" fn (*mut udev_enumerate, *const c_char, *const c_char) -> c_int);
define!(udev_enumerate_add_match_property, extern "C" fn (*mut udev_enumerate, *const c_char, *const c_char) -> c_int);
define!(udev_enumerate_add_match_tag, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
define!(udev_enumerate_add_match_parent, extern "C" fn (*mut udev_enumerate, *mut udev_device) -> c_int);
define!(udev_enumerate_add_match_is_initialized, extern "C" fn (*mut udev_enumerate) -> c_int);
define!(udev_enumerate_add_match_sysname, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
define!(udev_enumerate_add_syspath, extern "C" fn (*mut udev_enumerate, *const c_char) -> c_int);
define!(udev_enumerate_scan_devices, extern "C" fn (*mut udev_enumerate) -> c_int);
define!(udev_enumerate_get_list_entry, extern "C" fn (*mut udev_enumerate) -> *mut udev_list_entry);

View File

@ -2621,13 +2621,15 @@ XMLHttpRequestMainThread::MaybeLowerChannelPriority()
return;
}
nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel);
if (cos) {
// Adding TailAllowed to overrule the Unblocked flag, but to preserve
// the effect of Unblocked when tailing is off.
cos->AddClassFlags(nsIClassOfService::Throttleable |
nsIClassOfService::Tail |
nsIClassOfService::TailAllowed);
if (nsContentUtils::IsTailingEnabled()) {
nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel);
if (cos) {
// Adding TailAllowed to overrule the Unblocked flag, but to preserve
// the effect of Unblocked when tailing is off.
cos->AddClassFlags(nsIClassOfService::Throttleable |
nsIClassOfService::Tail |
nsIClassOfService::TailAllowed);
}
}
nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);

View File

@ -74,6 +74,7 @@ class Mutex;
namespace gfx {
class UnscaledFont;
class ScaledFont;
}
template<>
@ -89,6 +90,15 @@ struct WeakPtrTraits<gfx::UnscaledFont>
}
};
template<>
struct WeakPtrTraits<gfx::ScaledFont>
{
static void AssertSafeToAccessFromNonOwningThread()
{
AssertIsMainThreadOrServoFontMetricsLocked();
}
};
namespace gfx {
class ScaledFont;
@ -769,23 +779,29 @@ protected:
UnscaledFont() {}
private:
static uint32_t sDeletionCounter;
static Atomic<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.
*/
class ScaledFont : public external::AtomicRefCounted<ScaledFont>
class ScaledFont
: public external::AtomicRefCounted<ScaledFont>
, public SupportsWeakPtr<ScaledFont>
{
public:
MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFont)
virtual ~ScaledFont() {}
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(ScaledFont)
virtual ~ScaledFont();
virtual FontType GetType() const = 0;
virtual Float GetSize() const = 0;
virtual AntialiasMode GetDefaultAAMode();
static uint32_t DeletionCounter() { return sDeletionCounter; }
/** This allows getting a path that describes the outline of a set of glyphs.
* A target is passed in so that the guarantee is made the returned path
* can be used with any DrawTarget that has the same backend as the one
@ -833,6 +849,9 @@ protected:
UserData mUserData;
RefPtr<UnscaledFont> mUnscaledFont;
private:
static Atomic<uint32_t> sDeletionCounter;
};
/**

View File

@ -26,13 +26,20 @@ using namespace std;
namespace mozilla {
namespace gfx {
uint32_t UnscaledFont::sDeletionCounter = 0;
Atomic<uint32_t> UnscaledFont::sDeletionCounter(0);
UnscaledFont::~UnscaledFont()
{
sDeletionCounter++;
}
Atomic<uint32_t> ScaledFont::sDeletionCounter(0);
ScaledFont::~ScaledFont()
{
sDeletionCounter++;
}
AntialiasMode
ScaledFont::GetDefaultAAMode()
{

View File

@ -79,4 +79,4 @@ to make sure that mozjs_sys also has its Cargo.lock file updated if needed, henc
the need to run the cargo update command in js/src as well. Hopefully this will
be resolved soon.
Latest Commit: 5edd3da7ee11e1d0caaf0b53cb7f04cfab20e585
Latest Commit: 81cba6b139c4c1061cab6a1c38acf2ae7f50445d

View File

@ -315,6 +315,11 @@ public:
nsIntRegion& aOutRegion,
NotifySubDocInvalidationFunc aCallback)
{
if (mLayer->AsHostLayer() && !mLayer->GetLocalVisibleRegion().ToUnknownRegion().IsEqual(mVisibleRegion)) {
IntRect result = NewTransformedBoundsForLeaf();
result = result.Union(OldTransformedBoundsForLeaf());
aOutRegion = result;
}
return true;
}

View File

@ -22,6 +22,9 @@ using mozilla::wr::ByteBuffer from "mozilla/webrender/WebRenderTypes.h";
using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h";
using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h";
using mozilla::wr::FontKey from "mozilla/webrender/WebRenderTypes.h";
using mozilla::wr::FontInstanceKey from "mozilla/webrender/WebRenderTypes.h";
using mozilla::wr::MaybeFontInstanceOptions from "mozilla/webrender/WebRenderTypes.h";
using mozilla::wr::MaybeFontInstancePlatformOptions from "mozilla/webrender/WebRenderTypes.h";
using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h";
using mozilla::wr::BuiltDisplayListDescriptor from "mozilla/webrender/webrender_ffi.h";
using mozilla::wr::IdNamespace from "mozilla/webrender/WebRenderTypes.h";
@ -54,6 +57,9 @@ parent:
async DeleteCompositorAnimations(uint64_t[] aIds);
async AddRawFont(FontKey aFontKey, ByteBuffer aBytes, uint32_t aFontIndex);
async DeleteFont(FontKey aFontKey);
async AddFontInstance(FontInstanceKey aInstanceKey, FontKey aFontKey, float aGlyphSize,
MaybeFontInstanceOptions aOptions, MaybeFontInstancePlatformOptions aPlatformOptions);
async DeleteFontInstance(FontInstanceKey aInstanceKey);
async DPBegin(IntSize aSize);
async DPEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
LayoutSize aContentSize, ByteBuffer aDL, BuiltDisplayListDescriptor aDLDesc,

View File

@ -29,6 +29,7 @@ WebRenderBridgeChild::WebRenderBridgeChild(const wr::PipelineId& aPipelineId)
, mIPCOpen(false)
, mDestroyed(false)
, mFontKeysDeleted(0)
, mFontInstanceKeysDeleted(0)
{
}
@ -219,7 +220,7 @@ WebRenderBridgeChild::PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArra
MOZ_ASSERT(aFont);
MOZ_ASSERT(!aGlyphs.IsEmpty());
wr::WrFontKey key = GetFontKeyForScaledFont(aFont);
wr::WrFontInstanceKey key = GetFontKeyForScaledFont(aFont);
MOZ_ASSERT(key.mNamespace.mHandle && key.mHandle);
nsTArray<wr::GlyphInstance> wr_glyph_instances;
@ -235,11 +236,10 @@ WebRenderBridgeChild::PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArra
aSc.ToRelativeLayoutRect(aClip),
aColor,
key,
Range<const wr::GlyphInstance>(wr_glyph_instances.Elements(), wr_glyph_instances.Length()),
aFont->GetSize());
Range<const wr::GlyphInstance>(wr_glyph_instances.Elements(), wr_glyph_instances.Length()));
}
wr::FontKey
wr::FontInstanceKey
WebRenderBridgeChild::GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont)
{
MOZ_ASSERT(!mDestroyed);
@ -248,34 +248,54 @@ WebRenderBridgeChild::GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont)
(aScaledFont->GetType() == gfx::FontType::MAC) ||
(aScaledFont->GetType() == gfx::FontType::FONTCONFIG));
wr::FontInstanceKey instanceKey = { wr::IdNamespace { 0 }, 0 };
if (mFontInstanceKeys.Get(aScaledFont, &instanceKey)) {
return instanceKey;
}
RefPtr<gfx::UnscaledFont> unscaled = aScaledFont->GetUnscaledFont();
MOZ_ASSERT(unscaled);
wr::FontKey key = { wr::IdNamespace { 0 }, 0};
if (mFontKeys.Get(unscaled, &key)) {
return key;
wr::FontKey fontKey = { wr::IdNamespace { 0 }, 0};
if (!mFontKeys.Get(unscaled, &fontKey)) {
FontFileData data;
if (!unscaled->GetFontFileData(WriteFontFileData, &data) ||
!data.mFontBuffer.mData) {
return instanceKey;
}
fontKey.mNamespace = GetNamespace();
fontKey.mHandle = GetNextResourceId();
SendAddRawFont(fontKey, data.mFontBuffer, data.mFontIndex);
mFontKeys.Put(unscaled, fontKey);
}
FontFileData data;
if (!unscaled->GetFontFileData(WriteFontFileData, &data) ||
!data.mFontBuffer.mData) {
return key;
}
instanceKey.mNamespace = GetNamespace();
instanceKey.mHandle = GetNextResourceId();
key.mNamespace = GetNamespace();
key.mHandle = GetNextResourceId();
SendAddFontInstance(instanceKey, fontKey, aScaledFont->GetSize(), Nothing(), Nothing());
SendAddRawFont(key, data.mFontBuffer, data.mFontIndex);
mFontInstanceKeys.Put(aScaledFont, instanceKey);
mFontKeys.Put(unscaled, key);
return key;
return instanceKey;
}
void
WebRenderBridgeChild::RemoveExpiredFontKeys()
{
uint32_t counter = gfx::UnscaledFont::DeletionCounter();
uint32_t counter = gfx::ScaledFont::DeletionCounter();
if (mFontInstanceKeysDeleted != counter) {
mFontInstanceKeysDeleted = counter;
for (auto iter = mFontInstanceKeys.Iter(); !iter.Done(); iter.Next()) {
if (!iter.Key()) {
SendDeleteFontInstance(iter.Data());
iter.Remove();
}
}
}
counter = gfx::UnscaledFont::DeletionCounter();
if (mFontKeysDeleted != counter) {
mFontKeysDeleted = counter;
for (auto iter = mFontKeys.Iter(); !iter.Done(); iter.Next()) {
@ -459,7 +479,8 @@ WebRenderBridgeChild::RecvWrUpdated(const wr::IdNamespace& aNewIdNamespace)
// Update mIdNamespace to identify obsolete keys and messages by WebRenderBridgeParent.
// Since usage of invalid keys could cause crash in webrender.
mIdNamespace = aNewIdNamespace;
// Just clear FontKeys, they are removed during WebRenderAPI destruction.
// Just clear FontInstaceKeys/FontKeys, they are removed during WebRenderAPI destruction.
mFontInstanceKeys.Clear();
mFontKeys.Clear();
GetCompositorBridgeChild()->RecvInvalidateLayers(wr::AsUint64(mPipelineId));
return IPC_OK();

View File

@ -27,13 +27,14 @@ class CompositorBridgeChild;
class StackingContextHelper;
class TextureForwarder;
class UnscaledFontHashKey : public PLDHashEntryHdr
template<class T>
class WeakPtrHashKey : public PLDHashEntryHdr
{
public:
typedef gfx::UnscaledFont* KeyType;
typedef const gfx::UnscaledFont* KeyTypePointer;
typedef T* KeyType;
typedef const T* KeyTypePointer;
explicit UnscaledFontHashKey(KeyTypePointer aKey) : mKey(const_cast<KeyType>(aKey)) {}
explicit WeakPtrHashKey(KeyTypePointer aKey) : mKey(const_cast<KeyType>(aKey)) {}
KeyType GetKey() const { return mKey; }
bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; }
@ -46,9 +47,12 @@ public:
enum { ALLOW_MEMMOVE = true };
private:
WeakPtr<gfx::UnscaledFont> mKey;
WeakPtr<T> mKey;
};
typedef WeakPtrHashKey<gfx::UnscaledFont> UnscaledFontHashKey;
typedef WeakPtrHashKey<gfx::ScaledFont> ScaledFontHashKey;
class WebRenderBridgeChild final : public PWebRenderBridgeChild
, public CompositableForwarder
{
@ -109,7 +113,7 @@ public:
const StackingContextHelper& aSc,
const LayerRect& aBounds, const LayerRect& aClip);
wr::FontKey GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont);
wr::FontInstanceKey GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont);
void RemoveExpiredFontKeys();
void ClearReadLocks();
@ -179,6 +183,9 @@ private:
uint32_t mFontKeysDeleted;
nsDataHashtable<UnscaledFontHashKey, wr::FontKey> mFontKeys;
uint32_t mFontInstanceKeysDeleted;
nsDataHashtable<ScaledFontHashKey, wr::FontInstanceKey> mFontInstanceKeys;
};
} // namespace layers

View File

@ -312,8 +312,7 @@ WebRenderBridgeParent::RecvDeleteFont(const wr::FontKey& aFontKey)
return IPC_OK();
}
if (mFontKeys.find(wr::AsUint64(aFontKey)) != mFontKeys.end()) {
mFontKeys.erase(wr::AsUint64(aFontKey));
if (mFontKeys.erase(wr::AsUint64(aFontKey)) > 0) {
mApi->DeleteFont(aFontKey);
} else {
MOZ_ASSERT_UNREACHABLE("invalid FontKey");
@ -322,6 +321,54 @@ WebRenderBridgeParent::RecvDeleteFont(const wr::FontKey& aFontKey)
return IPC_OK();
}
mozilla::ipc::IPCResult
WebRenderBridgeParent::RecvAddFontInstance(const wr::FontInstanceKey& aInstanceKey,
const wr::FontKey& aFontKey,
const float& aGlyphSize,
const MaybeFontInstanceOptions& aOptions,
const MaybeFontInstancePlatformOptions& aPlatformOptions)
{
if (mDestroyed) {
return IPC_OK();
}
// Check if key is obsoleted.
if (aInstanceKey.mNamespace != mIdNamespace) {
return IPC_OK();
}
MOZ_ASSERT(mApi);
MOZ_ASSERT(mFontInstanceKeys.find(wr::AsUint64(aInstanceKey)) == mFontInstanceKeys.end());
mFontInstanceKeys.insert(wr::AsUint64(aInstanceKey));
mApi->AddFontInstance(aInstanceKey, aFontKey, aGlyphSize,
aOptions.ptrOr(nullptr), aPlatformOptions.ptrOr(nullptr));
return IPC_OK();
}
mozilla::ipc::IPCResult
WebRenderBridgeParent::RecvDeleteFontInstance(const wr::FontInstanceKey& aInstanceKey)
{
if (mDestroyed) {
return IPC_OK();
}
MOZ_ASSERT(mApi);
// Check if key is obsoleted.
if (aInstanceKey.mNamespace != mIdNamespace) {
return IPC_OK();
}
if (mFontInstanceKeys.erase(wr::AsUint64(aInstanceKey)) > 0) {
mApi->DeleteFontInstance(aInstanceKey);
} else {
MOZ_ASSERT_UNREACHABLE("invalid FontInstanceKey");
}
return IPC_OK();
}
mozilla::ipc::IPCResult
WebRenderBridgeParent::RecvUpdateImage(const wr::ImageKey& aImageKey,
const gfx::IntSize& aSize,
@ -357,8 +404,7 @@ WebRenderBridgeParent::RecvDeleteImage(const wr::ImageKey& aImageKey)
return IPC_OK();
}
if (mActiveImageKeys.find(wr::AsUint64(aImageKey)) != mActiveImageKeys.end()) {
mActiveImageKeys.erase(wr::AsUint64(aImageKey));
if (mActiveImageKeys.erase(wr::AsUint64(aImageKey)) > 0) {
mKeysToDelete.push_back(aImageKey);
} else {
MOZ_ASSERT_UNREACHABLE("invalid ImageKey");
@ -374,9 +420,8 @@ WebRenderBridgeParent::RecvDeleteCompositorAnimations(InfallibleTArray<uint64_t>
}
for (uint32_t i = 0; i < aIds.Length(); i++) {
if (mActiveAnimations.find(aIds[i]) != mActiveAnimations.end()) {
if (mActiveAnimations.erase(aIds[i]) > 0) {
mAnimStorage->ClearById(aIds[i]);
mActiveAnimations.erase(aIds[i]);
} else {
NS_ERROR("Tried to delete invalid animation");
}
@ -1307,6 +1352,7 @@ WebRenderBridgeParent::ClearResources()
// Schedule composition to clean up Pipeline
mCompositorScheduler->ScheduleComposition();
// WrFontKeys and WrImageKeys are deleted during WebRenderAPI destruction.
mFontInstanceKeys.clear();
mFontKeys.clear();
mActiveImageKeys.clear();
mKeysToDelete.clear();

View File

@ -92,6 +92,12 @@ public:
const ByteBuffer& aBuffer,
const uint32_t& aFontIndex) override;
mozilla::ipc::IPCResult RecvDeleteFont(const wr::FontKey& aFontKey) override;
mozilla::ipc::IPCResult RecvAddFontInstance(const wr::FontInstanceKey& aInstanceKey,
const wr::FontKey& aFontKey,
const float& aGlyphSize,
const MaybeFontInstanceOptions& aOptions,
const MaybeFontInstancePlatformOptions& aPlatformOptions) override;
mozilla::ipc::IPCResult RecvDeleteFontInstance(const wr::FontInstanceKey& aInstanceKey) override;
mozilla::ipc::IPCResult RecvDPBegin(const gfx::IntSize& aSize) override;
mozilla::ipc::IPCResult RecvDPEnd(const gfx::IntSize& aSize,
InfallibleTArray<WebRenderParentCommand>&& aCommands,
@ -272,6 +278,7 @@ private:
// WebRenderBridgeParent is destroyed abnormally and Tab move between different windows.
std::unordered_set<uint64_t> mActiveImageKeys;
std::unordered_set<uint64_t> mFontKeys;
std::unordered_set<uint64_t> mFontInstanceKeys;
// mActiveAnimations is used to avoid leaking animations when WebRenderBridgeParent is
// destroyed abnormally and Tab move between different windows.
std::unordered_set<uint64_t> mActiveAnimations;

View File

@ -53,6 +53,24 @@ struct ParamTraits<mozilla::wr::FontKey>
{
};
template<>
struct ParamTraits<mozilla::wr::FontInstanceKey>
: public PlainOldDataSerializer<mozilla::wr::FontInstanceKey>
{
};
template<>
struct ParamTraits<mozilla::wr::FontInstanceOptions>
: public PlainOldDataSerializer<mozilla::wr::FontInstanceOptions>
{
};
template<>
struct ParamTraits<mozilla::wr::FontInstancePlatformOptions>
: public PlainOldDataSerializer<mozilla::wr::FontInstancePlatformOptions>
{
};
template<>
struct ParamTraits<mozilla::wr::ExternalImageId>
: public PlainOldDataSerializer<mozilla::wr::ExternalImageId>

View File

@ -1,6 +1,6 @@
[package]
name = "webrender"
version = "0.49.0"
version = "0.50.0"
authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
license = "MPL-2.0"
repository = "https://github.com/servo/webrender"

View File

@ -241,6 +241,9 @@ impl Example for App {
let font_bytes = load_file("res/FreeSans.ttf");
resources.add_raw_font(font_key, font_bytes, 0);
let font_instance_key = api.generate_font_instance_key();
resources.add_font_instance(font_instance_key, font_key, Au::from_px(32), None, None);
let text_bounds = (100, 200).by(700, 300);
let glyphs = vec![
GlyphInstance {
@ -296,9 +299,8 @@ impl Example for App {
builder.push_text(text_bounds,
None,
&glyphs,
font_key,
font_instance_key,
ColorF::new(1.0, 1.0, 0.0, 1.0),
Au::from_px(32),
None);
}

View File

@ -210,6 +210,7 @@ impl api::BlobImageRenderer for CheckerboardRenderer {
Err(api::BlobImageError::Other("Channel closed".into()))
}
fn delete_font(&mut self, _font: api::FontKey) { }
fn delete_font_instance(&mut self, _instance: api::FontInstanceKey) { }
}
struct App {

View File

@ -183,6 +183,6 @@ void main(void) {
float value = color(pos, p0Rect, p1Rect, radii, sigma);
value = max(value, 0.0);
oFragColor = dither(vec4(1.0, 1.0, 1.0, vInverted == 1.0 ? 1.0 - value : value));
oFragColor = dither(vec4(vInverted == 1.0 ? 1.0 - value : value));
}
#endif

View File

@ -1,65 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
float clip_against_ellipse_if_needed(vec2 pos,
float current_distance,
vec4 ellipse_center_radius,
vec2 sign_modifier,
float afwidth) {
float ellipse_distance = distance_to_ellipse(pos - ellipse_center_radius.xy,
ellipse_center_radius.zw);
return mix(current_distance,
ellipse_distance + afwidth,
all(lessThan(sign_modifier * pos, sign_modifier * ellipse_center_radius.xy)));
}
float rounded_rect(vec2 pos) {
float current_distance = 0.0;
// Apply AA
float afwidth = 0.5 * length(fwidth(pos));
// Clip against each ellipse.
current_distance = clip_against_ellipse_if_needed(pos,
current_distance,
vClipCenter_Radius_TL,
vec2(1.0),
afwidth);
current_distance = clip_against_ellipse_if_needed(pos,
current_distance,
vClipCenter_Radius_TR,
vec2(-1.0, 1.0),
afwidth);
current_distance = clip_against_ellipse_if_needed(pos,
current_distance,
vClipCenter_Radius_BR,
vec2(-1.0),
afwidth);
current_distance = clip_against_ellipse_if_needed(pos,
current_distance,
vClipCenter_Radius_BL,
vec2(1.0, -1.0),
afwidth);
return smoothstep(0.0, afwidth, 1.0 - current_distance);
}
void main(void) {
float alpha = 1.f;
vec2 local_pos = init_transform_fs(vPos, alpha);
float clip_alpha = rounded_rect(local_pos);
float combined_alpha = min(alpha, clip_alpha);
// Select alpha or inverse alpha depending on clip in/out.
float final_alpha = mix(combined_alpha, 1.0 - combined_alpha, vClipMode);
oFragColor = vec4(final_alpha, 0.0, 0.0, 1.0);
}

View File

@ -2,7 +2,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/. */
#include shared,prim_shared,clip_shared
#include shared,prim_shared,clip_shared,ellipse
varying vec3 vPos;
flat varying float vClipMode;
@ -10,3 +10,142 @@ flat varying vec4 vClipCenter_Radius_TL;
flat varying vec4 vClipCenter_Radius_TR;
flat varying vec4 vClipCenter_Radius_BL;
flat varying vec4 vClipCenter_Radius_BR;
#ifdef WR_VERTEX_SHADER
struct ClipRect {
RectWithSize rect;
vec4 mode;
};
ClipRect fetch_clip_rect(ivec2 address) {
vec4 data[2] = fetch_from_resource_cache_2_direct(address);
return ClipRect(RectWithSize(data[0].xy, data[0].zw), data[1]);
}
struct ClipCorner {
RectWithSize rect;
vec4 outer_inner_radius;
};
ClipCorner fetch_clip_corner(ivec2 address, int index) {
address += ivec2(2 + 2 * index, 0);
vec4 data[2] = fetch_from_resource_cache_2_direct(address);
return ClipCorner(RectWithSize(data[0].xy, data[0].zw), data[1]);
}
struct ClipData {
ClipRect rect;
ClipCorner top_left;
ClipCorner top_right;
ClipCorner bottom_left;
ClipCorner bottom_right;
};
ClipData fetch_clip(ivec2 address) {
ClipData clip;
clip.rect = fetch_clip_rect(address);
clip.top_left = fetch_clip_corner(address, 0);
clip.top_right = fetch_clip_corner(address, 1);
clip.bottom_left = fetch_clip_corner(address, 2);
clip.bottom_right = fetch_clip_corner(address, 3);
return clip;
}
void main(void) {
CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
ClipArea area = fetch_clip_area(cci.render_task_index);
Layer layer = fetch_layer(cci.layer_index);
ClipData clip = fetch_clip(cci.clip_data_address);
RectWithSize local_rect = clip.rect.rect;
ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
layer,
area,
cci.segment);
vPos = vi.local_pos;
vClipMode = clip.rect.mode.x;
RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
vClipCenter_Radius_TL = vec4(clip_rect.p0 + clip.top_left.outer_inner_radius.xy,
clip.top_left.outer_inner_radius.xy);
vClipCenter_Radius_TR = vec4(clip_rect.p1.x - clip.top_right.outer_inner_radius.x,
clip_rect.p0.y + clip.top_right.outer_inner_radius.y,
clip.top_right.outer_inner_radius.xy);
vClipCenter_Radius_BR = vec4(clip_rect.p1 - clip.bottom_right.outer_inner_radius.xy,
clip.bottom_right.outer_inner_radius.xy);
vClipCenter_Radius_BL = vec4(clip_rect.p0.x + clip.bottom_left.outer_inner_radius.x,
clip_rect.p1.y - clip.bottom_left.outer_inner_radius.y,
clip.bottom_left.outer_inner_radius.xy);
}
#endif
#ifdef WR_FRAGMENT_SHADER
float clip_against_ellipse_if_needed(vec2 pos,
float current_distance,
vec4 ellipse_center_radius,
vec2 sign_modifier,
float afwidth) {
float ellipse_distance = distance_to_ellipse(pos - ellipse_center_radius.xy,
ellipse_center_radius.zw);
return mix(current_distance,
ellipse_distance + afwidth,
all(lessThan(sign_modifier * pos, sign_modifier * ellipse_center_radius.xy)));
}
float rounded_rect(vec2 pos) {
float current_distance = 0.0;
// Apply AA
float afwidth = 0.5 * length(fwidth(pos));
// Clip against each ellipse.
current_distance = clip_against_ellipse_if_needed(pos,
current_distance,
vClipCenter_Radius_TL,
vec2(1.0),
afwidth);
current_distance = clip_against_ellipse_if_needed(pos,
current_distance,
vClipCenter_Radius_TR,
vec2(-1.0, 1.0),
afwidth);
current_distance = clip_against_ellipse_if_needed(pos,
current_distance,
vClipCenter_Radius_BR,
vec2(-1.0),
afwidth);
current_distance = clip_against_ellipse_if_needed(pos,
current_distance,
vClipCenter_Radius_BL,
vec2(1.0, -1.0),
afwidth);
return smoothstep(0.0, afwidth, 1.0 - current_distance);
}
void main(void) {
float alpha = 1.f;
vec2 local_pos = init_transform_fs(vPos, alpha);
float clip_alpha = rounded_rect(local_pos);
float combined_alpha = min(alpha, clip_alpha);
// Select alpha or inverse alpha depending on clip in/out.
float final_alpha = mix(combined_alpha, 1.0 - combined_alpha, vClipMode);
oFragColor = vec4(final_alpha, 0.0, 0.0, 1.0);
}
#endif

View File

@ -1,76 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
struct ClipRect {
RectWithSize rect;
vec4 mode;
};
ClipRect fetch_clip_rect(ivec2 address) {
vec4 data[2] = fetch_from_resource_cache_2_direct(address);
return ClipRect(RectWithSize(data[0].xy, data[0].zw), data[1]);
}
struct ClipCorner {
RectWithSize rect;
vec4 outer_inner_radius;
};
ClipCorner fetch_clip_corner(ivec2 address, int index) {
address += ivec2(2 + 2 * index, 0);
vec4 data[2] = fetch_from_resource_cache_2_direct(address);
return ClipCorner(RectWithSize(data[0].xy, data[0].zw), data[1]);
}
struct ClipData {
ClipRect rect;
ClipCorner top_left;
ClipCorner top_right;
ClipCorner bottom_left;
ClipCorner bottom_right;
};
ClipData fetch_clip(ivec2 address) {
ClipData clip;
clip.rect = fetch_clip_rect(address);
clip.top_left = fetch_clip_corner(address, 0);
clip.top_right = fetch_clip_corner(address, 1);
clip.bottom_left = fetch_clip_corner(address, 2);
clip.bottom_right = fetch_clip_corner(address, 3);
return clip;
}
void main(void) {
CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
ClipArea area = fetch_clip_area(cci.render_task_index);
Layer layer = fetch_layer(cci.layer_index);
ClipData clip = fetch_clip(cci.clip_data_address);
RectWithSize local_rect = clip.rect.rect;
ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
layer,
area,
cci.segment);
vPos = vi.local_pos;
vClipMode = clip.rect.mode.x;
RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
vClipCenter_Radius_TL = vec4(clip_rect.p0 + clip.top_left.outer_inner_radius.xy,
clip.top_left.outer_inner_radius.xy);
vClipCenter_Radius_TR = vec4(clip_rect.p1.x - clip.top_right.outer_inner_radius.x,
clip_rect.p0.y + clip.top_right.outer_inner_radius.y,
clip.top_right.outer_inner_radius.xy);
vClipCenter_Radius_BR = vec4(clip_rect.p1 - clip.bottom_right.outer_inner_radius.xy,
clip.bottom_right.outer_inner_radius.xy);
vClipCenter_Radius_BL = vec4(clip_rect.p0.x + clip.bottom_left.outer_inner_radius.x,
clip_rect.p1.y - clip.bottom_left.outer_inner_radius.y,
clip.bottom_left.outer_inner_radius.xy);
}

View File

@ -0,0 +1,69 @@
/* 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/. */
#ifdef WR_FRAGMENT_SHADER
//
// Signed distance to an ellipse.
// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
// Note that this fails for exact circles.
//
float sdEllipse( vec2 p, in vec2 ab ) {
p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
float l = ab.y*ab.y - ab.x*ab.x;
float m = ab.x*p.x/l;
float n = ab.y*p.y/l;
float m2 = m*m;
float n2 = n*n;
float c = (m2 + n2 - 1.0)/3.0;
float c3 = c*c*c;
float q = c3 + m2*n2*2.0;
float d = c3 + m2*n2;
float g = m + m*n2;
float co;
if( d<0.0 )
{
float p = acos(q/c3)/3.0;
float s = cos(p);
float t = sin(p)*sqrt(3.0);
float rx = sqrt( -c*(s + t + 2.0) + m2 );
float ry = sqrt( -c*(s - t + 2.0) + m2 );
co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
}
else
{
float h = 2.0*m*n*sqrt( d );
float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
float rx = -s - u - c*4.0 + 2.0*m2;
float ry = (s - u)*sqrt(3.0);
float rm = sqrt( rx*rx + ry*ry );
float p = ry/sqrt(rm-rx);
co = (p + 2.0*g/rm - m)/2.0;
}
float si = sqrt( 1.0 - co*co );
vec2 r = vec2( ab.x*co, ab.y*si );
return length(r - p ) * sign(p.y-r.y);
}
float distance_to_ellipse(vec2 p, vec2 radii) {
// sdEllipse fails on exact circles, so handle equal
// radii here. The branch coherency should make this
// a performance win for the circle case too.
if (radii.x == radii.y) {
return length(p) - radii.x;
} else {
return sdEllipse(p, radii);
}
}
#endif

View File

@ -855,66 +855,4 @@ vec4 sample_gradient(int address, float offset, float gradient_repeat) {
return dither(mix(texels[0], texels[1], fract(x)));
}
//
// Signed distance to an ellipse.
// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
// Note that this fails for exact circles.
//
float sdEllipse( vec2 p, in vec2 ab ) {
p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
float l = ab.y*ab.y - ab.x*ab.x;
float m = ab.x*p.x/l;
float n = ab.y*p.y/l;
float m2 = m*m;
float n2 = n*n;
float c = (m2 + n2 - 1.0)/3.0;
float c3 = c*c*c;
float q = c3 + m2*n2*2.0;
float d = c3 + m2*n2;
float g = m + m*n2;
float co;
if( d<0.0 )
{
float p = acos(q/c3)/3.0;
float s = cos(p);
float t = sin(p)*sqrt(3.0);
float rx = sqrt( -c*(s + t + 2.0) + m2 );
float ry = sqrt( -c*(s - t + 2.0) + m2 );
co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
}
else
{
float h = 2.0*m*n*sqrt( d );
float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
float rx = -s - u - c*4.0 + 2.0*m2;
float ry = (s - u)*sqrt(3.0);
float rm = sqrt( rx*rx + ry*ry );
float p = ry/sqrt(rm-rx);
co = (p + 2.0*g/rm - m)/2.0;
}
float si = sqrt( 1.0 - co*co );
vec2 r = vec2( ab.x*co, ab.y*si );
return length(r - p ) * sign(p.y-r.y);
}
float distance_to_ellipse(vec2 p, vec2 radii) {
// sdEllipse fails on exact circles, so handle equal
// radii here. The branch coherency should make this
// a performance win for the circle case too.
if (radii.x == radii.y) {
return length(p) - radii.x;
} else {
return sdEllipse(p, radii);
}
}
#endif //WR_FRAGMENT_SHADER

View File

@ -1,85 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
void main(void) {
float alpha = 1.0;
#ifdef WR_FEATURE_TRANSFORM
alpha = 0.0;
vec2 local_pos = init_transform_fs(vLocalPos, alpha);
#else
vec2 local_pos = vLocalPos;
#endif
alpha = min(alpha, do_clip());
// Find the appropriate distance to apply the AA smoothstep over.
vec2 fw = fwidth(local_pos);
float afwidth = length(fw);
float distance_for_color;
float color_mix_factor;
// Only apply the clip AA if inside the clip region. This is
// necessary for correctness when the border width is greater
// than the border radius.
if (all(lessThan(local_pos * vClipSign, vClipCenter * vClipSign))) {
vec2 p = local_pos - vClipCenter;
// Get signed distance from the inner/outer clips.
float d0 = distance_to_ellipse(p, vRadii0.xy);
float d1 = distance_to_ellipse(p, vRadii0.zw);
float d2 = distance_to_ellipse(p, vRadii1.xy);
float d3 = distance_to_ellipse(p, vRadii1.zw);
// SDF subtract main radii
float d_main = max(d0, 0.5 * afwidth - d1);
// SDF subtract inner radii (double style borders)
float d_inner = max(d2 - 0.5 * afwidth, -d3);
// Select how to combine the SDF based on border style.
float d = mix(max(d_main, -d_inner), d_main, vSDFSelect);
// Only apply AA to fragments outside the signed distance field.
alpha = min(alpha, 1.0 - smoothstep(0.0, 0.5 * afwidth, d));
// Get the groove/ridge mix factor.
color_mix_factor = smoothstep(-0.5 * afwidth,
0.5 * afwidth,
-d2);
} else {
// Handle the case where the fragment is outside the clip
// region in a corner. This occurs when border width is
// greater than border radius.
// Get linear distances along horizontal and vertical edges.
vec2 d0 = vClipSign.xx * (local_pos.xx - vEdgeDistance.xz);
vec2 d1 = vClipSign.yy * (local_pos.yy - vEdgeDistance.yw);
// Apply union to get the outer edge signed distance.
float da = min(d0.x, d1.x);
// Apply intersection to get the inner edge signed distance.
float db = max(-d0.y, -d1.y);
// Apply union to get both edges.
float d = min(da, db);
// Select fragment on/off based on signed distance.
// No AA here, since we know we're on a straight edge
// and the width is rounded to a whole CSS pixel.
alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
// Get the groove/ridge mix factor.
// TODO(gw): Support AA for groove/ridge border edge with transforms.
color_mix_factor = mix(0.0, 1.0, da > 0.0);
}
// Mix inner/outer color.
vec4 color0 = mix(vColor00, vColor01, color_mix_factor);
vec4 color1 = mix(vColor10, vColor11, color_mix_factor);
// Select color based on side of line. Get distance from the
// reference line, and then apply AA along the edge.
float ld = distance_to_line(vColorEdgeLine.xy, vColorEdgeLine.zw, local_pos);
float m = smoothstep(-0.5 * afwidth, 0.5 * afwidth, ld);
vec4 color = mix(color0, color1, m);
oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
}

View File

@ -2,7 +2,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/. */
#include shared,prim_shared,shared_border
#include shared,prim_shared,shared_border,ellipse
// Edge color transition
flat varying vec4 vColor00;
@ -27,3 +27,371 @@ varying vec3 vLocalPos;
#else
varying vec2 vLocalPos;
#endif
#ifdef WR_VERTEX_SHADER
// Matches BorderCornerSide enum in border.rs
#define SIDE_BOTH 0
#define SIDE_FIRST 1
#define SIDE_SECOND 2
vec2 get_radii(vec2 radius, vec2 invalid) {
if (all(greaterThan(radius, vec2(0.0)))) {
return radius;
}
return invalid;
}
void set_radii(int style,
vec2 radii,
vec2 widths,
vec2 adjusted_widths) {
vRadii0.xy = get_radii(radii, 2.0 * widths);
vRadii0.zw = get_radii(radii - widths, -widths);
switch (style) {
case BORDER_STYLE_RIDGE:
case BORDER_STYLE_GROOVE:
vRadii1.xy = radii - adjusted_widths;
// See comment in default branch
vRadii1.zw = vec2(-100.0);
break;
case BORDER_STYLE_DOUBLE:
vRadii1.xy = get_radii(radii - adjusted_widths, -widths);
vRadii1.zw = get_radii(radii - widths + adjusted_widths, -widths);
break;
default:
// These aren't needed, so we set them to some reasonably large
// negative value so later computations will discard them. This
// avoids branches and numerical issues in the fragment shader.
vRadii1.xy = vec2(-100.0);
vRadii1.zw = vec2(-100.0);
break;
}
}
void set_edge_line(vec2 border_width,
vec2 outer_corner,
vec2 gradient_sign) {
vec2 gradient = border_width * gradient_sign;
vColorEdgeLine = vec4(outer_corner, vec2(-gradient.y, gradient.x));
}
void write_color(vec4 color0, vec4 color1, int style, vec2 delta, int instance_kind) {
vec4 modulate;
switch (style) {
case BORDER_STYLE_GROOVE:
modulate = vec4(1.0 - 0.3 * delta.x,
1.0 + 0.3 * delta.x,
1.0 - 0.3 * delta.y,
1.0 + 0.3 * delta.y);
break;
case BORDER_STYLE_RIDGE:
modulate = vec4(1.0 + 0.3 * delta.x,
1.0 - 0.3 * delta.x,
1.0 + 0.3 * delta.y,
1.0 - 0.3 * delta.y);
break;
default:
modulate = vec4(1.0);
break;
}
// Optionally mask out one side of the border corner,
// depending on the instance kind.
switch (instance_kind) {
case SIDE_FIRST:
color0.a = 0.0;
break;
case SIDE_SECOND:
color1.a = 0.0;
break;
}
vColor00 = vec4(color0.rgb * modulate.x, color0.a);
vColor01 = vec4(color0.rgb * modulate.y, color0.a);
vColor10 = vec4(color1.rgb * modulate.z, color1.a);
vColor11 = vec4(color1.rgb * modulate.w, color1.a);
}
int select_style(int color_select, vec2 fstyle) {
ivec2 style = ivec2(fstyle);
switch (color_select) {
case SIDE_BOTH:
{
// TODO(gw): A temporary hack! While we don't support
// border corners that have dots or dashes
// with another style, pretend they are solid
// border corners.
bool has_dots = style.x == BORDER_STYLE_DOTTED ||
style.y == BORDER_STYLE_DOTTED;
bool has_dashes = style.x == BORDER_STYLE_DASHED ||
style.y == BORDER_STYLE_DASHED;
if (style.x != style.y && (has_dots || has_dashes))
return BORDER_STYLE_SOLID;
return style.x;
}
case SIDE_FIRST:
return style.x;
case SIDE_SECOND:
return style.y;
}
}
void main(void) {
Primitive prim = load_primitive();
Border border = fetch_border(prim.specific_prim_address);
int sub_part = prim.user_data0;
BorderCorners corners = get_border_corners(border, prim.local_rect);
vec2 p0, p1;
// TODO(gw): We'll need to pass through multiple styles
// once we support style transitions per corner.
int style;
vec4 edge_distances;
vec4 color0, color1;
vec2 color_delta;
// TODO(gw): Now that all border styles are supported, the switch
// statement below can be tidied up quite a bit.
switch (sub_part) {
case 0: {
p0 = corners.tl_outer;
p1 = corners.tl_inner;
color0 = border.colors[0];
color1 = border.colors[1];
vClipCenter = corners.tl_outer + border.radii[0].xy;
vClipSign = vec2(1.0);
style = select_style(prim.user_data1, border.style.yx);
vec4 adjusted_widths = get_effective_border_widths(border, style);
vec4 inv_adjusted_widths = border.widths - adjusted_widths;
set_radii(style,
border.radii[0].xy,
border.widths.xy,
adjusted_widths.xy);
set_edge_line(border.widths.xy,
corners.tl_outer,
vec2(1.0, 1.0));
edge_distances = vec4(p0 + adjusted_widths.xy,
p0 + inv_adjusted_widths.xy);
color_delta = vec2(1.0);
break;
}
case 1: {
p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
p1 = vec2(corners.tr_outer.x, corners.tr_inner.y);
color0 = border.colors[1];
color1 = border.colors[2];
vClipCenter = corners.tr_outer + vec2(-border.radii[0].z, border.radii[0].w);
vClipSign = vec2(-1.0, 1.0);
style = select_style(prim.user_data1, border.style.zy);
vec4 adjusted_widths = get_effective_border_widths(border, style);
vec4 inv_adjusted_widths = border.widths - adjusted_widths;
set_radii(style,
border.radii[0].zw,
border.widths.zy,
adjusted_widths.zy);
set_edge_line(border.widths.zy,
corners.tr_outer,
vec2(-1.0, 1.0));
edge_distances = vec4(p1.x - adjusted_widths.z,
p0.y + adjusted_widths.y,
p1.x - border.widths.z + adjusted_widths.z,
p0.y + inv_adjusted_widths.y);
color_delta = vec2(1.0, -1.0);
break;
}
case 2: {
p0 = corners.br_inner;
p1 = corners.br_outer;
color0 = border.colors[2];
color1 = border.colors[3];
vClipCenter = corners.br_outer - border.radii[1].xy;
vClipSign = vec2(-1.0, -1.0);
style = select_style(prim.user_data1, border.style.wz);
vec4 adjusted_widths = get_effective_border_widths(border, style);
vec4 inv_adjusted_widths = border.widths - adjusted_widths;
set_radii(style,
border.radii[1].xy,
border.widths.zw,
adjusted_widths.zw);
set_edge_line(border.widths.zw,
corners.br_outer,
vec2(-1.0, -1.0));
edge_distances = vec4(p1.x - adjusted_widths.z,
p1.y - adjusted_widths.w,
p1.x - border.widths.z + adjusted_widths.z,
p1.y - border.widths.w + adjusted_widths.w);
color_delta = vec2(-1.0);
break;
}
case 3: {
p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
p1 = vec2(corners.bl_inner.x, corners.bl_outer.y);
color0 = border.colors[3];
color1 = border.colors[0];
vClipCenter = corners.bl_outer + vec2(border.radii[1].z, -border.radii[1].w);
vClipSign = vec2(1.0, -1.0);
style = select_style(prim.user_data1, border.style.xw);
vec4 adjusted_widths = get_effective_border_widths(border, style);
vec4 inv_adjusted_widths = border.widths - adjusted_widths;
set_radii(style,
border.radii[1].zw,
border.widths.xw,
adjusted_widths.xw);
set_edge_line(border.widths.xw,
corners.bl_outer,
vec2(1.0, -1.0));
edge_distances = vec4(p0.x + adjusted_widths.x,
p1.y - adjusted_widths.w,
p0.x + inv_adjusted_widths.x,
p1.y - border.widths.w + adjusted_widths.w);
color_delta = vec2(-1.0, 1.0);
break;
}
}
switch (style) {
case BORDER_STYLE_DOUBLE: {
vEdgeDistance = edge_distances;
vAlphaSelect = 0.0;
vSDFSelect = 0.0;
break;
}
case BORDER_STYLE_GROOVE:
case BORDER_STYLE_RIDGE:
vEdgeDistance = vec4(edge_distances.xy, 0.0, 0.0);
vAlphaSelect = 1.0;
vSDFSelect = 1.0;
break;
case BORDER_STYLE_DOTTED:
// Disable normal clip radii for dotted corners, since
// all the clipping is handled by the clip mask.
vClipSign = vec2(0.0);
vEdgeDistance = vec4(0.0);
vAlphaSelect = 1.0;
vSDFSelect = 0.0;
break;
default: {
vEdgeDistance = vec4(0.0);
vAlphaSelect = 1.0;
vSDFSelect = 0.0;
break;
}
}
write_color(color0, color1, style, color_delta, prim.user_data1);
RectWithSize segment_rect;
segment_rect.p0 = p0;
segment_rect.size = p1 - p0;
#ifdef WR_FEATURE_TRANSFORM
TransformVertexInfo vi = write_transform_vertex(segment_rect,
prim.local_clip_rect,
prim.z,
prim.layer,
prim.task,
prim.local_rect);
#else
VertexInfo vi = write_vertex(segment_rect,
prim.local_clip_rect,
prim.z,
prim.layer,
prim.task,
prim.local_rect);
#endif
vLocalPos = vi.local_pos;
write_clip(vi.screen_pos, prim.clip_area);
}
#endif
#ifdef WR_FRAGMENT_SHADER
void main(void) {
float alpha = 1.0;
#ifdef WR_FEATURE_TRANSFORM
alpha = 0.0;
vec2 local_pos = init_transform_fs(vLocalPos, alpha);
#else
vec2 local_pos = vLocalPos;
#endif
alpha = min(alpha, do_clip());
// Find the appropriate distance to apply the AA smoothstep over.
vec2 fw = fwidth(local_pos);
float afwidth = length(fw);
float distance_for_color;
float color_mix_factor;
// Only apply the clip AA if inside the clip region. This is
// necessary for correctness when the border width is greater
// than the border radius.
if (all(lessThan(local_pos * vClipSign, vClipCenter * vClipSign))) {
vec2 p = local_pos - vClipCenter;
// Get signed distance from the inner/outer clips.
float d0 = distance_to_ellipse(p, vRadii0.xy);
float d1 = distance_to_ellipse(p, vRadii0.zw);
float d2 = distance_to_ellipse(p, vRadii1.xy);
float d3 = distance_to_ellipse(p, vRadii1.zw);
// SDF subtract main radii
float d_main = max(d0, 0.5 * afwidth - d1);
// SDF subtract inner radii (double style borders)
float d_inner = max(d2 - 0.5 * afwidth, -d3);
// Select how to combine the SDF based on border style.
float d = mix(max(d_main, -d_inner), d_main, vSDFSelect);
// Only apply AA to fragments outside the signed distance field.
alpha = min(alpha, 1.0 - smoothstep(0.0, 0.5 * afwidth, d));
// Get the groove/ridge mix factor.
color_mix_factor = smoothstep(-0.5 * afwidth,
0.5 * afwidth,
-d2);
} else {
// Handle the case where the fragment is outside the clip
// region in a corner. This occurs when border width is
// greater than border radius.
// Get linear distances along horizontal and vertical edges.
vec2 d0 = vClipSign.xx * (local_pos.xx - vEdgeDistance.xz);
vec2 d1 = vClipSign.yy * (local_pos.yy - vEdgeDistance.yw);
// Apply union to get the outer edge signed distance.
float da = min(d0.x, d1.x);
// Apply intersection to get the inner edge signed distance.
float db = max(-d0.y, -d1.y);
// Apply union to get both edges.
float d = min(da, db);
// Select fragment on/off based on signed distance.
// No AA here, since we know we're on a straight edge
// and the width is rounded to a whole CSS pixel.
alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
// Get the groove/ridge mix factor.
// TODO(gw): Support AA for groove/ridge border edge with transforms.
color_mix_factor = mix(0.0, 1.0, da > 0.0);
}
// Mix inner/outer color.
vec4 color0 = mix(vColor00, vColor01, color_mix_factor);
vec4 color1 = mix(vColor10, vColor11, color_mix_factor);
// Select color based on side of line. Get distance from the
// reference line, and then apply AA along the edge.
float ld = distance_to_line(vColorEdgeLine.xy, vColorEdgeLine.zw, local_pos);
float m = smoothstep(-0.5 * afwidth, 0.5 * afwidth, ld);
vec4 color = mix(color0, color1, m);
oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
}
#endif

View File

@ -1,285 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Matches BorderCornerSide enum in border.rs
#define SIDE_BOTH 0
#define SIDE_FIRST 1
#define SIDE_SECOND 2
vec2 get_radii(vec2 radius, vec2 invalid) {
if (all(greaterThan(radius, vec2(0.0)))) {
return radius;
}
return invalid;
}
void set_radii(int style,
vec2 radii,
vec2 widths,
vec2 adjusted_widths) {
vRadii0.xy = get_radii(radii, 2.0 * widths);
vRadii0.zw = get_radii(radii - widths, -widths);
switch (style) {
case BORDER_STYLE_RIDGE:
case BORDER_STYLE_GROOVE:
vRadii1.xy = radii - adjusted_widths;
// See comment in default branch
vRadii1.zw = vec2(-100.0);
break;
case BORDER_STYLE_DOUBLE:
vRadii1.xy = get_radii(radii - adjusted_widths, -widths);
vRadii1.zw = get_radii(radii - widths + adjusted_widths, -widths);
break;
default:
// These aren't needed, so we set them to some reasonably large
// negative value so later computations will discard them. This
// avoids branches and numerical issues in the fragment shader.
vRadii1.xy = vec2(-100.0);
vRadii1.zw = vec2(-100.0);
break;
}
}
void set_edge_line(vec2 border_width,
vec2 outer_corner,
vec2 gradient_sign) {
vec2 gradient = border_width * gradient_sign;
vColorEdgeLine = vec4(outer_corner, vec2(-gradient.y, gradient.x));
}
void write_color(vec4 color0, vec4 color1, int style, vec2 delta, int instance_kind) {
vec4 modulate;
switch (style) {
case BORDER_STYLE_GROOVE:
modulate = vec4(1.0 - 0.3 * delta.x,
1.0 + 0.3 * delta.x,
1.0 - 0.3 * delta.y,
1.0 + 0.3 * delta.y);
break;
case BORDER_STYLE_RIDGE:
modulate = vec4(1.0 + 0.3 * delta.x,
1.0 - 0.3 * delta.x,
1.0 + 0.3 * delta.y,
1.0 - 0.3 * delta.y);
break;
default:
modulate = vec4(1.0);
break;
}
// Optionally mask out one side of the border corner,
// depending on the instance kind.
switch (instance_kind) {
case SIDE_FIRST:
color0.a = 0.0;
break;
case SIDE_SECOND:
color1.a = 0.0;
break;
}
vColor00 = vec4(color0.rgb * modulate.x, color0.a);
vColor01 = vec4(color0.rgb * modulate.y, color0.a);
vColor10 = vec4(color1.rgb * modulate.z, color1.a);
vColor11 = vec4(color1.rgb * modulate.w, color1.a);
}
int select_style(int color_select, vec2 fstyle) {
ivec2 style = ivec2(fstyle);
switch (color_select) {
case SIDE_BOTH:
{
// TODO(gw): A temporary hack! While we don't support
// border corners that have dots or dashes
// with another style, pretend they are solid
// border corners.
bool has_dots = style.x == BORDER_STYLE_DOTTED ||
style.y == BORDER_STYLE_DOTTED;
bool has_dashes = style.x == BORDER_STYLE_DASHED ||
style.y == BORDER_STYLE_DASHED;
if (style.x != style.y && (has_dots || has_dashes))
return BORDER_STYLE_SOLID;
return style.x;
}
case SIDE_FIRST:
return style.x;
case SIDE_SECOND:
return style.y;
}
}
void main(void) {
Primitive prim = load_primitive();
Border border = fetch_border(prim.specific_prim_address);
int sub_part = prim.user_data0;
BorderCorners corners = get_border_corners(border, prim.local_rect);
vec2 p0, p1;
// TODO(gw): We'll need to pass through multiple styles
// once we support style transitions per corner.
int style;
vec4 edge_distances;
vec4 color0, color1;
vec2 color_delta;
// TODO(gw): Now that all border styles are supported, the switch
// statement below can be tidied up quite a bit.
switch (sub_part) {
case 0: {
p0 = corners.tl_outer;
p1 = corners.tl_inner;
color0 = border.colors[0];
color1 = border.colors[1];
vClipCenter = corners.tl_outer + border.radii[0].xy;
vClipSign = vec2(1.0);
style = select_style(prim.user_data1, border.style.yx);
vec4 adjusted_widths = get_effective_border_widths(border, style);
vec4 inv_adjusted_widths = border.widths - adjusted_widths;
set_radii(style,
border.radii[0].xy,
border.widths.xy,
adjusted_widths.xy);
set_edge_line(border.widths.xy,
corners.tl_outer,
vec2(1.0, 1.0));
edge_distances = vec4(p0 + adjusted_widths.xy,
p0 + inv_adjusted_widths.xy);
color_delta = vec2(1.0);
break;
}
case 1: {
p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
p1 = vec2(corners.tr_outer.x, corners.tr_inner.y);
color0 = border.colors[1];
color1 = border.colors[2];
vClipCenter = corners.tr_outer + vec2(-border.radii[0].z, border.radii[0].w);
vClipSign = vec2(-1.0, 1.0);
style = select_style(prim.user_data1, border.style.zy);
vec4 adjusted_widths = get_effective_border_widths(border, style);
vec4 inv_adjusted_widths = border.widths - adjusted_widths;
set_radii(style,
border.radii[0].zw,
border.widths.zy,
adjusted_widths.zy);
set_edge_line(border.widths.zy,
corners.tr_outer,
vec2(-1.0, 1.0));
edge_distances = vec4(p1.x - adjusted_widths.z,
p0.y + adjusted_widths.y,
p1.x - border.widths.z + adjusted_widths.z,
p0.y + inv_adjusted_widths.y);
color_delta = vec2(1.0, -1.0);
break;
}
case 2: {
p0 = corners.br_inner;
p1 = corners.br_outer;
color0 = border.colors[2];
color1 = border.colors[3];
vClipCenter = corners.br_outer - border.radii[1].xy;
vClipSign = vec2(-1.0, -1.0);
style = select_style(prim.user_data1, border.style.wz);
vec4 adjusted_widths = get_effective_border_widths(border, style);
vec4 inv_adjusted_widths = border.widths - adjusted_widths;
set_radii(style,
border.radii[1].xy,
border.widths.zw,
adjusted_widths.zw);
set_edge_line(border.widths.zw,
corners.br_outer,
vec2(-1.0, -1.0));
edge_distances = vec4(p1.x - adjusted_widths.z,
p1.y - adjusted_widths.w,
p1.x - border.widths.z + adjusted_widths.z,
p1.y - border.widths.w + adjusted_widths.w);
color_delta = vec2(-1.0);
break;
}
case 3: {
p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
p1 = vec2(corners.bl_inner.x, corners.bl_outer.y);
color0 = border.colors[3];
color1 = border.colors[0];
vClipCenter = corners.bl_outer + vec2(border.radii[1].z, -border.radii[1].w);
vClipSign = vec2(1.0, -1.0);
style = select_style(prim.user_data1, border.style.xw);
vec4 adjusted_widths = get_effective_border_widths(border, style);
vec4 inv_adjusted_widths = border.widths - adjusted_widths;
set_radii(style,
border.radii[1].zw,
border.widths.xw,
adjusted_widths.xw);
set_edge_line(border.widths.xw,
corners.bl_outer,
vec2(1.0, -1.0));
edge_distances = vec4(p0.x + adjusted_widths.x,
p1.y - adjusted_widths.w,
p0.x + inv_adjusted_widths.x,
p1.y - border.widths.w + adjusted_widths.w);
color_delta = vec2(-1.0, 1.0);
break;
}
}
switch (style) {
case BORDER_STYLE_DOUBLE: {
vEdgeDistance = edge_distances;
vAlphaSelect = 0.0;
vSDFSelect = 0.0;
break;
}
case BORDER_STYLE_GROOVE:
case BORDER_STYLE_RIDGE:
vEdgeDistance = vec4(edge_distances.xy, 0.0, 0.0);
vAlphaSelect = 1.0;
vSDFSelect = 1.0;
break;
case BORDER_STYLE_DOTTED:
// Disable normal clip radii for dotted corners, since
// all the clipping is handled by the clip mask.
vClipSign = vec2(0.0);
vEdgeDistance = vec4(0.0);
vAlphaSelect = 1.0;
vSDFSelect = 0.0;
break;
default: {
vEdgeDistance = vec4(0.0);
vAlphaSelect = 1.0;
vSDFSelect = 0.0;
break;
}
}
write_color(color0, color1, style, color_delta, prim.user_data1);
RectWithSize segment_rect;
segment_rect.p0 = p0;
segment_rect.size = p1 - p0;
#ifdef WR_FEATURE_TRANSFORM
TransformVertexInfo vi = write_transform_vertex(segment_rect,
prim.local_clip_rect,
prim.z,
prim.layer,
prim.task,
prim.local_rect);
#else
VertexInfo vi = write_vertex(segment_rect,
prim.local_clip_rect,
prim.z,
prim.layer,
prim.task,
prim.local_rect);
#endif
vLocalPos = vi.local_pos;
write_clip(vi.screen_pos, prim.clip_area);
}

View File

@ -1,63 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
void main(void) {
float alpha = 1.0;
#ifdef WR_FEATURE_TRANSFORM
alpha = 0.0;
vec2 local_pos = init_transform_fs(vLocalPos, alpha);
#else
vec2 local_pos = vLocalPos;
#endif
alpha = min(alpha, do_clip());
// Find the appropriate distance to apply the step over.
vec2 fw = fwidth(local_pos);
float afwidth = length(fw);
// Applies the math necessary to draw a style: double
// border. In the case of a solid border, the vertex
// shader sets interpolator values that make this have
// no effect.
// Select the x/y coord, depending on which axis this edge is.
vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
// Get signed distance from each of the inner edges.
float d0 = pos.x - vEdgeDistance.x;
float d1 = vEdgeDistance.y - pos.x;
// SDF union to select both outer edges.
float d = min(d0, d1);
// Select fragment on/off based on signed distance.
// No AA here, since we know we're on a straight edge
// and the width is rounded to a whole CSS pixel.
alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
// Mix color based on first distance.
// TODO(gw): Support AA for groove/ridge border edge with transforms.
vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0));
// Apply dashing / dotting parameters.
// Get the main-axis position relative to closest dot or dash.
float x = mod(pos.y - vClipParams.x, vClipParams.y);
// Calculate dash alpha (on/off) based on dash length
float dash_alpha = step(x, vClipParams.z);
// Get the dot alpha
vec2 dot_relative_pos = vec2(x, pos.x) - vClipParams.zw;
float dot_distance = length(dot_relative_pos) - vClipParams.z;
float dot_alpha = 1.0 - smoothstep(-0.5 * afwidth,
0.5 * afwidth,
dot_distance);
// Select between dot/dash alpha based on clip mode.
alpha = min(alpha, mix(dash_alpha, dot_alpha, vClipSelect));
oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
}

View File

@ -17,3 +17,287 @@ varying vec3 vLocalPos;
#else
varying vec2 vLocalPos;
#endif
#ifdef WR_VERTEX_SHADER
void write_edge_distance(float p0,
float original_width,
float adjusted_width,
float style,
float axis_select,
float sign_adjust) {
switch (int(style)) {
case BORDER_STYLE_DOUBLE:
vEdgeDistance = vec2(p0 + adjusted_width,
p0 + original_width - adjusted_width);
break;
case BORDER_STYLE_GROOVE:
case BORDER_STYLE_RIDGE:
vEdgeDistance = vec2(p0 + adjusted_width, sign_adjust);
break;
default:
vEdgeDistance = vec2(0.0);
break;
}
vAxisSelect = axis_select;
}
void write_alpha_select(float style) {
switch (int(style)) {
case BORDER_STYLE_DOUBLE:
vAlphaSelect = 0.0;
break;
default:
vAlphaSelect = 1.0;
break;
}
}
// write_color function is duplicated to work around a Mali-T880 GPU driver program link error.
// See https://github.com/servo/webrender/issues/1403 for more info.
// TODO: convert back to a single function once the driver issues are resolved, if ever.
void write_color0(vec4 color, float style, bool flip) {
vec2 modulate;
switch (int(style)) {
case BORDER_STYLE_GROOVE:
{
modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
break;
}
case BORDER_STYLE_RIDGE:
{
modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
break;
}
default:
modulate = vec2(1.0);
break;
}
vColor0 = vec4(color.rgb * modulate.x, color.a);
}
void write_color1(vec4 color, float style, bool flip) {
vec2 modulate;
switch (int(style)) {
case BORDER_STYLE_GROOVE:
{
modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
break;
}
case BORDER_STYLE_RIDGE:
{
modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
break;
}
default:
modulate = vec2(1.0);
break;
}
vColor1 = vec4(color.rgb * modulate.y, color.a);
}
void write_clip_params(float style,
float border_width,
float edge_length,
float edge_offset,
float center_line) {
// x = offset
// y = dash on + off length
// z = dash length
// w = center line of edge cross-axis (for dots only)
switch (int(style)) {
case BORDER_STYLE_DASHED: {
float desired_dash_length = border_width * 3.0;
// Consider half total length since there is an equal on/off for each dash.
float dash_count = ceil(0.5 * edge_length / desired_dash_length);
float dash_length = 0.5 * edge_length / dash_count;
vClipParams = vec4(edge_offset - 0.5 * dash_length,
2.0 * dash_length,
dash_length,
0.0);
vClipSelect = 0.0;
break;
}
case BORDER_STYLE_DOTTED: {
float diameter = border_width;
float radius = 0.5 * diameter;
float dot_count = ceil(0.5 * edge_length / diameter);
float empty_space = edge_length - dot_count * diameter;
float distance_between_centers = diameter + empty_space / dot_count;
vClipParams = vec4(edge_offset - radius,
distance_between_centers,
radius,
center_line);
vClipSelect = 1.0;
break;
}
default:
vClipParams = vec4(1.0);
vClipSelect = 0.0;
break;
}
}
void main(void) {
Primitive prim = load_primitive();
Border border = fetch_border(prim.specific_prim_address);
int sub_part = prim.user_data0;
BorderCorners corners = get_border_corners(border, prim.local_rect);
vec4 color = border.colors[sub_part];
// TODO(gw): Now that all border styles are supported, the switch
// statement below can be tidied up quite a bit.
float style;
bool color_flip;
RectWithSize segment_rect;
switch (sub_part) {
case 0: {
segment_rect.p0 = vec2(corners.tl_outer.x, corners.tl_inner.y);
segment_rect.size = vec2(border.widths.x, corners.bl_inner.y - corners.tl_inner.y);
vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.x));
write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0);
style = border.style.x;
color_flip = false;
write_clip_params(border.style.x,
border.widths.x,
segment_rect.size.y,
segment_rect.p0.y,
segment_rect.p0.x + 0.5 * segment_rect.size.x);
break;
}
case 1: {
segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.y));
write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0);
style = border.style.y;
color_flip = false;
write_clip_params(border.style.y,
border.widths.y,
segment_rect.size.x,
segment_rect.p0.x,
segment_rect.p0.y + 0.5 * segment_rect.size.y);
break;
}
case 2: {
segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.z));
write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0);
style = border.style.z;
color_flip = true;
write_clip_params(border.style.z,
border.widths.z,
segment_rect.size.y,
segment_rect.p0.y,
segment_rect.p0.x + 0.5 * segment_rect.size.x);
break;
}
case 3: {
segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.w));
write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0);
style = border.style.w;
color_flip = true;
write_clip_params(border.style.w,
border.widths.w,
segment_rect.size.x,
segment_rect.p0.x,
segment_rect.p0.y + 0.5 * segment_rect.size.y);
break;
}
}
write_alpha_select(style);
write_color0(color, style, color_flip);
write_color1(color, style, color_flip);
#ifdef WR_FEATURE_TRANSFORM
TransformVertexInfo vi = write_transform_vertex(segment_rect,
prim.local_clip_rect,
prim.z,
prim.layer,
prim.task,
prim.local_rect);
#else
VertexInfo vi = write_vertex(segment_rect,
prim.local_clip_rect,
prim.z,
prim.layer,
prim.task,
prim.local_rect);
#endif
vLocalPos = vi.local_pos;
write_clip(vi.screen_pos, prim.clip_area);
}
#endif
#ifdef WR_FRAGMENT_SHADER
void main(void) {
float alpha = 1.0;
#ifdef WR_FEATURE_TRANSFORM
alpha = 0.0;
vec2 local_pos = init_transform_fs(vLocalPos, alpha);
#else
vec2 local_pos = vLocalPos;
#endif
alpha = min(alpha, do_clip());
// Find the appropriate distance to apply the step over.
vec2 fw = fwidth(local_pos);
float afwidth = length(fw);
// Applies the math necessary to draw a style: double
// border. In the case of a solid border, the vertex
// shader sets interpolator values that make this have
// no effect.
// Select the x/y coord, depending on which axis this edge is.
vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
// Get signed distance from each of the inner edges.
float d0 = pos.x - vEdgeDistance.x;
float d1 = vEdgeDistance.y - pos.x;
// SDF union to select both outer edges.
float d = min(d0, d1);
// Select fragment on/off based on signed distance.
// No AA here, since we know we're on a straight edge
// and the width is rounded to a whole CSS pixel.
alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
// Mix color based on first distance.
// TODO(gw): Support AA for groove/ridge border edge with transforms.
vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0));
// Apply dashing / dotting parameters.
// Get the main-axis position relative to closest dot or dash.
float x = mod(pos.y - vClipParams.x, vClipParams.y);
// Calculate dash alpha (on/off) based on dash length
float dash_alpha = step(x, vClipParams.z);
// Get the dot alpha
vec2 dot_relative_pos = vec2(x, pos.x) - vClipParams.zw;
float dot_distance = length(dot_relative_pos) - vClipParams.z;
float dot_alpha = 1.0 - smoothstep(-0.5 * afwidth,
0.5 * afwidth,
dot_distance);
// Select between dot/dash alpha based on clip mode.
alpha = min(alpha, mix(dash_alpha, dot_alpha, vClipSelect));
oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
}
#endif

View File

@ -1,223 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
void write_edge_distance(float p0,
float original_width,
float adjusted_width,
float style,
float axis_select,
float sign_adjust) {
switch (int(style)) {
case BORDER_STYLE_DOUBLE:
vEdgeDistance = vec2(p0 + adjusted_width,
p0 + original_width - adjusted_width);
break;
case BORDER_STYLE_GROOVE:
case BORDER_STYLE_RIDGE:
vEdgeDistance = vec2(p0 + adjusted_width, sign_adjust);
break;
default:
vEdgeDistance = vec2(0.0);
break;
}
vAxisSelect = axis_select;
}
void write_alpha_select(float style) {
switch (int(style)) {
case BORDER_STYLE_DOUBLE:
vAlphaSelect = 0.0;
break;
default:
vAlphaSelect = 1.0;
break;
}
}
// write_color function is duplicated to work around a Mali-T880 GPU driver program link error.
// See https://github.com/servo/webrender/issues/1403 for more info.
// TODO: convert back to a single function once the driver issues are resolved, if ever.
void write_color0(vec4 color, float style, bool flip) {
vec2 modulate;
switch (int(style)) {
case BORDER_STYLE_GROOVE:
{
modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
break;
}
case BORDER_STYLE_RIDGE:
{
modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
break;
}
default:
modulate = vec2(1.0);
break;
}
vColor0 = vec4(color.rgb * modulate.x, color.a);
}
void write_color1(vec4 color, float style, bool flip) {
vec2 modulate;
switch (int(style)) {
case BORDER_STYLE_GROOVE:
{
modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
break;
}
case BORDER_STYLE_RIDGE:
{
modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
break;
}
default:
modulate = vec2(1.0);
break;
}
vColor1 = vec4(color.rgb * modulate.y, color.a);
}
void write_clip_params(float style,
float border_width,
float edge_length,
float edge_offset,
float center_line) {
// x = offset
// y = dash on + off length
// z = dash length
// w = center line of edge cross-axis (for dots only)
switch (int(style)) {
case BORDER_STYLE_DASHED: {
float desired_dash_length = border_width * 3.0;
// Consider half total length since there is an equal on/off for each dash.
float dash_count = ceil(0.5 * edge_length / desired_dash_length);
float dash_length = 0.5 * edge_length / dash_count;
vClipParams = vec4(edge_offset - 0.5 * dash_length,
2.0 * dash_length,
dash_length,
0.0);
vClipSelect = 0.0;
break;
}
case BORDER_STYLE_DOTTED: {
float diameter = border_width;
float radius = 0.5 * diameter;
float dot_count = ceil(0.5 * edge_length / diameter);
float empty_space = edge_length - dot_count * diameter;
float distance_between_centers = diameter + empty_space / dot_count;
vClipParams = vec4(edge_offset - radius,
distance_between_centers,
radius,
center_line);
vClipSelect = 1.0;
break;
}
default:
vClipParams = vec4(1.0);
vClipSelect = 0.0;
break;
}
}
void main(void) {
Primitive prim = load_primitive();
Border border = fetch_border(prim.specific_prim_address);
int sub_part = prim.user_data0;
BorderCorners corners = get_border_corners(border, prim.local_rect);
vec4 color = border.colors[sub_part];
// TODO(gw): Now that all border styles are supported, the switch
// statement below can be tidied up quite a bit.
float style;
bool color_flip;
RectWithSize segment_rect;
switch (sub_part) {
case 0: {
segment_rect.p0 = vec2(corners.tl_outer.x, corners.tl_inner.y);
segment_rect.size = vec2(border.widths.x, corners.bl_inner.y - corners.tl_inner.y);
vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.x));
write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0);
style = border.style.x;
color_flip = false;
write_clip_params(border.style.x,
border.widths.x,
segment_rect.size.y,
segment_rect.p0.y,
segment_rect.p0.x + 0.5 * segment_rect.size.x);
break;
}
case 1: {
segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.y));
write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0);
style = border.style.y;
color_flip = false;
write_clip_params(border.style.y,
border.widths.y,
segment_rect.size.x,
segment_rect.p0.x,
segment_rect.p0.y + 0.5 * segment_rect.size.y);
break;
}
case 2: {
segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.z));
write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0);
style = border.style.z;
color_flip = true;
write_clip_params(border.style.z,
border.widths.z,
segment_rect.size.y,
segment_rect.p0.y,
segment_rect.p0.x + 0.5 * segment_rect.size.x);
break;
}
case 3: {
segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.w));
write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0);
style = border.style.w;
color_flip = true;
write_clip_params(border.style.w,
border.widths.w,
segment_rect.size.x,
segment_rect.p0.x,
segment_rect.p0.y + 0.5 * segment_rect.size.y);
break;
}
}
write_alpha_select(style);
write_color0(color, style, color_flip);
write_color1(color, style, color_flip);
#ifdef WR_FEATURE_TRANSFORM
TransformVertexInfo vi = write_transform_vertex(segment_rect,
prim.local_clip_rect,
prim.z,
prim.layer,
prim.task,
prim.local_rect);
#else
VertexInfo vi = write_vertex(segment_rect,
prim.local_clip_rect,
prim.z,
prim.layer,
prim.task,
prim.local_rect);
#endif
vLocalPos = vi.local_pos;
write_clip(vi.screen_pos, prim.clip_area);
}

View File

@ -18,5 +18,6 @@ void main(void) {
uv = mix(vCacheUvRectCoords.xy, vCacheUvRectCoords.zw, uv);
// Modulate the box shadow by the color.
oFragColor = clip_scale * dither(vColor * texture(sCacheRGBA8, vec3(uv, vUv.z)));
float mask = texture(sColor1, vec3(uv, vUv.z)).r;
oFragColor = clip_scale * dither(vColor * vec4(1.0, 1.0, 1.0, mask));
}

View File

@ -27,7 +27,7 @@ void main(void) {
vUv.xy = (vi.local_pos - prim.local_rect.p0) / patch_size;
vMirrorPoint = 0.5 * prim.local_rect.size / patch_size;
vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
vec2 texture_size = vec2(textureSize(sCacheA8, 0));
vCacheUvRectCoords = vec4(patch_origin, patch_origin + patch_size_device_pixels) / texture_size.xyxy;
vColor = bs.color;

View File

@ -14,8 +14,9 @@ use ws;
// debug command queue. These are sent in a separate queue so
// that none of these types are exposed to the RenderApi interfaces.
// We can't use select!() as it's not stable...
pub enum DebugMsg {
FetchPasses(ws::Sender),
enum DebugMsg {
AddSender(ws::Sender),
RemoveSender(ws::util::Token),
}
// Represents a connection to a client.
@ -26,6 +27,16 @@ struct Server {
}
impl ws::Handler for Server {
fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
self.debug_tx.send(DebugMsg::AddSender(self.ws.clone())).ok();
Ok(())
}
fn on_close(&mut self, _: ws::CloseCode, _: &str) {
self.debug_tx.send(DebugMsg::RemoveSender(self.ws.token())).ok();
}
fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
match msg {
ws::Message::Text(string) => {
@ -49,9 +60,10 @@ impl ws::Handler for Server {
DebugCommand::EnableRenderTargetDebug(false)
}
"fetch_passes" => {
let msg = DebugMsg::FetchPasses(self.ws.clone());
self.debug_tx.send(msg).unwrap();
DebugCommand::Flush
DebugCommand::FetchPasses
}
"fetch_documents" => {
DebugCommand::FetchDocuments
}
msg => {
println!("unknown msg {}", msg);
@ -74,7 +86,8 @@ impl ws::Handler for Server {
pub struct DebugServer {
join_handle: Option<thread::JoinHandle<()>>,
broadcaster: ws::Sender,
pub debug_rx: Receiver<DebugMsg>,
debug_rx: Receiver<DebugMsg>,
senders: Vec<ws::Sender>,
}
impl DebugServer {
@ -101,6 +114,42 @@ impl DebugServer {
join_handle,
broadcaster,
debug_rx,
senders: Vec::new(),
}
}
pub fn send(&mut self, message: String) {
// Add any new connections that have been queued.
while let Ok(msg) = self.debug_rx.try_recv() {
match msg {
DebugMsg::AddSender(sender) => {
self.senders.push(sender);
}
DebugMsg::RemoveSender(token) => {
self.senders.retain(|sender| {
sender.token() != token
});
}
}
}
// Broadcast the message to all senders. Keep
// track of the ones that failed, so they can
// be removed from the active sender list.
let mut disconnected_senders = Vec::new();
for (i, sender) in self.senders.iter().enumerate() {
if let Err(..) = sender.send(message.clone()) {
disconnected_senders.push(i);
}
}
// Remove the broken senders from the list
// for next broadcast. Remove in reverse
// order so the indices are valid for the
// entire loop.
for i in disconnected_senders.iter().rev() {
self.senders.remove(*i);
}
}
}
@ -190,3 +239,45 @@ struct Batch {
description: String,
count: usize,
}
#[derive(Serialize)]
pub struct TreeNode {
description: String,
children: Vec<TreeNode>,
}
impl TreeNode {
pub fn new(description: &str) -> TreeNode {
TreeNode {
description: description.to_owned(),
children: Vec::new(),
}
}
pub fn add_child(&mut self, child: TreeNode) {
self.children.push(child);
}
pub fn add_item(&mut self, description: &str) {
self.children.push(TreeNode::new(description));
}
}
#[derive(Serialize)]
pub struct DocumentList {
kind: &'static str,
root: TreeNode,
}
impl DocumentList {
pub fn new() -> DocumentList {
DocumentList {
kind: "documents",
root: TreeNode::new("root"),
}
}
pub fn add(&mut self, item: TreeNode) {
self.root.add_child(item);
}
}

View File

@ -1176,6 +1176,7 @@ impl Device {
pub fn free_texture_storage(&mut self, texture: &mut Texture) {
debug_assert!(self.inside_frame);
debug_assert_eq!(self.bound_pbo, PBOId(0));
if texture.format == ImageFormat::Invalid {
return;
@ -1373,7 +1374,7 @@ impl Device {
pub fn update_pbo_data<T>(&mut self, data: &[T]) {
debug_assert!(self.inside_frame);
debug_assert!(self.bound_pbo.0 != 0);
debug_assert_ne!(self.bound_pbo, PBOId(0));
gl::buffer_data(&*self.gl,
gl::PIXEL_UNPACK_BUFFER,
@ -1383,7 +1384,7 @@ impl Device {
pub fn orphan_pbo(&mut self, new_size: usize) {
debug_assert!(self.inside_frame);
debug_assert!(self.bound_pbo.0 != 0);
debug_assert_ne!(self.bound_pbo, PBOId(0));
self.gl.buffer_data_untyped(gl::PIXEL_UNPACK_BUFFER,
new_size as isize,

View File

@ -95,6 +95,7 @@ impl NestedDisplayListInfo {
struct FlattenContext<'a> {
scene: &'a Scene,
builder: &'a mut FrameBuilder,
resource_cache: &'a ResourceCache,
tiled_image_map: TiledImageMap,
replacements: Vec<(ClipId, ClipId)>,
nested_display_list_info: Vec<NestedDisplayListInfo>,
@ -104,11 +105,12 @@ struct FlattenContext<'a> {
impl<'a> FlattenContext<'a> {
fn new(scene: &'a Scene,
builder: &'a mut FrameBuilder,
resource_cache: &ResourceCache)
resource_cache: &'a ResourceCache)
-> FlattenContext<'a> {
FlattenContext {
scene,
builder,
resource_cache,
tiled_image_map: resource_cache.get_tiled_image_map(),
replacements: Vec::new(),
nested_display_list_info: Vec::new(),
@ -565,12 +567,12 @@ impl Frame {
info.image_rendering);
}
SpecificDisplayItem::Text(ref text_info) => {
let instance = context.resource_cache.get_font_instance(text_info.font_key).unwrap();
context.builder.add_text(clip_and_scroll,
reference_frame_relative_offset,
item_rect_with_offset,
&clip_with_offset,
text_info.font_key,
text_info.size,
instance,
&text_info.color,
item.glyphs(),
item.display_list().get(item.glyphs()).count(),

View File

@ -4,7 +4,8 @@
use api::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
use api::{ExtendMode, FontInstance, FontRenderMode};
use api::{GlyphInstance, GlyphOptions, GradientStop};
use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, SubpixelDirection, TextShadow};
@ -875,24 +876,37 @@ impl FrameBuilder {
run_offset: LayoutVector2D,
rect: LayerRect,
local_clip: &LocalClip,
font_key: FontKey,
size: Au,
font: &FontInstance,
color: &ColorF,
glyph_range: ItemRange<GlyphInstance>,
glyph_count: usize,
glyph_options: Option<GlyphOptions>) {
// Trivial early out checks
if size.0 <= 0 {
if font.size.0 <= 0 {
return
}
// Sanity check - anything with glyphs bigger than this
// is probably going to consume too much memory to render
// efficiently anyway. This is specifically to work around
// the font_advance.html reftest, which creates a very large
// font as a crash test - the rendering is also ignored
// by the azure renderer.
if font.size >= Au::from_px(4096) {
return
}
// TODO(gw): Use a proper algorithm to select
// whether this item should be rendered with
// subpixel AA!
let mut normal_render_mode = self.config.default_font_render_mode;
let mut default_render_mode = self.config.default_font_render_mode.limit_by(font.render_mode);
if let Some(options) = glyph_options {
default_render_mode = default_render_mode.limit_by(options.render_mode);
}
// There are some conditions under which we can't use
// subpixel text rendering, even if enabled.
let mut normal_render_mode = default_render_mode;
if normal_render_mode == FontRenderMode::Subpixel {
if color.a != 1.0 {
normal_render_mode = FontRenderMode::Alpha;
@ -913,30 +927,32 @@ impl FrameBuilder {
// Shadows never use subpixel AA, but need to respect the alpha/mono flag
// for reftests.
let (shadow_render_mode, subpx_dir) = match self.config.default_font_render_mode {
let (shadow_render_mode, subpx_dir) = match default_render_mode {
FontRenderMode::Subpixel | FontRenderMode::Alpha => {
// TODO(gw): Expose subpixel direction in API once WR supports
// vertical text runs.
(FontRenderMode::Alpha, SubpixelDirection::Horizontal)
(FontRenderMode::Alpha, font.subpx_dir)
}
FontRenderMode::Mono => {
(FontRenderMode::Mono, SubpixelDirection::None)
}
};
let prim_font = FontInstance::new(font.font_key,
font.size,
*color,
normal_render_mode,
subpx_dir,
font.platform_options);
let prim = TextRunPrimitiveCpu {
font_key,
logical_font_size: size,
font: prim_font,
glyph_range,
glyph_count,
glyph_gpu_blocks: Vec::new(),
glyph_keys: Vec::new(),
glyph_options,
normal_render_mode,
shadow_render_mode,
offset: run_offset,
color: *color,
subpx_dir,
};
// Text shadows that have a blur radius of 0 need to be rendered as normal
@ -953,6 +969,7 @@ impl FrameBuilder {
let shadow_prim = &self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0];
if shadow_prim.shadow.blur_radius == 0.0 {
let mut text_prim = prim.clone();
text_prim.font.color = shadow_prim.shadow.color.into();
text_prim.color = shadow_prim.shadow.color;
text_prim.offset += shadow_prim.shadow.offset;
fast_text_shadow_prims.push(text_prim);

View File

@ -378,14 +378,12 @@ fn raterize_200_glyphs() {
let font_key = FontKey::new(IdNamespace(0), 0);
glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0));
let font = FontInstance {
font_key,
color: ColorF::new(0.0, 0.0, 0.0, 1.0).into(),
size: Au::from_px(32),
render_mode: FontRenderMode::Subpixel,
glyph_options: None,
subpx_dir: SubpixelDirection::Horizontal,
};
let font = FontInstance::new(font_key,
Au::from_px(32),
ColorF::new(0.0, 0.0, 0.0, 1.0),
FontRenderMode::Subpixel,
SubpixelDirection::Horizontal,
None);
let mut glyph_keys = Vec::with_capacity(200);
for i in 0..200 {

View File

@ -69,7 +69,7 @@ impl BatchTextures {
BatchTextures {
colors: [
SourceTexture::CacheRGBA8,
SourceTexture::Invalid,
SourceTexture::CacheA8,
SourceTexture::Invalid,
]
}
@ -156,8 +156,13 @@ impl RendererFrame {
}
}
pub enum DebugOutput {
FetchDocuments(String),
}
pub enum ResultMsg {
DebugCommand(DebugCommand),
DebugOutput(DebugOutput),
RefreshShader(PathBuf),
NewFrame(DocumentId, RendererFrame, TextureUpdateList, BackendProfileCounters),
UpdateResources { updates: TextureUpdateList, cancel_rendering: bool },

View File

@ -2,8 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{FontKey, FontRenderMode, GlyphDimensions};
use api::{FontInstance, GlyphKey, GlyphOptions, SubpixelDirection};
use api::{FontInstance, FontInstancePlatformOptions, FontKey, FontRenderMode};
use api::{GlyphDimensions, GlyphKey, GlyphOptions, SubpixelDirection};
use gamma_lut::{GammaLut, Color as ColorLut};
use internal_types::FastHashMap;
@ -46,9 +46,9 @@ fn dwrite_texture_type(render_mode: FontRenderMode) ->
}
}
fn dwrite_measure_mode(render_mode: FontRenderMode, options: Option<GlyphOptions>) ->
fn dwrite_measure_mode(render_mode: FontRenderMode, options: Option<FontInstancePlatformOptions>) ->
dwrote::DWRITE_MEASURING_MODE {
if let Some(GlyphOptions{ force_gdi_rendering: true, .. }) = options {
if let Some(FontInstancePlatformOptions{ force_gdi_rendering: true, .. }) = options {
return dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC;
}
@ -63,9 +63,9 @@ fn dwrite_render_mode(font_face: &dwrote::FontFace,
render_mode: FontRenderMode,
em_size: f32,
measure_mode: dwrote::DWRITE_MEASURING_MODE,
options: Option<GlyphOptions>) ->
options: Option<FontInstancePlatformOptions>) ->
dwrote::DWRITE_RENDERING_MODE {
if let Some(GlyphOptions{ force_gdi_rendering: true, .. }) = options {
if let Some(FontInstancePlatformOptions{ force_gdi_rendering: true, .. }) = options {
return dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC;
}
@ -175,12 +175,12 @@ impl FontContext {
};
let dwrite_measure_mode = dwrite_measure_mode(font.render_mode,
font.glyph_options);
font.platform_options);
let dwrite_render_mode = dwrite_render_mode(face,
font.render_mode,
font.size.to_f32_px(),
dwrite_measure_mode,
font.glyph_options);
font.platform_options);
let (x_offset, y_offset) = font.get_subpx_offset(key);
let transform = Some(
@ -302,7 +302,7 @@ impl FontContext {
let mut pixels = analysis.create_alpha_texture(texture_type, bounds);
if font.render_mode != FontRenderMode::Mono {
let lut_correction = match font.glyph_options {
let lut_correction = match font.platform_options {
Some(option) => {
if option.force_gdi_rendering {
&self.gdi_gamma_lut

View File

@ -3,10 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
use api::{ExtendMode, FontRenderMode, GlyphInstance, GradientStop};
use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
use api::{GlyphKey, LayerToWorldTransform, TileOffset, YuvColorSpace, YuvFormat};
use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle, SubpixelDirection};
use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle};
use app_units::Au;
use border::BorderCornerInstance;
use euclid::{Size2D};
@ -504,21 +504,17 @@ pub struct TextShadowPrimitiveCpu {
#[derive(Debug, Clone)]
pub struct TextRunPrimitiveCpu {
pub font_key: FontKey,
pub font: FontInstance,
pub offset: LayerVector2D,
pub logical_font_size: Au,
pub glyph_range: ItemRange<GlyphInstance>,
pub glyph_count: usize,
pub glyph_keys: Vec<GlyphKey>,
pub glyph_gpu_blocks: Vec<GpuBlockData>,
pub glyph_options: Option<GlyphOptions>,
pub normal_render_mode: FontRenderMode,
pub shadow_render_mode: FontRenderMode,
pub color: ColorF,
pub subpx_dir: SubpixelDirection,
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum TextRunMode {
Normal,
Shadow,
@ -531,18 +527,18 @@ impl TextRunPrimitiveCpu {
display_list: &BuiltDisplayList,
run_mode: TextRunMode,
gpu_cache: &mut GpuCache) {
let font_size_dp = self.logical_font_size.scale_by(device_pixel_ratio);
let render_mode = match run_mode {
TextRunMode::Normal => self.normal_render_mode,
TextRunMode::Shadow => self.shadow_render_mode,
};
let mut font = self.font.clone();
font.size = font.size.scale_by(device_pixel_ratio);
match run_mode {
TextRunMode::Shadow => {
font.render_mode = self.shadow_render_mode;
}
TextRunMode::Normal => {}
}
let font = FontInstance::new(self.font_key,
font_size_dp,
self.color,
render_mode,
self.glyph_options,
self.subpx_dir);
if run_mode == TextRunMode::Shadow {
font.render_mode = self.shadow_render_mode;
}
// Cache the glyph positions, if not in the cache already.
// TODO(gw): In the future, remove `glyph_instances`
@ -590,7 +586,7 @@ impl TextRunPrimitiveCpu {
request.push(self.color);
request.push([self.offset.x,
self.offset.y,
self.subpx_dir as u32 as f32,
self.font.subpx_dir as u32 as f32,
0.0]);
request.extend_from_slice(&self.glyph_gpu_blocks);

View File

@ -131,6 +131,11 @@ impl ResourceProfileCounter {
self.value += 1;
self.size += size;
}
pub fn set(&mut self, count: usize, size: usize) {
self.value = count;
self.size = size;
}
}
impl ProfileCounter for ResourceProfileCounter {

View File

@ -2,14 +2,18 @@
* 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/. */
#[cfg(feature = "debugger")]
use debug_server;
use frame::Frame;
use frame_builder::FrameBuilderConfig;
use gpu_cache::GpuCache;
use internal_types::{FastHashMap, ResultMsg, RendererFrame};
use internal_types::{DebugOutput, FastHashMap, ResultMsg, RendererFrame};
use profiler::{BackendProfileCounters, ResourceProfileCounters};
use record::ApiRecordingReceiver;
use resource_cache::ResourceCache;
use scene::Scene;
#[cfg(feature = "debugger")]
use serde_json;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::Sender;
use std::u32;
@ -19,9 +23,11 @@ use thread_profiler::register_thread_with_profiler;
use rayon::ThreadPool;
use api::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
use api::channel::{PayloadSender, PayloadSenderHelperMethods};
use api::{ApiMsg, BlobImageRenderer, BuiltDisplayList, DeviceIntPoint};
use api::{ApiMsg, DebugCommand, BlobImageRenderer, BuiltDisplayList, DeviceIntPoint};
use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentId, DocumentMsg};
use api::{IdNamespace, LayerPoint, RenderNotifier};
#[cfg(feature = "debugger")]
use api::{BuiltDisplayListIter, SpecificDisplayItem};
struct Document {
scene: Scene,
@ -275,6 +281,12 @@ impl RenderBackend {
DocumentOp::Nop
}
}
DocumentMsg::RemovePipeline(pipeline_id) => {
profile_scope!("RemovePipeline");
doc.scene.remove_pipeline(pipeline_id);
DocumentOp::Nop
}
DocumentMsg::Scroll(delta, cursor, move_phase) => {
profile_scope!("Scroll");
let _timer = profile_counters.total_time.timer();
@ -462,7 +474,15 @@ impl RenderBackend {
self.notifier.lock().unwrap().as_mut().unwrap().new_frame_ready();
}
ApiMsg::DebugCommand(option) => {
let msg = ResultMsg::DebugCommand(option);
let msg = match option {
DebugCommand::FetchDocuments => {
let json = self.get_docs_for_debugger();
ResultMsg::DebugOutput(DebugOutput::FetchDocuments(json))
}
_ => {
ResultMsg::DebugCommand(option)
}
};
self.result_tx.send(msg).unwrap();
let notifier = self.notifier.lock();
notifier.unwrap()
@ -514,4 +534,139 @@ impl RenderBackend {
let mut notifier = self.notifier.lock();
notifier.as_mut().unwrap().as_mut().unwrap().new_scroll_frame_ready(composite_needed);
}
#[cfg(not(feature = "debugger"))]
fn get_docs_for_debugger(&self) -> String {
String::new()
}
#[cfg(feature = "debugger")]
fn traverse_items<'a>(&self,
traversal: &mut BuiltDisplayListIter<'a>,
node: &mut debug_server::TreeNode) {
loop {
let subtraversal = {
let item = match traversal.next() {
Some(item) => item,
None => break,
};
match *item.item() {
display_item @ SpecificDisplayItem::PushStackingContext(..) => {
let mut subtraversal = item.sub_iter();
let mut child_node = debug_server::TreeNode::new(&display_item.debug_string());
self.traverse_items(&mut subtraversal, &mut child_node);
node.add_child(child_node);
Some(subtraversal)
}
SpecificDisplayItem::PopStackingContext => {
return;
}
display_item => {
node.add_item(&display_item.debug_string());
None
}
}
};
// If flatten_item created a sub-traversal, we need `traversal` to have the
// same state as the completed subtraversal, so we reinitialize it here.
if let Some(subtraversal) = subtraversal {
*traversal = subtraversal;
}
}
}
#[cfg(feature = "debugger")]
fn get_docs_for_debugger(&self) -> String {
let mut docs = debug_server::DocumentList::new();
for (_, doc) in &self.documents {
let mut debug_doc = debug_server::TreeNode::new("document");
for (_, display_list) in &doc.scene.display_lists {
let mut debug_dl = debug_server::TreeNode::new("display_list");
self.traverse_items(&mut display_list.iter(), &mut debug_dl);
debug_doc.add_child(debug_dl);
}
docs.add(debug_doc);
}
serde_json::to_string(&docs).unwrap()
}
}
#[cfg(feature = "debugger")]
trait ToDebugString {
fn debug_string(&self) -> String;
}
#[cfg(feature = "debugger")]
impl ToDebugString for SpecificDisplayItem {
fn debug_string(&self) -> String {
match *self {
SpecificDisplayItem::Image(..) => {
String::from("image")
}
SpecificDisplayItem::YuvImage(..) => {
String::from("yuv_image")
}
SpecificDisplayItem::Text(..) => {
String::from("text")
}
SpecificDisplayItem::Rectangle(..) => {
String::from("rectangle")
}
SpecificDisplayItem::Line(..) => {
String::from("line")
}
SpecificDisplayItem::Gradient(..) => {
String::from("gradient")
}
SpecificDisplayItem::RadialGradient(..) => {
String::from("radial_gradient")
}
SpecificDisplayItem::BoxShadow(..) => {
String::from("box_shadow")
}
SpecificDisplayItem::Border(..) => {
String::from("border")
}
SpecificDisplayItem::PushStackingContext(..) => {
String::from("push_stacking_context")
}
SpecificDisplayItem::Iframe(..) => {
String::from("iframe")
}
SpecificDisplayItem::Clip(..) => {
String::from("clip")
}
SpecificDisplayItem::ScrollFrame(..) => {
String::from("scroll_frame")
}
SpecificDisplayItem::StickyFrame(..) => {
String::from("sticky_frame")
}
SpecificDisplayItem::PushNestedDisplayList => {
String::from("push_nested_display_list")
}
SpecificDisplayItem::PopNestedDisplayList => {
String::from("pop_nested_display_list")
}
SpecificDisplayItem::SetGradientStops => {
String::from("set_gradient_stops")
}
SpecificDisplayItem::PopStackingContext => {
String::from("pop_stacking_context")
}
SpecificDisplayItem::PushTextShadow(..) => {
String::from("push_text_shadow")
}
SpecificDisplayItem::PopTextShadow => {
String::from("pop_text_shadow")
}
}
}
}

View File

@ -166,6 +166,7 @@ pub struct RenderTaskData {
pub enum RenderTaskKind {
Alpha(AlphaRenderTask),
CachePrimitive(PrimitiveIndex),
BoxShadow(PrimitiveIndex),
CacheMask(CacheMaskTask),
VerticalBlur(DeviceIntLength),
HorizontalBlur(DeviceIntLength),
@ -217,7 +218,7 @@ impl RenderTask {
cache_key: Some(RenderTaskKey::BoxShadow(key)),
children: Vec::new(),
location: RenderTaskLocation::Dynamic(None, size),
kind: RenderTaskKind::CachePrimitive(prim_index),
kind: RenderTaskKind::BoxShadow(prim_index),
}
}
@ -344,6 +345,7 @@ impl RenderTask {
match self.kind {
RenderTaskKind::Alpha(ref mut task) => task,
RenderTaskKind::CachePrimitive(..) |
RenderTaskKind::BoxShadow(..) |
RenderTaskKind::CacheMask(..) |
RenderTaskKind::VerticalBlur(..) |
RenderTaskKind::Readback(..) |
@ -356,6 +358,7 @@ impl RenderTask {
match self.kind {
RenderTaskKind::Alpha(ref task) => task,
RenderTaskKind::CachePrimitive(..) |
RenderTaskKind::BoxShadow(..) |
RenderTaskKind::CacheMask(..) |
RenderTaskKind::VerticalBlur(..) |
RenderTaskKind::Readback(..) |
@ -396,7 +399,8 @@ impl RenderTask {
],
}
}
RenderTaskKind::CachePrimitive(..) => {
RenderTaskKind::CachePrimitive(..) |
RenderTaskKind::BoxShadow(..) => {
let (target_rect, target_index) = self.get_target_rect();
RenderTaskData {
data: [
@ -500,7 +504,10 @@ impl RenderTask {
RenderTaskKind::VerticalBlur(..) |
RenderTaskKind::Readback(..) |
RenderTaskKind::HorizontalBlur(..) => RenderTargetKind::Color,
RenderTaskKind::CacheMask(..) => RenderTargetKind::Alpha,
RenderTaskKind::CacheMask(..) |
RenderTaskKind::BoxShadow(..) => RenderTargetKind::Alpha,
RenderTaskKind::Alias(..) => {
panic!("BUG: target_kind() called on invalidated task");
}

View File

@ -17,7 +17,7 @@ use api::DebugCommand;
use debug_colors;
use debug_render::DebugRenderer;
#[cfg(feature = "debugger")]
use debug_server::{self, DebugMsg, DebugServer};
use debug_server::{self, DebugServer};
use device::{DepthFunction, Device, FrameId, Program, Texture, VertexDescriptor, GpuMarker, GpuProfiler, PBOId};
use device::{GpuTimer, TextureFilter, VAO, VertexUsageHint, FileWatcherHandler, TextureTarget, ShaderError};
use device::{ExternalTexture, get_gl_format_bgra, TextureSlot, VertexAttribute, VertexAttributeKind};
@ -26,7 +26,7 @@ use frame_builder::FrameBuilderConfig;
use gleam::gl;
use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
use internal_types::{FastHashMap, CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp};
use internal_types::{TextureUpdateList, RenderTargetMode, TextureUpdateSource};
use internal_types::{DebugOutput, TextureUpdateList, RenderTargetMode, TextureUpdateSource};
use internal_types::{BatchTextures, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, SourceTexture};
use profiler::{Profiler, BackendProfileCounters};
use profiler::{GpuProfileTag, RendererProfileTimers, RendererProfileCounters};
@ -1587,6 +1587,13 @@ impl Renderer {
ResultMsg::RefreshShader(path) => {
self.pending_shader_updates.push(path);
}
ResultMsg::DebugOutput(output) => {
match output {
DebugOutput::FetchDocuments(string) => {
self.debug_server.send(string);
}
}
}
ResultMsg::DebugCommand(command) => {
self.handle_debug_command(command);
}
@ -1595,72 +1602,66 @@ impl Renderer {
}
#[cfg(not(feature = "debugger"))]
fn update_debug_server(&self) {
fn get_passes_for_debugger(&self) -> String {
// Avoid unused param warning.
let _ = &self.debug_server;
String::new()
}
#[cfg(feature = "debugger")]
fn update_debug_server(&self) {
while let Ok(msg) = self.debug_server.debug_rx.try_recv() {
match msg {
DebugMsg::FetchPasses(sender) => {
let mut debug_passes = debug_server::PassList::new();
fn get_passes_for_debugger(&self) -> String {
let mut debug_passes = debug_server::PassList::new();
if let Some(frame) = self.current_frame.as_ref().and_then(|frame| frame.frame.as_ref()) {
for pass in &frame.passes {
let mut debug_pass = debug_server::Pass::new();
if let Some(frame) = self.current_frame.as_ref().and_then(|frame| frame.frame.as_ref()) {
for pass in &frame.passes {
let mut debug_pass = debug_server::Pass::new();
for target in &pass.alpha_targets.targets {
let mut debug_target = debug_server::Target::new("A8");
for target in &pass.alpha_targets.targets {
let mut debug_target = debug_server::Target::new("A8");
debug_target.add(debug_server::BatchKind::Clip, "Clear", target.clip_batcher.border_clears.len());
debug_target.add(debug_server::BatchKind::Clip, "Borders", target.clip_batcher.borders.len());
debug_target.add(debug_server::BatchKind::Clip, "Rectangles", target.clip_batcher.rectangles.len());
for (_, items) in target.clip_batcher.images.iter() {
debug_target.add(debug_server::BatchKind::Clip, "Image mask", items.len());
}
debug_target.add(debug_server::BatchKind::Clip, "Clear", target.clip_batcher.border_clears.len());
debug_target.add(debug_server::BatchKind::Clip, "Borders", target.clip_batcher.borders.len());
debug_target.add(debug_server::BatchKind::Clip, "Rectangles", target.clip_batcher.rectangles.len());
for (_, items) in target.clip_batcher.images.iter() {
debug_target.add(debug_server::BatchKind::Clip, "Image mask", items.len());
}
debug_target.add(debug_server::BatchKind::Cache, "Box Shadow", target.box_shadow_cache_prims.len());
debug_pass.add(debug_target);
}
debug_pass.add(debug_target);
}
for target in &pass.color_targets.targets {
let mut debug_target = debug_server::Target::new("RGBA8");
for target in &pass.color_targets.targets {
let mut debug_target = debug_server::Target::new("RGBA8");
debug_target.add(debug_server::BatchKind::Cache, "Vertical Blur", target.vertical_blurs.len());
debug_target.add(debug_server::BatchKind::Cache, "Horizontal Blur", target.horizontal_blurs.len());
debug_target.add(debug_server::BatchKind::Cache, "Box Shadow", target.box_shadow_cache_prims.len());
debug_target.add(debug_server::BatchKind::Cache, "Text Shadow", target.text_run_cache_prims.len());
debug_target.add(debug_server::BatchKind::Cache, "Lines", target.line_cache_prims.len());
debug_target.add(debug_server::BatchKind::Cache, "Vertical Blur", target.vertical_blurs.len());
debug_target.add(debug_server::BatchKind::Cache, "Horizontal Blur", target.horizontal_blurs.len());
debug_target.add(debug_server::BatchKind::Cache, "Text Shadow", target.text_run_cache_prims.len());
debug_target.add(debug_server::BatchKind::Cache, "Lines", target.line_cache_prims.len());
for batch in target.alpha_batcher
.batch_list
.opaque_batch_list
.batches
.iter()
.rev() {
debug_target.add(debug_server::BatchKind::Opaque, batch.key.kind.debug_name(), batch.instances.len());
}
for batch in &target.alpha_batcher
.batch_list
.alpha_batch_list
.batches {
debug_target.add(debug_server::BatchKind::Alpha, batch.key.kind.debug_name(), batch.instances.len());
}
debug_pass.add(debug_target);
}
debug_passes.add(debug_pass);
}
for batch in target.alpha_batcher
.batch_list
.opaque_batch_list
.batches
.iter()
.rev() {
debug_target.add(debug_server::BatchKind::Opaque, batch.key.kind.debug_name(), batch.instances.len());
}
let json = serde_json::to_string(&debug_passes).unwrap();
sender.send(json).ok();
for batch in &target.alpha_batcher
.batch_list
.alpha_batch_list
.batches {
debug_target.add(debug_server::BatchKind::Alpha, batch.key.kind.debug_name(), batch.instances.len());
}
debug_pass.add(debug_target);
}
debug_passes.add(debug_pass);
}
}
serde_json::to_string(&debug_passes).unwrap()
}
fn handle_debug_command(&mut self, command: DebugCommand) {
@ -1686,8 +1687,10 @@ impl Renderer {
self.debug_flags.remove(RENDER_TARGET_DBG);
}
}
DebugCommand::Flush => {
self.update_debug_server();
DebugCommand::FetchDocuments => {}
DebugCommand::FetchPasses => {
let json = self.get_passes_for_debugger();
self.debug_server.send(json);
}
}
}
@ -1839,7 +1842,6 @@ impl Renderer {
// Ensure no PBO is bound when creating the texture storage,
// or GL will attempt to read data from there.
self.device.bind_pbo(None);
self.device.init_texture(texture,
width,
height,
@ -1882,6 +1884,9 @@ impl Renderer {
layer_index,
stride,
0);
// Ensure that other texture updates won't read from this PBO.
self.device.bind_pbo(None);
}
TextureUpdateOp::Free => {
let texture = &mut self.texture_resolver.cache_texture_map[update.id.0];
@ -1890,9 +1895,6 @@ impl Renderer {
}
}
}
// Ensure that other texture updates won't read from this PBO.
self.device.bind_pbo(None);
}
fn draw_instanced_batch<T>(&mut self,
@ -2164,16 +2166,6 @@ impl Renderer {
}
}
// Draw any box-shadow caches for this target.
if !target.box_shadow_cache_prims.is_empty() {
self.device.set_blend(false);
let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_BOX_SHADOW);
self.cs_box_shadow.bind(&mut self.device, projection);
self.draw_instanced_batch(&target.box_shadow_cache_prims,
VertexArrayKind::CacheBoxShadow,
&BatchTextures::no_texture());
}
// Draw any textrun caches for this target. For now, this
// is only used to cache text runs that are to be blurred
// for text-shadow support. In the future it may be worth
@ -2297,6 +2289,16 @@ impl Renderer {
target.used_rect());
}
// Draw any box-shadow caches for this target.
if !target.box_shadow_cache_prims.is_empty() {
self.device.set_blend(false);
let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_BOX_SHADOW);
self.cs_box_shadow.bind(&mut self.device, projection);
self.draw_instanced_batch(&target.box_shadow_cache_prims,
VertexArrayKind::CacheBoxShadow,
&BatchTextures::no_texture());
}
// Draw the clip items into the tiled alpha mask.
{
let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_CLIP);
@ -2863,4 +2865,6 @@ impl DebugServer {
pub fn new(_: MsgSender<ApiMsg>) -> DebugServer {
DebugServer
}
pub fn send(&mut self, _: String) {}
}

View File

@ -2,6 +2,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/. */
use app_units::Au;
use device::TextureFilter;
use frame::FrameId;
use glyph_cache::GlyphCache;
@ -17,7 +18,9 @@ use texture_cache::{TextureCache, TextureCacheHandle};
use api::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest};
use api::{BlobImageResources, BlobImageData, ResourceUpdates, ResourceUpdate, AddFont};
use api::{DevicePoint, DeviceUintRect, DeviceUintSize};
use api::{Epoch, FontInstance, FontKey, FontTemplate};
use api::{Epoch, FontInstance, FontInstanceKey, FontKey, FontTemplate};
use api::{FontInstanceOptions, FontInstancePlatformOptions};
use api::{ColorF, FontRenderMode, SubpixelDirection};
use api::{GlyphDimensions, GlyphKey, IdNamespace};
use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering};
use api::{TileOffset, TileSize};
@ -170,6 +173,7 @@ impl Into<BlobImageRequest> for ImageRequest {
struct Resources {
font_templates: FastHashMap<FontKey, FontTemplate>,
font_instances: FastHashMap<FontInstanceKey, FontInstance>,
image_templates: ImageTemplates,
}
@ -213,6 +217,7 @@ impl ResourceCache {
cached_images: ResourceClassCache::new(),
resources: Resources {
font_templates: FastHashMap::default(),
font_instances: FastHashMap::default(),
image_templates: ImageTemplates::new(),
},
cached_glyph_dimensions: FastHashMap::default(),
@ -279,6 +284,13 @@ impl ResourceCache {
ResourceUpdate::DeleteFont(font) => {
self.delete_font_template(font);
}
ResourceUpdate::AddFontInstance(instance) => {
self.add_font_instance(instance.key, instance.font_key, instance.glyph_size,
instance.options, instance.platform_options);
}
ResourceUpdate::DeleteFontInstance(instance) => {
self.delete_font_instance(instance);
}
}
}
}
@ -298,6 +310,40 @@ impl ResourceCache {
}
}
pub fn add_font_instance(&mut self,
instance_key: FontInstanceKey,
font_key: FontKey,
glyph_size: Au,
options: Option<FontInstanceOptions>,
platform_options: Option<FontInstancePlatformOptions>) {
let mut render_mode = FontRenderMode::Subpixel;
let mut subpx_dir = SubpixelDirection::Horizontal;
if let Some(options) = options {
render_mode = options.render_mode;
if render_mode == FontRenderMode::Mono {
subpx_dir = SubpixelDirection::None;
}
}
let instance = FontInstance::new(font_key,
glyph_size,
ColorF::new(0.0, 0.0, 0.0, 1.0),
render_mode,
subpx_dir,
platform_options);
self.resources.font_instances.insert(instance_key, instance);
}
pub fn delete_font_instance(&mut self, instance_key: FontInstanceKey) {
self.resources.font_instances.remove(&instance_key);
if let Some(ref mut r) = self.blob_image_renderer {
r.delete_font_instance(instance_key);
}
}
pub fn get_font_instance(&self, instance_key: FontInstanceKey) -> Option<&FontInstance> {
self.resources.font_instances.get(&instance_key)
}
pub fn add_image_template(&mut self,
image_key: ImageKey,
descriptor: ImageDescriptor,
@ -607,13 +653,11 @@ impl ResourceCache {
);
// Apply any updates of new / updated images (incl. blobs) to the texture cache.
self.update_texture_cache(gpu_cache, texture_cache_profile);
self.texture_cache.end_frame();
self.update_texture_cache(gpu_cache);
self.texture_cache.end_frame(texture_cache_profile);
}
fn update_texture_cache(&mut self,
gpu_cache: &mut GpuCache,
_texture_cache_profile: &mut TextureCacheProfileCounters) {
fn update_texture_cache(&mut self, gpu_cache: &mut GpuCache) {
for request in self.pending_image_requests.drain() {
let image_template = self.resources.image_templates.get_mut(request.key).unwrap();
debug_assert!(image_template.data.uses_texture_cache());
@ -667,7 +711,7 @@ impl ResourceCache {
let (stride, offset) = if tiled_on_cpu {
(image_descriptor.stride, 0)
} else {
let bpp = image_descriptor.format.bytes_per_pixel().unwrap();
let bpp = image_descriptor.format.bytes_per_pixel();
let stride = image_descriptor.compute_stride();
let offset = image_descriptor.offset + tile.y as u32 * tile_size as u32 * stride
+ tile.x as u32 * tile_size as u32 * bpp;

View File

@ -127,4 +127,13 @@ impl Scene {
self.pipeline_map.insert(pipeline_id, new_pipeline);
}
pub fn remove_pipeline(&mut self,
pipeline_id: PipelineId) {
if self.root_pipeline_id == Some(pipeline_id) {
self.root_pipeline_id = None;
}
self.display_lists.remove(&pipeline_id);
self.pipeline_map.remove(&pipeline_id);
}
}

View File

@ -8,6 +8,7 @@ use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle};
use gpu_cache::{GpuCache, GpuCacheHandle};
use internal_types::{SourceTexture, TextureUpdate, TextureUpdateOp};
use internal_types::{CacheTextureId, RenderTargetMode, TextureUpdateList, TextureUpdateSource};
use profiler::{ResourceProfileCounter, TextureCacheProfileCounters};
use resource_cache::CacheItem;
use std::cmp;
use std::mem;
@ -223,8 +224,13 @@ impl TextureCache {
self.frame_id = frame_id;
}
pub fn end_frame(&mut self) {
pub fn end_frame(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters) {
self.expire_old_standalone_entries();
self.array_a8.update_profile(&mut texture_cache_profile.pages_a8);
self.array_rg8.update_profile(&mut texture_cache_profile.pages_rg8);
self.array_rgb8.update_profile(&mut texture_cache_profile.pages_rgb8);
self.array_rgba8.update_profile(&mut texture_cache_profile.pages_rgba8);
}
// Request an item in the texture cache. All images that will
@ -835,6 +841,18 @@ impl TextureArray {
}
}
fn update_profile(&self, counter: &mut ResourceProfileCounter) {
if self.is_allocated {
let size = TEXTURE_ARRAY_LAYERS as u32 *
TEXTURE_LAYER_DIMENSIONS *
TEXTURE_LAYER_DIMENSIONS *
self.format.bytes_per_pixel();
counter.set(TEXTURE_ARRAY_LAYERS as usize, size as usize);
} else {
counter.set(0, 0);
}
}
// Allocate space in this texture array.
fn alloc(&mut self,
width: u32,
@ -957,7 +975,7 @@ impl TextureUpdate {
}
ImageData::Raw(bytes) => {
let finish = descriptor.offset +
descriptor.width * descriptor.format.bytes_per_pixel().unwrap_or(0) +
descriptor.width * descriptor.format.bytes_per_pixel() +
(descriptor.height-1) * descriptor.compute_stride();
assert!(bytes.len() >= finish as usize);

View File

@ -22,7 +22,7 @@ use std::{f32, i32, usize};
use texture_allocator::GuillotineAllocator;
use util::{TransformedRect, TransformedRectKind};
use api::{BuiltDisplayList, ClipAndScrollInfo, ClipId, ColorF, DeviceIntPoint, ImageKey};
use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize, FontInstance};
use api::{DeviceIntRect, DeviceIntSize, DeviceUintPoint, DeviceUintSize};
use api::{ExternalImageType, FilterOp, FontRenderMode, ImageRendering, LayerRect};
use api::{LayerToWorldTransform, MixBlendMode, PipelineId, PropertyBinding, TransformStyle};
use api::{TileOffset, WorldToLayerTransform, YuvColorSpace, YuvFormat, LayerVector2D};
@ -44,7 +44,7 @@ impl AlphaBatchHelpers for PrimitiveStore {
match metadata.prim_kind {
PrimitiveKind::TextRun => {
let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0];
match text_run_cpu.normal_render_mode {
match text_run_cpu.font.render_mode {
FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
}
@ -453,17 +453,12 @@ impl AlphaRenderItem {
}
PrimitiveKind::TextRun => {
let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
let font_size_dp = text_cpu.logical_font_size.scale_by(ctx.device_pixel_ratio);
// TODO(gw): avoid / recycle this allocation in the future.
let mut instances = Vec::new();
let font = FontInstance::new(text_cpu.font_key,
font_size_dp,
text_cpu.color,
text_cpu.normal_render_mode,
text_cpu.glyph_options,
text_cpu.subpx_dir);
let mut font = text_cpu.font.clone();
font.size = font.size.scale_by(ctx.device_pixel_ratio);
let texture_id = ctx.resource_cache.get_glyphs(font,
&text_cpu.glyph_keys,
@ -576,8 +571,9 @@ impl AlphaRenderItem {
let box_shadow = &ctx.prim_store.cpu_box_shadows[prim_metadata.cpu_prim_index.0];
let cache_task_id = prim_metadata.render_task_id.unwrap();
let cache_task_address = render_tasks.get_task_address(cache_task_id);
let textures = BatchTextures::render_target_cache();
let key = AlphaBatchKey::new(AlphaBatchKind::BoxShadow, flags, blend_mode, no_textures);
let key = AlphaBatchKey::new(AlphaBatchKind::BoxShadow, flags, blend_mode, textures);
let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
for rect_index in 0..box_shadow.rects.len() {
@ -906,7 +902,6 @@ impl<T: RenderTarget> RenderTargetList<T> {
/// A render target represents a number of rendering operations on a surface.
pub struct ColorRenderTarget {
pub alpha_batcher: AlphaBatcher,
pub box_shadow_cache_prims: Vec<BoxShadowCacheInstance>,
// List of text runs to be cached to this render target.
// TODO(gw): For now, assume that these all come from
// the same source texture id. This is almost
@ -933,7 +928,6 @@ impl RenderTarget for ColorRenderTarget {
fn new(size: DeviceUintSize) -> ColorRenderTarget {
ColorRenderTarget {
alpha_batcher: AlphaBatcher::new(),
box_shadow_cache_prims: Vec::new(),
text_run_cache_prims: Vec::new(),
line_cache_prims: Vec::new(),
text_run_textures: BatchTextures::no_texture(),
@ -993,16 +987,9 @@ impl RenderTarget for ColorRenderTarget {
}
RenderTaskKind::CachePrimitive(prim_index) => {
let prim_metadata = ctx.prim_store.get_metadata(prim_index);
let prim_address = prim_metadata.gpu_location.as_int(gpu_cache);
match prim_metadata.prim_kind {
PrimitiveKind::BoxShadow => {
self.box_shadow_cache_prims.push(BoxShadowCacheInstance {
prim_address: gpu_cache.get_address(&prim_metadata.gpu_location),
task_index: render_tasks.get_task_address(task_id),
});
}
PrimitiveKind::TextShadow => {
let prim = &ctx.prim_store.cpu_text_shadows[prim_metadata.cpu_prim_index.0];
@ -1026,14 +1013,9 @@ impl RenderTarget for ColorRenderTarget {
// the parent text-shadow prim address as a user data field, allowing
// the shader to fetch the text-shadow parameters.
let text = &ctx.prim_store.cpu_text_runs[sub_metadata.cpu_prim_index.0];
let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio);
let font = FontInstance::new(text.font_key,
font_size_dp,
text.color,
text.shadow_render_mode,
text.glyph_options,
text.subpx_dir);
let mut font = text.font.clone();
font.size = font.size.scale_by(ctx.device_pixel_ratio);
let texture_id = ctx.resource_cache.get_glyphs(font,
&text.glyph_keys,
@ -1073,7 +1055,8 @@ impl RenderTarget for ColorRenderTarget {
}
}
}
RenderTaskKind::CacheMask(..) => {
RenderTaskKind::CacheMask(..) |
RenderTaskKind::BoxShadow(..) => {
panic!("Should not be added to color target!");
}
RenderTaskKind::Readback(device_rect) => {
@ -1085,6 +1068,7 @@ impl RenderTarget for ColorRenderTarget {
pub struct AlphaRenderTarget {
pub clip_batcher: ClipBatcher,
pub box_shadow_cache_prims: Vec<BoxShadowCacheInstance>,
allocator: TextureAllocator,
}
@ -1096,6 +1080,7 @@ impl RenderTarget for AlphaRenderTarget {
fn new(size: DeviceUintSize) -> AlphaRenderTarget {
AlphaRenderTarget {
clip_batcher: ClipBatcher::new(),
box_shadow_cache_prims: Vec::new(),
allocator: TextureAllocator::new(size),
}
}
@ -1121,6 +1106,21 @@ impl RenderTarget for AlphaRenderTarget {
RenderTaskKind::Readback(..) => {
panic!("Should not be added to alpha target!");
}
RenderTaskKind::BoxShadow(prim_index) => {
let prim_metadata = ctx.prim_store.get_metadata(prim_index);
match prim_metadata.prim_kind {
PrimitiveKind::BoxShadow => {
self.box_shadow_cache_prims.push(BoxShadowCacheInstance {
prim_address: gpu_cache.get_address(&prim_metadata.gpu_location),
task_index: render_tasks.get_task_address(task_id),
});
}
_ => {
panic!("BUG: invalid prim kind");
}
}
}
RenderTaskKind::CacheMask(ref task_info) => {
let task_address = render_tasks.get_task_address(task_id);
self.clip_batcher.add(task_address,

View File

@ -1,6 +1,6 @@
[package]
name = "webrender_api"
version = "0.49.0"
version = "0.50.0"
authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
license = "MPL-2.0"
repository = "https://github.com/servo/webrender"

View File

@ -2,14 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use app_units::Au;
use channel::{self, MsgSender, Payload, PayloadSenderHelperMethods, PayloadSender};
use std::cell::Cell;
use std::fmt;
use std::marker::PhantomData;
use {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint};
use {DeviceUintRect, DeviceUintSize, FontKey, GlyphDimensions, GlyphKey};
use {DeviceUintRect, DeviceUintSize, FontInstanceKey, FontKey, GlyphDimensions, GlyphKey};
use {ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutVector2D, LayoutSize, LayoutTransform};
use {FontInstance, NativeFontHandle, WorldPoint};
use {FontInstance, FontInstanceOptions, FontInstancePlatformOptions, NativeFontHandle, WorldPoint};
pub type TileSize = u16;
@ -26,6 +27,8 @@ pub enum ResourceUpdate {
DeleteImage(ImageKey),
AddFont(AddFont),
DeleteFont(FontKey),
AddFontInstance(AddFontInstance),
DeleteFontInstance(FontInstanceKey),
}
impl ResourceUpdates {
@ -71,6 +74,19 @@ impl ResourceUpdates {
self.updates.push(ResourceUpdate::DeleteFont(key));
}
pub fn add_font_instance(&mut self,
key: FontInstanceKey,
font_key: FontKey,
glyph_size: Au,
options: Option<FontInstanceOptions>,
platform_options: Option<FontInstancePlatformOptions>) {
self.updates.push(ResourceUpdate::AddFontInstance(AddFontInstance { key, font_key, glyph_size, options, platform_options }));
}
pub fn delete_font_instance(&mut self, key: FontInstanceKey) {
self.updates.push(ResourceUpdate::DeleteFontInstance(key));
}
pub fn merge(&mut self, mut other: ResourceUpdates) {
self.updates.append(&mut other.updates);
}
@ -102,6 +118,15 @@ pub enum AddFont {
Native(FontKey, NativeFontHandle),
}
#[derive(Clone, Deserialize, Serialize)]
pub struct AddFontInstance {
pub key: FontInstanceKey,
pub font_key: FontKey,
pub glyph_size: Au,
pub options: Option<FontInstanceOptions>,
pub platform_options: Option<FontInstancePlatformOptions>,
}
#[derive(Clone, Deserialize, Serialize)]
pub enum DocumentMsg {
SetDisplayList {
@ -118,6 +143,7 @@ pub enum DocumentMsg {
SetPinchZoom(ZoomFactor),
SetPan(DeviceIntPoint),
SetRootPipeline(PipelineId),
RemovePipeline(PipelineId),
SetWindowParameters {
window_size: DeviceUintSize,
inner_rect: DeviceUintRect,
@ -137,6 +163,7 @@ impl fmt::Debug for DocumentMsg {
DocumentMsg::SetPinchZoom(..) => "DocumentMsg::SetPinchZoom",
DocumentMsg::SetPan(..) => "DocumentMsg::SetPan",
DocumentMsg::SetRootPipeline(..) => "DocumentMsg::SetRootPipeline",
DocumentMsg::RemovePipeline(..) => "DocumentMsg::RemovePipeline",
DocumentMsg::SetWindowParameters{..} => "DocumentMsg::SetWindowParameters",
DocumentMsg::Scroll(..) => "DocumentMsg::Scroll",
DocumentMsg::ScrollNodeWithId(..) => "DocumentMsg::ScrollNodeWithId",
@ -155,8 +182,10 @@ pub enum DebugCommand {
EnableTextureCacheDebug(bool),
// Display intermediate render targets on screen.
EnableRenderTargetDebug(bool),
// Flush any pending debug commands.
Flush,
// Fetch current documents and display lists.
FetchDocuments,
// Fetch current passes and batches.
FetchPasses,
}
#[derive(Clone, Deserialize, Serialize)]
@ -329,6 +358,11 @@ impl RenderApi {
FontKey::new(self.namespace_id, new_id)
}
pub fn generate_font_instance_key(&self) -> FontInstanceKey {
let new_id = self.next_unique_id();
FontInstanceKey::new(self.namespace_id, new_id)
}
/// Gets the dimensions for the supplied glyph keys
///
/// Note: Internally, the internal texture cache doesn't store
@ -422,7 +456,7 @@ impl RenderApi {
self.api_sender.send(ApiMsg::UpdateDocument(document_id, msg)).unwrap()
}
/// Sets the root pipeline.
/// Sets the root pipeline.
///
/// # Examples
///
@ -439,6 +473,13 @@ impl RenderApi {
self.send(document_id, DocumentMsg::SetRootPipeline(pipeline_id));
}
/// Removes data associated with a pipeline from the internal data structures.
/// If the specified `pipeline_id` is for the root pipeline, the root pipeline
/// is reset back to `None`.
pub fn remove_pipeline(&self, document_id: DocumentId, pipeline_id: PipelineId) {
self.send(document_id, DocumentMsg::RemovePipeline(pipeline_id));
}
/// Supplies a new frame to WebRender.
///
/// Non-blocking, it notifies a worker process which processes the display list.

View File

@ -2,9 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use app_units::Au;
use euclid::{SideOffsets2D, TypedSideOffsets2D};
use {ColorF, FontKey, ImageKey, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
use {ColorF, FontInstanceKey, ImageKey, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
use {GlyphOptions, LayoutVector2D, PipelineId, PropertyBinding};
// NOTE: some of these structs have an "IMPLICIT" comment.
@ -139,8 +138,7 @@ pub enum LineStyle {
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct TextDisplayItem {
pub font_key: FontKey,
pub size: Au,
pub font_key: FontInstanceKey,
pub color: ColorF,
pub glyph_options: Option<GlyphOptions>,
} // IMPLICIT: glyphs: Vec<GlyphInstance>

View File

@ -2,14 +2,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use app_units::Au;
use bincode;
use serde::{Deserialize, Serialize, Serializer};
use serde::ser::{SerializeSeq, SerializeMap};
use time::precise_time_ns;
use {BorderDetails, BorderDisplayItem, BorderWidths, BoxShadowClipMode, BoxShadowDisplayItem};
use {ClipAndScrollInfo, ClipDisplayItem, ClipId, ColorF, ComplexClipRegion, DisplayItem};
use {ExtendMode, FastHashMap, FastHashSet, FilterOp, FontKey, GlyphIndex, GlyphInstance};
use {ExtendMode, FastHashMap, FastHashSet, FilterOp, FontInstanceKey, GlyphIndex, GlyphInstance};
use {GlyphOptions, Gradient, GradientDisplayItem, GradientStop, IframeDisplayItem};
use {ImageDisplayItem, ImageKey, ImageMask, ImageRendering, LayoutPoint, LayoutRect, LayoutSize};
use {LayoutTransform, LayoutVector2D, LineDisplayItem, LineOrientation, LineStyle, LocalClip};
@ -301,7 +300,7 @@ impl<'a> BuiltDisplayListIter<'a> {
}
impl<'a> Iterator for GlyphsIter<'a> {
type Item = (FontKey, ColorF, ItemRange<GlyphIndex>);
type Item = (FontInstanceKey, ColorF, ItemRange<GlyphIndex>);
fn next(&mut self) -> Option<Self::Item> {
if self.data.len() == 0 { return None; }
@ -457,7 +456,7 @@ pub struct DisplayListBuilder {
pub pipeline_id: PipelineId,
clip_stack: Vec<ClipAndScrollInfo>,
// FIXME: audit whether fast hashers (FNV?) are safe here
glyphs: FastHashMap<(FontKey, ColorF), FastHashSet<GlyphIndex>>,
glyphs: FastHashMap<(FontInstanceKey, ColorF), FastHashSet<GlyphIndex>>,
next_clip_id: u64,
builder_start_time: u64,
@ -605,38 +604,28 @@ impl DisplayListBuilder {
rect: LayoutRect,
local_clip: Option<LocalClip>,
glyphs: &[GlyphInstance],
font_key: FontKey,
font_key: FontInstanceKey,
color: ColorF,
size: Au,
glyph_options: Option<GlyphOptions>) {
// Sanity check - anything with glyphs bigger than this
// is probably going to consume too much memory to render
// efficiently anyway. This is specifically to work around
// the font_advance.html reftest, which creates a very large
// font as a crash test - the rendering is also ignored
// by the azure renderer.
if size < Au::from_px(4096) {
let item = SpecificDisplayItem::Text(TextDisplayItem {
color,
font_key,
size,
glyph_options,
});
let item = SpecificDisplayItem::Text(TextDisplayItem {
color,
font_key,
glyph_options,
});
for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) {
self.push_item(item, rect, local_clip);
self.push_iter(split_glyphs);
for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) {
self.push_item(item, rect, local_clip);
self.push_iter(split_glyphs);
// Remember that we've seen these glyphs
self.cache_glyphs(font_key, color, split_glyphs.iter().map(|glyph| glyph.index));
}
// Remember that we've seen these glyphs
self.cache_glyphs(font_key, color, split_glyphs.iter().map(|glyph| glyph.index));
}
}
fn cache_glyphs<I: Iterator<Item=GlyphIndex>>(&mut self,
font_key: FontKey,
color: ColorF,
glyphs: I) {
font_key: FontInstanceKey,
color: ColorF,
glyphs: I) {
let mut font_glyphs = self.glyphs.entry((font_key, color))
.or_insert(FastHashSet::default());

View File

@ -72,7 +72,7 @@ pub enum FontTemplate {
Native(NativeFontHandle),
}
#[repr(C)]
#[repr(u32)]
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
pub enum FontRenderMode {
Mono = 0,
@ -80,7 +80,7 @@ pub enum FontRenderMode {
Subpixel,
}
#[repr(C)]
#[repr(u32)]
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
pub enum SubpixelDirection {
None = 0,
@ -115,6 +115,14 @@ impl FontRenderMode {
_ => panic!("Should only be given the fractional part"),
}
}
// Combine two font render modes such that the lesser amount of AA limits the AA of the result.
pub fn limit_by(self, other: FontRenderMode) -> FontRenderMode {
match (self, other) {
(FontRenderMode::Subpixel, _) | (_, FontRenderMode::Mono) => other,
_ => self,
}
}
}
#[repr(u8)]
@ -137,8 +145,21 @@ impl Into<f64> for SubpixelOffset {
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
pub struct GlyphOptions {
pub render_mode: FontRenderMode,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
pub struct FontInstanceOptions {
pub render_mode: FontRenderMode,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
pub struct FontInstancePlatformOptions {
// These are currently only used on windows for dwrite fonts.
pub use_embedded_bitmap: bool,
pub force_gdi_rendering: bool,
@ -155,8 +176,8 @@ pub struct FontInstance {
pub size: Au,
pub color: ColorU,
pub render_mode: FontRenderMode,
pub glyph_options: Option<GlyphOptions>,
pub subpx_dir: SubpixelDirection,
pub platform_options: Option<FontInstancePlatformOptions>,
}
impl FontInstance {
@ -164,8 +185,8 @@ impl FontInstance {
size: Au,
mut color: ColorF,
render_mode: FontRenderMode,
glyph_options: Option<GlyphOptions>,
subpx_dir: SubpixelDirection) -> FontInstance {
subpx_dir: SubpixelDirection,
platform_options: Option<FontInstancePlatformOptions>) -> FontInstance {
// In alpha/mono mode, the color of the font is irrelevant.
// Forcing it to black in those cases saves rasterizing glyphs
// of different colors when not needed.
@ -178,8 +199,8 @@ impl FontInstance {
size,
color: color.into(),
render_mode,
glyph_options,
subpx_dir,
platform_options,
}
}
@ -192,6 +213,16 @@ impl FontInstance {
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Ord, PartialOrd)]
pub struct FontInstanceKey(pub IdNamespace, pub u32);
impl FontInstanceKey {
pub fn new(namespace: IdNamespace, key: u32) -> FontInstanceKey {
FontInstanceKey(namespace, key)
}
}
#[derive(Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
pub struct GlyphKey {
pub index: u32,

View File

@ -6,7 +6,7 @@ use std::sync::Arc;
use {DeviceUintRect, DevicePoint};
use {IdNamespace};
use {TileOffset, TileSize};
use font::{FontKey, FontTemplate};
use font::{FontKey, FontInstanceKey, FontTemplate};
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
@ -59,14 +59,14 @@ pub enum ImageFormat {
}
impl ImageFormat {
pub fn bytes_per_pixel(self) -> Option<u32> {
pub fn bytes_per_pixel(self) -> u32 {
match self {
ImageFormat::A8 => Some(1),
ImageFormat::RGB8 => Some(3),
ImageFormat::BGRA8 => Some(4),
ImageFormat::RGBAF32 => Some(16),
ImageFormat::RG8 => Some(2),
ImageFormat::Invalid => None,
ImageFormat::A8 => 1,
ImageFormat::RGB8 => 3,
ImageFormat::BGRA8 => 4,
ImageFormat::RGBAF32 => 16,
ImageFormat::RG8 => 2,
ImageFormat::Invalid => 0,
}
}
}
@ -94,7 +94,7 @@ impl ImageDescriptor {
}
pub fn compute_stride(&self) -> u32 {
self.stride.unwrap_or(self.width * self.format.bytes_per_pixel().unwrap())
self.stride.unwrap_or(self.width * self.format.bytes_per_pixel())
}
}
@ -165,6 +165,8 @@ pub trait BlobImageRenderer: Send {
fn resolve(&mut self, key: BlobImageRequest) -> BlobImageResult;
fn delete_font(&mut self, key: FontKey);
fn delete_font_instance(&mut self, key: FontInstanceKey);
}
pub type BlobImageData = Vec<u8>;

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