mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 15:23:51 +00:00
merge mozilla-inbound to mozilla-central. r=merge a=merge
MozReview-Commit-ID: ES9rKhiQo10
This commit is contained in:
commit
43bc951ac7
@ -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()) {
|
||||
|
@ -327,6 +327,7 @@ toolbarpaletteitem > toolbaritem[sdkstylewidget="true"][cui-areatype="toolbar"]
|
||||
}
|
||||
|
||||
.webextension-browser-action > .toolbarbutton-badge-stack > .toolbarbutton-icon {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
},
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
@ -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
821
browser/extensions/pdfjs/content/build/pdf.worker.js
vendored
821
browser/extensions/pdfjs/content/build/pdf.worker.js
vendored
File diff suppressed because it is too large
Load Diff
@ -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) {
|
||||
|
@ -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()
|
||||
|
||||
|
@ -63,6 +63,7 @@ button {
|
||||
|
||||
.target-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-inline-end: 5px;
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
|
@ -8,7 +8,7 @@
|
||||
:root {
|
||||
font: message-box;
|
||||
|
||||
--tab-line-selected-color: highlight;
|
||||
--tab-line-selected-color: var(--blue-50);
|
||||
}
|
||||
|
||||
:root.theme-light {
|
||||
|
@ -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;
|
||||
|
538
devtools/server/actors/accessibility.js
Normal file
538
devtools/server/actors/accessibility.js
Normal 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;
|
@ -11,6 +11,7 @@ DIRS += [
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'accessibility.js',
|
||||
'actor-registry.js',
|
||||
'addon.js',
|
||||
'addons.js',
|
||||
|
@ -585,6 +585,11 @@ var DebuggerServer = {
|
||||
constructor: "WebExtensionInspectedWindowActor",
|
||||
type: { tab: true }
|
||||
});
|
||||
this.registerModule("devtools/server/actors/accessibility", {
|
||||
prefix: "accessibility",
|
||||
constructor: "AccessibilityActor",
|
||||
type: { tab: true }
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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]
|
||||
|
74
devtools/server/tests/browser/browser_accessibility_node.js
Normal file
74
devtools/server/tests/browser/browser_accessibility_node.js
Normal 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();
|
||||
});
|
@ -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();
|
||||
});
|
@ -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();
|
||||
});
|
@ -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();
|
||||
});
|
12
devtools/server/tests/browser/doc_accessibility.html
Normal file
12
devtools/server/tests/browser/doc_accessibility.html
Normal 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>
|
@ -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");
|
||||
}));
|
||||
}
|
||||
|
136
devtools/shared/fronts/accessibility.js
Normal file
136
devtools/shared/fronts/accessibility.js
Normal 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;
|
@ -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',
|
||||
|
141
devtools/shared/specs/accessibility.js
Normal file
141
devtools/shared/specs/accessibility.js
Normal 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;
|
@ -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',
|
||||
|
@ -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); \
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -32,7 +32,11 @@ TestInterfaceJSMaplike.prototype = {
|
||||
|
||||
clearInternal: function() {
|
||||
return this.__DOM_IMPL__.__clear();
|
||||
}
|
||||
},
|
||||
|
||||
__onget: function(key, value) {
|
||||
/* no-op */
|
||||
},
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestInterfaceJSMaplike])
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -327,6 +327,10 @@ class RTCStatsReport {
|
||||
}
|
||||
|
||||
get mozPcid() { return this._pcid; }
|
||||
|
||||
__onget(key, value) {
|
||||
/* Do whatever here */
|
||||
}
|
||||
}
|
||||
setupPrototype(RTCStatsReport, {
|
||||
classID: PC_STATS_CID,
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
9
dom/webauthn/libudev-sys/Cargo.toml
Normal file
9
dom/webauthn/libudev-sys/Cargo.toml
Normal 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"
|
180
dom/webauthn/libudev-sys/src/lib.rs
Normal file
180
dom/webauthn/libudev-sys/src/lib.rs
Normal 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);
|
@ -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);
|
||||
|
25
gfx/2d/2D.h
25
gfx/2d/2D.h
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
69
gfx/webrender/res/ellipse.glsl
Normal file
69
gfx/webrender/res/ellipse.glsl
Normal 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
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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 },
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user