Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65d7d93760 | ||
|
|
dfd0ea4e8a | ||
|
|
12a49709cb | ||
|
|
2a70c8a6f0 | ||
|
|
1f639d78ca | ||
|
|
65e8245153 | ||
|
|
bce5dbad0b | ||
|
|
0b25b5f8db | ||
|
|
7345874098 | ||
|
|
edb82fd1c9 | ||
|
|
35ebdcac66 | ||
|
|
a095661e86 | ||
|
|
205a0d66bb | ||
|
|
54945a0808 | ||
|
|
84c1eedac6 | ||
|
|
ddf5d6256e | ||
|
|
0eae777fd0 |
No files matched your search
@@ -88,11 +88,6 @@ check_and_add_gcc_warning('-Wno-error=deprecated-declarations')
|
||||
# false positives depending on optimization
|
||||
check_and_add_gcc_warning('-Wno-error=array-bounds')
|
||||
|
||||
# GCC 9+ value-range analysis trips -Wformat-overflow on null %s args in debug
|
||||
# spew (e.g. js/src/jit/x64/BaseAssembler-x64.h:595); harmless for our build,
|
||||
# and js/src forces -Werror=format so this must be downgraded here to reach it.
|
||||
check_and_add_gcc_warning('-Wno-error=format-overflow')
|
||||
|
||||
# can't get rid of those PGO warnings
|
||||
check_and_add_gcc_warning('-Wno-error=coverage-mismatch', when='MOZ_PGO')
|
||||
|
||||
|
||||
@@ -143,7 +143,8 @@ TooltipsOverlay.prototype = {
|
||||
// Font preview tooltip
|
||||
if (type === VIEW_NODE_VALUE_TYPE && prop.property === "font-family") {
|
||||
let value = prop.value.toLowerCase();
|
||||
if (value !== "inherit" && value !== "unset" && value !== "initial" && value !== "revert") {
|
||||
if (value !== "inherit" && value !== "unset" && value !== "initial" &&
|
||||
value !== "revert" && value !== "revert-layer") {
|
||||
tooltipType = TOOLTIP_FONTFAMILY_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ const SPECIALVALUES = new Set([
|
||||
"initial",
|
||||
"inherit",
|
||||
"unset",
|
||||
"revert"
|
||||
"revert",
|
||||
"revert-layer"
|
||||
]);
|
||||
|
||||
const {getCSSLexer} = require("devtools/shared/css/lexer");
|
||||
|
||||
@@ -99,7 +99,14 @@ const filterList = [
|
||||
];
|
||||
|
||||
// Valid values that shouldn't be parsed for filters.
|
||||
const SPECIAL_VALUES = new Set(["none", "unset", "initial", "inherit", "revert"]);
|
||||
const SPECIAL_VALUES = new Set([
|
||||
"none",
|
||||
"unset",
|
||||
"initial",
|
||||
"inherit",
|
||||
"revert",
|
||||
"revert-layer"
|
||||
]);
|
||||
|
||||
/**
|
||||
* A CSS Filter editor widget used to add/remove/modify
|
||||
|
||||
@@ -20,7 +20,8 @@ const SPECIALVALUES = new Set([
|
||||
"inherit",
|
||||
"transparent",
|
||||
"unset",
|
||||
"revert"
|
||||
"revert",
|
||||
"revert-layer"
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,7 @@ class MachCommands(MachCommandBase):
|
||||
if not os.path.exists(generated_dir):
|
||||
generated_dir = mozpath.join(self.topsrcdir, 'platform',
|
||||
'devtools/shared/css/generated')
|
||||
else:
|
||||
if not os.path.exists(generated_dir):
|
||||
raise Exception('Directory expected at %s does not exist.' % generated_dir)
|
||||
self.generated_dir = generated_dir
|
||||
|
||||
@@ -75,6 +75,9 @@ class MachCommands(MachCommandBase):
|
||||
contents = subprocess.check_output([xpcshell_path, '-g', gre_path,
|
||||
'-a', browser_path, script_path],
|
||||
env = sub_env)
|
||||
if not isinstance(contents, str):
|
||||
contents = contents.decode('utf-8')
|
||||
|
||||
# Extract just the output between the delimiters as the xpcshell output can
|
||||
# have extra output that we don't want.
|
||||
contents = contents.split('DEVTOOLS_CSS_DB_DELIMITER')[1]
|
||||
@@ -88,13 +91,13 @@ class MachCommands(MachCommandBase):
|
||||
destination_path = mozpath.join(self.generated_dir,
|
||||
'properties-db.js')
|
||||
|
||||
with open(js_template_path, 'rb') as handle:
|
||||
with open(js_template_path, 'r') as handle:
|
||||
js_template = handle.read()
|
||||
|
||||
preamble = '/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n'
|
||||
contents = string.Template(js_template).substitute(substitutions)
|
||||
|
||||
with open(destination_path, 'wb') as destination:
|
||||
with open(destination_path, 'w') as destination:
|
||||
destination.write(preamble + contents)
|
||||
|
||||
print(('The database was successfully generated at ' + destination_path))
|
||||
File diff suppressed because it is too large.
Load diff
@@ -52,7 +52,7 @@ static RedirEntry kRedirMap[] = {
|
||||
{ "config", "chrome://global/content/config.xul", 0 },
|
||||
{ "console", "chrome://global/content/console.xul", 0 },
|
||||
{
|
||||
"credits", "https://net.riverstonerelay.jihad-browser/credits",
|
||||
"credits", "http://www.palemoon.org/Contributors.shtml",
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||
nsIAboutModule::MAKE_LINKABLE
|
||||
},
|
||||
|
||||
@@ -199,7 +199,6 @@
|
||||
#include "nsICommandManager.h"
|
||||
#include "nsIDOMNode.h"
|
||||
#include "nsIDocShellTreeOwner.h"
|
||||
#include "nsIBadCertListener2.h" // JIHAD: route cert errors to the embedder chrome
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsIIDNService.h"
|
||||
#include "nsIInputStreamChannel.h"
|
||||
@@ -1075,33 +1074,6 @@ nsDocShell::GetInterface(const nsIID& aIID, void** aSink)
|
||||
} else if (aIID.Equals(NS_GET_IID(nsITabChild))) {
|
||||
*aSink = GetTabChild().take();
|
||||
return *aSink ? NS_OK : NS_ERROR_FAILURE;
|
||||
} else if (aIID.Equals(NS_GET_IID(nsIBadCertListener2))) {
|
||||
// JIHAD: hand the embedder's chrome the certificate error.
|
||||
//
|
||||
// NSS asks the channel's notification callbacks for nsIBadCertListener2
|
||||
// (SSLServerCertVerification.cpp, CertErrorRunnable::CheckCertOverrides) and passes
|
||||
// it the nsISSLStatus carrying the SERVER CERTIFICATE. That call is the one and only
|
||||
// place an embedder is handed the cert while it still exists — the same shape the
|
||||
// Atlas browser gets for free from WebKit's load-failed-with-tls-errors signal, which
|
||||
// delivers the GTlsCertificate as a signal argument.
|
||||
//
|
||||
// Without this hop nothing answers: the docShell's fallthrough is
|
||||
// nsDocLoader::GetInterface, which Queries the docloader ITSELF, so the request never
|
||||
// reaches nsIWebBrowserChrome. The embedder is then left trying to recover the cert
|
||||
// afterwards, and there is no way to: the failed channel's securityInfo has a null
|
||||
// SSLStatus, and UXP has no nsIRecentBadCerts service. Measured 2026-08-04 — the
|
||||
// SSL-confirm dialog worked, the user accepted, and the accept could do nothing
|
||||
// because RememberValidityOverride had no nsIX509Cert to take.
|
||||
//
|
||||
// Routed through the tree owner, exactly as nsIWebBrowserChromeFocus and nsIPrompt
|
||||
// already are (nsDocShellTreeOwner::GetInterface).
|
||||
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
|
||||
GetTreeOwner(getter_AddRefs(treeOwner));
|
||||
nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(treeOwner));
|
||||
if (req) {
|
||||
return req->GetInterface(aIID, aSink);
|
||||
}
|
||||
return NS_NOINTERFACE;
|
||||
} else if (aIID.Equals(NS_GET_IID(nsIContentFrameMessageManager))) {
|
||||
nsCOMPtr<nsITabChild> tabChild =
|
||||
do_GetInterface(static_cast<nsIDocShell*>(this));
|
||||
|
||||
@@ -274,7 +274,7 @@ public:
|
||||
}
|
||||
|
||||
template<typename T,
|
||||
typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
||||
typename = typename std::enable_if_t<std::is_enum_v<T>>>
|
||||
EnumTable(const char* aTag, T aValue)
|
||||
: tag(aTag)
|
||||
, value(static_cast<int16_t>(aValue))
|
||||
|
||||
@@ -787,12 +787,6 @@ nsresult
|
||||
nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_NPAPI
|
||||
// JIHAD DIAGNOSTIC (R7): entry + the four early-out conditions. If this line never appears the
|
||||
// plugin was never asked for at all (no nsPluginFrame -> no HasNewFrame), which is a different
|
||||
// bug from being asked for and refused.
|
||||
fprintf(stderr, "[jihad-npapi] InstantiatePluginInstance(aIsLoading=%d) owner=%d type=%d "
|
||||
"mIsLoading=%d instantiating=%d\n", (int)aIsLoading, (int)!!mInstanceOwner,
|
||||
(int)mType, (int)mIsLoading, (int)mInstantiating);
|
||||
if (mInstanceOwner || mType != eType_Plugin || (mIsLoading != aIsLoading) ||
|
||||
mInstantiating) {
|
||||
// If we hit this assertion it's probably because LoadObject re-entered :(
|
||||
@@ -828,12 +822,10 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
|
||||
NS_ENSURE_TRUE(mInstantiating, NS_OK);
|
||||
|
||||
if (!thisContent->GetPrimaryFrame()) {
|
||||
// JIHAD DIAGNOSTIC (R7): a silent NS_OK return — the single most likely place for a plugin
|
||||
// to "do nothing" with no error anywhere.
|
||||
fprintf(stderr, "[jihad-npapi] NO PRIMARY FRAME -> not instantiating\n");
|
||||
LOG(("OBJLC [%p]: Not instantiating plugin with no frame", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult rv = NS_ERROR_FAILURE;
|
||||
RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
|
||||
|
||||
@@ -854,8 +846,6 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
|
||||
rv = pluginHost->InstantiatePluginInstance(mContentType,
|
||||
mURI.get(), this,
|
||||
getter_AddRefs(newOwner));
|
||||
fprintf(stderr, "[jihad-npapi] pluginHost->InstantiatePluginInstance rv=0x%x owner=%d\n",
|
||||
(unsigned)rv, (int)!!newOwner);
|
||||
|
||||
// XXX(johns): We don't suspend native inside stopping plugins...
|
||||
if (appShell) {
|
||||
@@ -2388,11 +2378,6 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
|
||||
FallbackType clickToPlayReason;
|
||||
if (!mActivated && (mType == eType_Null || mType == eType_Plugin) &&
|
||||
!ShouldPlay(clickToPlayReason, false)) {
|
||||
// JIHAD DIAGNOSTIC (R7): this is where a plugin that resolved fine is silently demoted to
|
||||
// eType_Null. Every path out of ShouldPlay that returns false lands here, so one line here
|
||||
// names the reason instead of instrumenting each exit.
|
||||
fprintf(stderr, "[jihad-npapi] demoted to fallback: mType=%d fallbackReason=%d\n",
|
||||
(int)mType, (int)clickToPlayReason);
|
||||
LOG(("OBJLC [%p]: Marking plugin as click-to-play", this));
|
||||
mType = eType_Null;
|
||||
fallbackType = clickToPlayReason;
|
||||
@@ -2902,16 +2887,9 @@ nsObjectLoadingContent::GetTypeOfContent(const nsCString& aMIMEType)
|
||||
|
||||
#ifdef MOZ_ENABLE_NPAPI
|
||||
RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
|
||||
bool jihadHave = pluginHost &&
|
||||
pluginHost->HavePluginForType(aMIMEType, nsPluginHost::eExcludeNone);
|
||||
// JIHAD DIAGNOSTIC (R7 windowless port): says, for one MIME type, whether the element
|
||||
// advertised plugin capability and whether the host has a plugin — the two facts that decide
|
||||
// if the NPAPI subsystem is entered at all. Added because a plugin that never instantiates
|
||||
// produces NO plugin logging whatsoever, which is indistinguishable from one that was never
|
||||
// asked for.
|
||||
fprintf(stderr, "[jihad-npapi] GetTypeOfContent mime=%s caps=0x%x supportsPlugins=%d have=%d\n",
|
||||
nsCString(aMIMEType).get(), caps, (int)!!(caps & eSupportPlugins), (int)jihadHave);
|
||||
if ((caps & eSupportPlugins) && jihadHave) {
|
||||
if ((caps & eSupportPlugins) &&
|
||||
pluginHost &&
|
||||
pluginHost->HavePluginForType(aMIMEType, nsPluginHost::eExcludeNone)) {
|
||||
// ShouldPlay will handle checking for disabled plugins
|
||||
return eType_Plugin;
|
||||
}
|
||||
@@ -3508,15 +3486,9 @@ nsObjectLoadingContent::ShouldPlay(FallbackType &aReason, bool aIgnoreCurrentTyp
|
||||
aReason = eFallbackClickToPlay;
|
||||
|
||||
uint32_t enabledState = nsIPluginTag::STATE_DISABLED;
|
||||
nsresult jihadStateRv = pluginHost->GetStateForType(mContentType, nsPluginHost::eExcludeNone,
|
||||
pluginHost->GetStateForType(mContentType, nsPluginHost::eExcludeNone,
|
||||
&enabledState);
|
||||
// JIHAD DIAGNOSTIC (R7): note that enabledState is pre-set to STATE_DISABLED and the rv of
|
||||
// GetStateForType is NOT checked upstream, so a failing lookup silently blocks the plugin
|
||||
// with no other trace. Log the rv as well as the value.
|
||||
fprintf(stderr, "[jihad-npapi] ShouldPlay type=%s enabledState=%u rv=0x%x\n",
|
||||
mContentType.get(), enabledState, (unsigned)jihadStateRv);
|
||||
if (nsIPluginTag::STATE_DISABLED == enabledState) {
|
||||
fprintf(stderr, "[jihad-npapi] ShouldPlay -> FALSE (eFallbackDisabled)\n");
|
||||
aReason = eFallbackDisabled;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -577,166 +577,6 @@ typedef struct _NPEvent
|
||||
} NPEvent;
|
||||
#elif defined(XP_UNIX) && defined(MOZ_X11)
|
||||
typedef XEvent NPEvent;
|
||||
#elif defined(XP_UNIX) && defined(MOZ_WIDGET_HEADLESS)
|
||||
/*
|
||||
* JIHAD (webOS, cavekit-addons-extensions.md R7) — THE PALM EVENT PROTOCOL.
|
||||
*
|
||||
* webOS plugins do not speak X11. Palm's own npapi.h defines NPEvent, under XP_WEBOS, as the
|
||||
* union below; the device's Adobe Flash was compiled against exactly that header and its
|
||||
* NPP_HandleEvent reads these fields. Reproduced here from the webOS WebKit fork's
|
||||
* WebCore/plugins/npapi.h (the XP_WEBOS block, NpPalmEventsEnum through the NPEvent union),
|
||||
* so that the shape crossing our NPAPI boundary is the shape the plugin was built to read.
|
||||
*
|
||||
* This replaces `typedef void* NPEvent` for the headless toolkit, and that arm was not merely
|
||||
* a placeholder — it was load-bearing in the wrong direction. NPRemoteEvent is a raw memcpy of
|
||||
* sizeof(NPEvent) (dom/plugins/ipc/NPEventUnix.h), so with a void* NPEvent the parent shipped
|
||||
* four bytes of its own address space to the child and the child handed the plugin a pointer
|
||||
* into a foreign process. No event could ever have carried a payload. Making NPEvent a real
|
||||
* POD is what lets the whole existing NPP_HandleEvent path work unmodified, parent to child.
|
||||
*
|
||||
* Gated on MOZ_WIDGET_HEADLESS because that is precisely the configuration this port builds
|
||||
* (--enable-default-toolkit=cairo-headless, no X anywhere in the bundle) and the only one that
|
||||
* reaches this arm. A non-webOS headless embedding would get these structs too; that is
|
||||
* harmless, since the alternative it loses is an NPEvent that could not carry an event.
|
||||
*
|
||||
* POINTER-BEARING MEMBERS DO NOT SURVIVE IPC. dstBuffer/graphicsContext (draw),
|
||||
* touches (touch) and reserved (system) are process-local. Pen, key and system events use
|
||||
* none of them; the draw event is built inside the child (PluginInstanceChild.cpp) and never
|
||||
* crosses the boundary, which is why it stays valid.
|
||||
*
|
||||
* One deliberate omission from Palm's union: NpPalmAccelerometerEvent. It is 16 bytes against
|
||||
* NpPalmDrawEvent's 44, so leaving it out changes sizeof(NPEvent) by nothing — and the union
|
||||
* is only ever read through the plugin's OWN copy of this declaration, so an unused member
|
||||
* here is unobservable. It is left out rather than copied because it carries a struct-scoped
|
||||
* `enum Orientation` whose tag would leak to file scope, for no gain.
|
||||
*/
|
||||
typedef enum NpPalmEventsEnum
|
||||
{
|
||||
npPalmPenDownEvent = 1 << 0,
|
||||
npPalmPenUpEvent = 1 << 1,
|
||||
npPalmPenMoveEvent = 1 << 2,
|
||||
npPalmKeyDownEvent = 1 << 3,
|
||||
npPalmKeyUpEvent = 1 << 4,
|
||||
npPalmKeyRepeatEvent = 1 << 5,
|
||||
npPalmKeyPressEvent = 1 << 6,
|
||||
npPalmDrawEvent = 1 << 7,
|
||||
npPalmSystemEvent = 1 << 8,
|
||||
npPalmGestureEvent = 1 << 9,
|
||||
npPalmAccelerometerEvent = 1 << 10,
|
||||
npPalmTouchStartEvent = 1 << 11,
|
||||
npPalmTouchMoveEvent = 1 << 12,
|
||||
npPalmTouchEndEvent = 1 << 13,
|
||||
npPalmTouchCancelledEvent = 1 << 14,
|
||||
npPalmPenDoubleClickEvent = 1 << 15,
|
||||
npPalmPenClickEvent = 1 << 16
|
||||
} NpPalmEventsEnum;
|
||||
|
||||
typedef enum NpPalmSystemEventsEnum
|
||||
{
|
||||
npPalmPauseEvent = 1,
|
||||
npPalmResumeEvent = 2,
|
||||
npPalmGainFocusEvent = 3,
|
||||
npPalmLoseFocusEvent = 4,
|
||||
npPalmDimmedEvent = 5,
|
||||
npPalmSetFullScreenEvent = 6,
|
||||
npPalmUnsetFullScreenEvent = 7,
|
||||
npPalmPageLoadingEvent = 8,
|
||||
npPalmPageLoadCompleteEvent = 9,
|
||||
npPalmViewPortChangedEvent = 10,
|
||||
npPalmSpotlightStartEvent = 11,
|
||||
npPalmSpotlightEndEvent = 12
|
||||
} NpPalmSystemEventsEnum;
|
||||
|
||||
typedef enum NpPalmKeyModifiersEnum
|
||||
{
|
||||
npPalmCtrlKeyModifier = 1 << 0,
|
||||
npPalmAltKeyModifier = 1 << 1,
|
||||
npPalmShiftKeyModifier = 1 << 2,
|
||||
npPalmMetaKeyModifier = 1 << 3
|
||||
} NpPalmKeyModifiersEnum;
|
||||
|
||||
typedef struct NpPalmKeyEvent
|
||||
{
|
||||
int32_t chr; /* 32-bit so any Unicode character fits */
|
||||
int32_t modifiers;
|
||||
int32_t rawkeyCode;
|
||||
int32_t rawModifier;
|
||||
} NpPalmKeyEvent;
|
||||
|
||||
typedef struct NpPalmPenEvent
|
||||
{
|
||||
int32_t xCoord, yCoord; /* plugin-local, in the plugin's own unscaled pixels */
|
||||
int32_t modifiers;
|
||||
} NpPalmPenEvent;
|
||||
|
||||
typedef struct NpPalmTouchPoint
|
||||
{
|
||||
int32_t xCoord, yCoord;
|
||||
} NpPalmTouchPoint;
|
||||
|
||||
typedef struct NpPalmTouchEvent
|
||||
{
|
||||
int32_t touchCount;
|
||||
NpPalmTouchPoint* touches; /* process-local — never send over IPC */
|
||||
int32_t modifiers;
|
||||
} NpPalmTouchEvent;
|
||||
|
||||
typedef struct NpPalmDrawEvent
|
||||
{
|
||||
void* dstBuffer; /* process-local; API 1.0 raster destination */
|
||||
uint32_t dstRowBytes;
|
||||
int32_t srcLeft;
|
||||
int32_t srcRight;
|
||||
int32_t srcTop;
|
||||
int32_t srcBottom;
|
||||
void* graphicsContext; /* process-local; the Piranha PGContext, API 2.0 */
|
||||
int32_t dstLeft;
|
||||
int32_t dstRight;
|
||||
int32_t dstTop;
|
||||
int32_t dstBottom;
|
||||
} NpPalmDrawEvent;
|
||||
|
||||
typedef struct NpPalmSystemEvent
|
||||
{
|
||||
NpPalmSystemEventsEnum type;
|
||||
int32_t value;
|
||||
int32_t viewLeft;
|
||||
int32_t viewRight;
|
||||
int32_t viewTop;
|
||||
int32_t viewBottom;
|
||||
void* reserved; /* process-local */
|
||||
} NpPalmSystemEvent;
|
||||
|
||||
typedef enum NpPalmGestureEventEnum
|
||||
{
|
||||
npPalmGestureStartEvent = 1,
|
||||
npPalmGestureChangeEvent = 2,
|
||||
npPalmGestureEndEvent = 3,
|
||||
npPalmGestureSingleTapEvent = 4
|
||||
} NpPalmGestureEventEnum;
|
||||
|
||||
typedef struct NpPalmGestureEvent
|
||||
{
|
||||
NpPalmGestureEventEnum type;
|
||||
int32_t x, y;
|
||||
float scale, rotate;
|
||||
int32_t center_x, center_y;
|
||||
int32_t modifiers;
|
||||
} NpPalmGestureEvent;
|
||||
|
||||
typedef struct NPEvent
|
||||
{
|
||||
NpPalmEventsEnum eventType;
|
||||
|
||||
union {
|
||||
NpPalmKeyEvent keyEvent;
|
||||
NpPalmPenEvent penEvent;
|
||||
NpPalmDrawEvent drawEvent;
|
||||
NpPalmSystemEvent systemEvent;
|
||||
NpPalmGestureEvent gestureEvent;
|
||||
NpPalmTouchEvent touchEvent;
|
||||
} data;
|
||||
} NPEvent;
|
||||
#else
|
||||
typedef void* NPEvent;
|
||||
#endif
|
||||
|
||||
@@ -341,10 +341,6 @@ nsNPAPIPlugin::CreatePlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin** aResult)
|
||||
RefPtr<nsNPAPIPlugin> plugin = new nsNPAPIPlugin();
|
||||
|
||||
PluginLibrary* pluginLib = GetNewPluginLibrary(aPluginTag);
|
||||
// JIHAD DIAGNOSTIC (R7): GetNewPluginLibrary is where the OOP decision is made —
|
||||
// RunPluginOOP() ? PluginModuleParent::LoadModule (launches plugin-container) : a
|
||||
// PluginPRLibrary loaded in-process. A null here is the plugin-container launch failing.
|
||||
fprintf(stderr, "[jihad-npapi] GetNewPluginLibrary lib=%d\n", (int)!!pluginLib);
|
||||
if (!pluginLib) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@@ -390,7 +386,6 @@ nsNPAPIPlugin::CreatePlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin** aResult)
|
||||
#else
|
||||
NPError pluginCallError;
|
||||
nsresult rv = pluginLib->NP_Initialize(&sBrowserFuncs, &plugin->mPluginFuncs, &pluginCallError);
|
||||
fprintf(stderr, "[jihad-npapi] NP_Initialize rv=0x%x npErr=%d\n", (unsigned)rv, (int)pluginCallError);
|
||||
if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@@ -1944,16 +1939,7 @@ _getvalue(NPP npp, NPNVariable variable, void *result)
|
||||
}
|
||||
|
||||
case NPNVSupportsWindowless: {
|
||||
// MOZ_WIDGET_HEADLESS added for Jihad Browser. This list enumerates the toolkits that can
|
||||
// host a windowless plugin, and it was written before a headless toolkit existed — so a
|
||||
// cairo-headless build answered "windowless unsupported" to every plugin, which is the
|
||||
// opposite of the truth. Headless is the toolkit that MOST needs windowless: there is no X
|
||||
// server, no Drawable and no XEmbed, so windowed hosting is impossible and drawing into a
|
||||
// memory bitmap we own is the only thing that can work. gfxPlatformHeadless already returns
|
||||
// true from SupportsPluginDirectBitmapDrawing(), i.e. the drawing model this unlocks
|
||||
// (NPDrawingModelAsyncBitmapSurface) is implemented for this platform; only this
|
||||
// advertisement was missing, and with it false no plugin would ever choose windowless.
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_HEADLESS) || \
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX) || \
|
||||
(defined(MOZ_X11) && defined(MOZ_WIDGET_GTK))
|
||||
*(NPBool*)result = true;
|
||||
#else
|
||||
|
||||
@@ -843,9 +843,6 @@ nsPluginHost::InstantiatePluginInstance(const nsACString& aMimeType, nsIURI* aUR
|
||||
}
|
||||
|
||||
rv = SetUpPluginInstance(aMimeType, aURL, instanceOwner);
|
||||
// JIHAD DIAGNOSTIC (R7): the real rv is flattened to NS_ERROR_FAILURE below, which is what
|
||||
// made this failure opaque from the caller's side.
|
||||
fprintf(stderr, "[jihad-npapi] SetUpPluginInstance rv=0x%x\n", (unsigned)rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
instanceOwner->Destroy();
|
||||
return NS_ERROR_FAILURE;
|
||||
@@ -964,12 +961,7 @@ nsPluginHost::TrySetUpPluginInstance(const nsACString &aMimeType,
|
||||
#endif
|
||||
|
||||
RefPtr<nsNPAPIPlugin> plugin;
|
||||
nsresult jihadGetRv = GetPlugin(aMimeType, getter_AddRefs(plugin));
|
||||
// JIHAD DIAGNOSTIC (R7): GetPlugin -> CreateNPAPIPlugin -> nsNPAPIPlugin::CreatePlugin, which
|
||||
// for an OOP build is PluginModuleParent::LoadModule (the plugin-container launch). Its rv is
|
||||
// DISCARDED here; only the null check survives, so a launch failure is invisible.
|
||||
fprintf(stderr, "[jihad-npapi] GetPlugin rv=0x%x plugin=%d\n",
|
||||
(unsigned)jihadGetRv, (int)!!plugin);
|
||||
GetPlugin(aMimeType, getter_AddRefs(plugin));
|
||||
if (!plugin) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@@ -995,7 +987,6 @@ nsPluginHost::TrySetUpPluginInstance(const nsACString &aMimeType,
|
||||
// except in some cases not Java, see bug 140931
|
||||
// our COM pointer will free the peer
|
||||
nsresult rv = instance->Initialize(plugin.get(), aOwner, aMimeType);
|
||||
fprintf(stderr, "[jihad-npapi] instance->Initialize rv=0x%x\n", (unsigned)rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
mInstances.RemoveElement(instance.get());
|
||||
aOwner->SetInstance(nullptr);
|
||||
@@ -4210,62 +4201,3 @@ PluginDestructionGuard::DelayDestroy(nsNPAPIPluginInstance *aInstance)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ── JIHAD (webOS, cavekit-addons-extensions.md R7) ────────────────────────────────────────
|
||||
//
|
||||
// A C entry point for the render daemon, following the jihad_offscreen_* precedent in
|
||||
// widget/PuppetWidget.cpp: the daemon links against libxul but cannot see internal classes,
|
||||
// and nsNPAPIPluginInstance::HandleEvent is neither exported nor virtual, so there is no
|
||||
// other way to reach a plugin from outside.
|
||||
//
|
||||
// This exists for the frozen YAP command pair asyncCmdPluginSpotlightStart/End. The card
|
||||
// scrims and smart-zooms to a plugin, then tells the server the rect it settled on; the
|
||||
// server's job is to pass that on to the plugin as Palm's npPalmSpotlightStartEvent(11) /
|
||||
// npPalmSpotlightEndEvent(12). It is not cosmetic: Flash routes 11 to the same handler as
|
||||
// npPalmSetFullScreenEvent(6) and 12 to the same as npPalmUnsetFullScreenEvent(7)
|
||||
// (libflashplayer.so jump table at 0x44e0c — entries 10 and 11 target 0x44fa0/0x44e80,
|
||||
// identical to entries 5 and 6), so this is how a plugin is told it went fullscreen.
|
||||
//
|
||||
// Broadcast to every running instance rather than targeting one: the spotlight is a
|
||||
// page-level, one-plugin-at-a-time state, this daemon serves a single card, and the
|
||||
// alternative — matching the rect against each instance's frame — would invent a
|
||||
// disambiguation rule that the contract does not define. Returns how many instances were
|
||||
// told, so a caller can tell "no plugin" from "delivered".
|
||||
extern "C" MOZ_EXPORT int
|
||||
jihad_plugin_palm_spotlight(int aOn, int aLeft, int aTop, int aRight, int aBottom)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_NPAPI
|
||||
RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
|
||||
if (!host) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NPEvent ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.eventType = npPalmSystemEvent;
|
||||
ev.data.systemEvent.type = aOn ? npPalmSpotlightStartEvent : npPalmSpotlightEndEvent;
|
||||
// Palm's host leaves systemEvent.value uninitialised here and fills only the rect
|
||||
// (libWebKitLuna handlePluginSpotlightStart 0x4e6cec-0x4e6d24, which never writes [sp+8]).
|
||||
// The memset above zeroes it, which is strictly tidier and observably the same.
|
||||
ev.data.systemEvent.viewLeft = aLeft;
|
||||
ev.data.systemEvent.viewTop = aTop;
|
||||
ev.data.systemEvent.viewRight = aRight;
|
||||
ev.data.systemEvent.viewBottom = aBottom;
|
||||
|
||||
int delivered = 0;
|
||||
// Copy the instance list first: HandleEvent re-enters the plugin, which may run script and
|
||||
// tear an instance down underneath us.
|
||||
nsTArray<RefPtr<nsNPAPIPluginInstance>> instances(host->JihadRunningInstances());
|
||||
for (uint32_t i = 0; i < instances.Length(); ++i) {
|
||||
int16_t response = kNPEventNotHandled;
|
||||
if (NS_SUCCEEDED(instances[i]->HandleEvent(&ev, &response,
|
||||
NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO))) {
|
||||
delivered++;
|
||||
}
|
||||
}
|
||||
return delivered;
|
||||
#else
|
||||
(void)aOn; (void)aLeft; (void)aTop; (void)aRight; (void)aBottom;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
@@ -274,13 +274,6 @@ public:
|
||||
InfallibleTArray<nsCString>& result,
|
||||
bool firstMatchOnly);
|
||||
|
||||
// Jihad R7 (webOS): a snapshot of the live instances, for jihad_plugin_palm_spotlight.
|
||||
// A COPY, not a reference: dispatching an event re-enters the plugin, which can run script
|
||||
// and destroy an instance, and mInstances is mutated by that teardown.
|
||||
const nsTArray<RefPtr<nsNPAPIPluginInstance>>& JihadRunningInstances() const {
|
||||
return mInstances;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class nsPluginUnloadRunnable;
|
||||
|
||||
|
||||
@@ -569,75 +569,6 @@ NS_IMETHODIMP nsPluginInstanceOwner::InvalidateRect(NPRect *invalidRect)
|
||||
GetContentsScaleFactor(&scaleFactor);
|
||||
rect.ScaleRoundOut(scaleFactor);
|
||||
mPluginFrame->InvalidateLayer(nsDisplayItem::TYPE_PLUGIN, &rect);
|
||||
|
||||
// JIHAD (2026-08-09): drive the offscreen daemon's re-capture for WINDOWLESS plugins.
|
||||
//
|
||||
// A windowless plugin has no mWidget, so the mWidget->Invalidate() branch above (which is what
|
||||
// ultimately reaches PuppetWidget::Invalidate and sets the daemon's mJihadDirty flag) never
|
||||
// runs. InvalidateLayer() alone only marks the plugin's ImageLayer for recomposition; with the
|
||||
// in-process BasicLayerManager and no separate compositor, that repaints the widget's own layer
|
||||
// backbuffer but never triggers jihad_offscreen_render_region, the path that actually renders a
|
||||
// frame into the adapter's shared buffer for the card. Measured: Flash pushes a fresh magenta
|
||||
// frame into its ImageContainer ~15x/s and BasicImageLayer::Paint runs on each, yet
|
||||
// render_region fired ONCE (at load) so the card froze on the pre-content frame. The control
|
||||
// plugin only appeared to work because its static fill was already present at that single
|
||||
// capture. Invalidate the nearest real widget here so every async plugin frame marks the daemon
|
||||
// dirty and gets composited to the card.
|
||||
//
|
||||
// THE RECT MATTERS, and the first version of this got it wrong. It invalidated
|
||||
// nearest->GetBounds() — the WIDGET's bounds, i.e. the whole viewport — on the reasoning
|
||||
// that the daemon re-renders the whole document per capture anyway. That reasoning stopped
|
||||
// being true when the daemon learned to repaint only the damaged rows: a whole-viewport
|
||||
// software render costs ~40 ms on this hardware against ~4 ms for a 320x240 plugin box, and
|
||||
// a full-viewport damage box silently forced the expensive path on every single frame.
|
||||
// Measured: an animated SWF ran at 19 fps with 1 partial repaint in 39, and the damage box
|
||||
// logged as 768x942 with the plugin's own 320x240 unioned uselessly into it.
|
||||
//
|
||||
// So invalidate the plugin's own area, translated from plugin-local into the widget's
|
||||
// coordinates the same way the rest of this file does it: content-box origin relative to the
|
||||
// frame, offset to the widget, in device px.
|
||||
if (!mWidget) {
|
||||
nsIWidget* nearest = mPluginFrame->GetNearestWidget();
|
||||
if (nearest) {
|
||||
nsIntRect widgetRect = rect;
|
||||
nsPresContext* pc = mPluginFrame->PresContext();
|
||||
// Offset to the ROOT frame, which is what the widget's coordinates are relative to.
|
||||
// GetReferenceFrame() was the first attempt and it is wrong here: for a plugin it
|
||||
// resolves to a frame that makes the offset come out 0, so every invalidation landed at
|
||||
// the viewport's top-left corner instead of on the plugin. That is not a small error —
|
||||
// the damage-only repaint then faithfully refreshed the wrong 320x240 box and the
|
||||
// plugin's own area never updated again, which reads on screen as the animation
|
||||
// freezing after its first frame. This is the same offset nsPluginFrame itself uses to
|
||||
// build NPWindow.x/y.
|
||||
nsIFrame* rootFrame = nullptr;
|
||||
if (nsIPresShell* ps = mPluginFrame->PresContext()->GetPresShell()) {
|
||||
rootFrame = ps->GetRootFrame();
|
||||
}
|
||||
nsPoint origin = mPluginFrame->GetContentRectRelativeToSelf().TopLeft() +
|
||||
(rootFrame ? mPluginFrame->GetOffsetToCrossDoc(rootFrame) : nsPoint());
|
||||
widgetRect.MoveBy(pc->AppUnitsToDevPixels(origin.x),
|
||||
pc->AppUnitsToDevPixels(origin.y));
|
||||
// A plugin is entitled to report an invalid rect larger than itself, and an empty one
|
||||
// would invalidate nothing at all; clamp to the plugin's own box, and fall back to the
|
||||
// widget bounds if that leaves nothing (correctness before speed).
|
||||
nsIntRect pluginBox(pc->AppUnitsToDevPixels(origin.x),
|
||||
pc->AppUnitsToDevPixels(origin.y),
|
||||
pc->AppUnitsToDevPixels(
|
||||
mPluginFrame->GetContentRectRelativeToSelf().width),
|
||||
pc->AppUnitsToDevPixels(
|
||||
mPluginFrame->GetContentRectRelativeToSelf().height));
|
||||
widgetRect = widgetRect.Intersect(pluginBox);
|
||||
if (widgetRect.IsEmpty()) {
|
||||
widgetRect = pluginBox;
|
||||
}
|
||||
if (widgetRect.IsEmpty()) {
|
||||
nearest->Invalidate(nearest->GetBounds());
|
||||
} else {
|
||||
nearest->Invalidate(LayoutDeviceIntRect(widgetRect.x, widgetRect.y,
|
||||
widgetRect.width, widgetRect.height));
|
||||
}
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -2599,122 +2530,6 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent)
|
||||
rv = nsEventStatus_eConsumeNoDefault;
|
||||
#endif
|
||||
|
||||
// Guard MUST match the one npapi.h uses to define NPEvent as Palm's union. Any other
|
||||
// predicate compiles this against `typedef void* NPEvent` and does not build.
|
||||
#if defined(XP_UNIX) && defined(MOZ_WIDGET_HEADLESS)
|
||||
// JIHAD (webOS, cavekit-addons-extensions.md R7) — DELIVER INPUT IN THE PALM PROTOCOL.
|
||||
//
|
||||
// Everything above this point is a platform arm: XP_MACOSX, XP_WIN, MOZ_X11. A
|
||||
// cairo-headless build matches none of them, so this function used to reduce to
|
||||
// `return nsEventStatus_eIgnore;` and a windowless plugin received no mouse, key, focus or
|
||||
// blur event in its whole lifetime. Measured on the TouchPad before this branch existed: a
|
||||
// tap hit-tested the <EMBED>, the daemon logged `mouseSend <EMBED> ... : down`, and the
|
||||
// plugin's NPP_HandleEvent was called exactly once ever — for the child's own gain-focus
|
||||
// injection. Nothing upstream is at fault; the DOM listeners, the hit test, the windowless
|
||||
// guards and the IPC hop are all live and correct (see the Dispatch*ToPlugin callers).
|
||||
//
|
||||
// The event shape is Palm's, because NPEvent on this platform IS Palm's union (npapi.h) and
|
||||
// the device's Flash was compiled against that declaration.
|
||||
{
|
||||
NPEvent pluginEvent;
|
||||
memset(&pluginEvent, 0, sizeof(pluginEvent));
|
||||
|
||||
if (anEvent.mClass == eMouseEventClass) {
|
||||
const WidgetMouseEvent& mouseEvent = *anEvent.AsMouseEvent();
|
||||
|
||||
switch (anEvent.mMessage) {
|
||||
case eMouseDown: pluginEvent.eventType = npPalmPenDownEvent; break;
|
||||
case eMouseUp: pluginEvent.eventType = npPalmPenUpEvent; break;
|
||||
case eMouseMove: pluginEvent.eventType = npPalmPenMoveEvent; break;
|
||||
// The synthesized click/dblclick that follows a down/up pair. Palm's host sends these
|
||||
// as their own event types rather than swallowing them (libWebKitLuna
|
||||
// handleMouseEvent 0x4e946c-0x4e94d8 maps click -> 65536 and dblclick -> 32768, in
|
||||
// ADDITION to the down/up pair, not instead of it), so this is what a plugin written
|
||||
// for webOS was built to see. The X11 arm above drops them instead, which is right
|
||||
// for X11 and wrong here.
|
||||
//
|
||||
// Flash itself ignores both: its NPP_HandleEvent jump table (libflashplayer.so
|
||||
// 0x4514c-0x4522c) compares only against 1/2/4/8/16/64/128/256/512, and anything
|
||||
// else falls through to "not handled" — confirmed on device, where a tap logs
|
||||
// `palm event 0x10000 ... handled=0` between two handled pen events. Sending them
|
||||
// anyway is deliberate: the contract is the host's, not this one plugin's, and a
|
||||
// plugin that acts only on the click is otherwise silently unusable.
|
||||
case eMouseClick: pluginEvent.eventType = npPalmPenClickEvent; break;
|
||||
case eMouseDoubleClick: pluginEvent.eventType = npPalmPenDoubleClickEvent; break;
|
||||
default: break; // over/out/wheel/aux: no Palm equivalent, drop
|
||||
}
|
||||
|
||||
if (pluginEvent.eventType) {
|
||||
// The plugin-local point, computed exactly as the MOZ_X11 arm above computes it. This
|
||||
// is also what Palm's own host does — PluginView's webOS block uses the renderer's
|
||||
// absoluteToLocal() on the same absolute location — so the two hosts agree on the
|
||||
// space without either being ported to the other.
|
||||
//
|
||||
// Deliberately NO zoom or scroll arithmetic here. The card's fit-zoom is not an engine
|
||||
// zoom (it is applied when the daemon scales the capture context), layout.css.
|
||||
// devPixelsPerPx is pinned to 1.0, and the daemon has already mapped card px to CSS
|
||||
// viewport px before dispatching. Adding a factor here is how plugin input drifts away
|
||||
// from where page content thinks it was clicked; the criterion is that both go through
|
||||
// the same transform, and they do precisely because this arm adds none.
|
||||
const nsPresContext* presContext = mPluginFrame->PresContext();
|
||||
nsPoint appPoint =
|
||||
nsLayoutUtils::GetEventCoordinatesRelativeTo(&anEvent, mPluginFrame) -
|
||||
mPluginFrame->GetContentRectRelativeToSelf().TopLeft();
|
||||
pluginEvent.data.penEvent.xCoord =
|
||||
presContext->AppUnitsToDevPixels(appPoint.x);
|
||||
pluginEvent.data.penEvent.yCoord =
|
||||
presContext->AppUnitsToDevPixels(appPoint.y);
|
||||
pluginEvent.data.penEvent.modifiers =
|
||||
(mouseEvent.IsControl() ? npPalmCtrlKeyModifier : 0) |
|
||||
(mouseEvent.IsAlt() ? npPalmAltKeyModifier : 0) |
|
||||
(mouseEvent.IsShift() ? npPalmShiftKeyModifier : 0) |
|
||||
(mouseEvent.IsMeta() ? npPalmMetaKeyModifier : 0);
|
||||
}
|
||||
} else if (anEvent.mClass == eKeyboardEventClass) {
|
||||
const WidgetKeyboardEvent& keyEvent = *anEvent.AsKeyboardEvent();
|
||||
|
||||
switch (anEvent.mMessage) {
|
||||
case eKeyDown: pluginEvent.eventType = npPalmKeyDownEvent; break;
|
||||
case eKeyUp: pluginEvent.eventType = npPalmKeyUpEvent; break;
|
||||
case eKeyPress: pluginEvent.eventType = npPalmKeyPressEvent; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (pluginEvent.eventType) {
|
||||
// There is no native key event to copy from on this platform (mPluginEvent is a
|
||||
// Windows concept), so the Palm fields are synthesized from the widget event.
|
||||
// rawkeyCode/rawModifier are Palm's "pass the unprocessed event through" pair, which
|
||||
// for a synthesized event is the same key code and modifier set.
|
||||
int32_t mods =
|
||||
(keyEvent.IsControl() ? npPalmCtrlKeyModifier : 0) |
|
||||
(keyEvent.IsAlt() ? npPalmAltKeyModifier : 0) |
|
||||
(keyEvent.IsShift() ? npPalmShiftKeyModifier : 0) |
|
||||
(keyEvent.IsMeta() ? npPalmMetaKeyModifier : 0);
|
||||
pluginEvent.data.keyEvent.chr =
|
||||
static_cast<int32_t>(keyEvent.PseudoCharCode());
|
||||
pluginEvent.data.keyEvent.modifiers = mods;
|
||||
pluginEvent.data.keyEvent.rawkeyCode = static_cast<int32_t>(keyEvent.mKeyCode);
|
||||
pluginEvent.data.keyEvent.rawModifier = mods;
|
||||
}
|
||||
} else if (anEvent.mMessage == eFocus || anEvent.mMessage == eBlur) {
|
||||
// Focus arrives as a bare WidgetGUIEvent with a null widget (see DispatchFocusToPlugin),
|
||||
// so it must not touch the coordinate path above.
|
||||
pluginEvent.eventType = npPalmSystemEvent;
|
||||
pluginEvent.data.systemEvent.type =
|
||||
(anEvent.mMessage == eFocus) ? npPalmGainFocusEvent : npPalmLoseFocusEvent;
|
||||
}
|
||||
|
||||
if (pluginEvent.eventType) {
|
||||
int16_t response = kNPEventNotHandled;
|
||||
mInstance->HandleEvent(&pluginEvent, &response,
|
||||
NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO);
|
||||
if (response == kNPEventHandled) {
|
||||
rv = nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -3162,40 +2977,23 @@ NS_IMETHODIMP nsPluginInstanceOwner::CreateWidget(void)
|
||||
if (!mWidget) {
|
||||
// native (single process)
|
||||
mWidget = do_CreateInstance(kWidgetCID, &rv);
|
||||
if (!mWidget) {
|
||||
// JIHAD (2026-10-05): A BUILD WITH NO CHILD-WINDOW WIDGET HAS NO WINDOWED PLUGINS.
|
||||
// The offscreen embedding registers no widget for kWidgetCID, so this came back
|
||||
// null and the Create() call below dereferenced it: the daemon died with SIGSEGV on
|
||||
// every page whose Flash movie was embedded the ordinary way. A plugin is windowed
|
||||
// unless it asks otherwise, and Flash asks only for wmode=opaque/transparent — the
|
||||
// default, wmode=window, is what most of the web uses (found on two "Flash test"
|
||||
// sites; every test page of ours had wmode=opaque). webOS's own browser never had
|
||||
// plugin windows either and handed every plugin a drawable; its Flash build draws
|
||||
// through the Palm window structure in both modes. So do the same: with no window
|
||||
// to give, the instance is windowless.
|
||||
mInstance->SetWindowless(true);
|
||||
windowless = true;
|
||||
} else {
|
||||
nsWidgetInitData initData;
|
||||
initData.mWindowType = eWindowType_plugin;
|
||||
initData.mUnicode = false;
|
||||
initData.clipChildren = true;
|
||||
initData.clipSiblings = true;
|
||||
rv = mWidget->Create(parentWidget.get(), nullptr,
|
||||
LayoutDeviceIntRect(0, 0, 0, 0), &initData);
|
||||
if (NS_FAILED(rv)) {
|
||||
mWidget->Destroy();
|
||||
mWidget = nullptr;
|
||||
return rv;
|
||||
}
|
||||
nsWidgetInitData initData;
|
||||
initData.mWindowType = eWindowType_plugin;
|
||||
initData.mUnicode = false;
|
||||
initData.clipChildren = true;
|
||||
initData.clipSiblings = true;
|
||||
rv = mWidget->Create(parentWidget.get(), nullptr,
|
||||
LayoutDeviceIntRect(0, 0, 0, 0), &initData);
|
||||
if (NS_FAILED(rv)) {
|
||||
mWidget->Destroy();
|
||||
mWidget = nullptr;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
if (mWidget) {
|
||||
mWidget->EnableDragDrop(true);
|
||||
mWidget->Show(false);
|
||||
mWidget->Enable(false);
|
||||
}
|
||||
mWidget->EnableDragDrop(true);
|
||||
mWidget->Show(false);
|
||||
mWidget->Enable(false);
|
||||
}
|
||||
|
||||
if (mPluginFrame) {
|
||||
|
||||
@@ -133,12 +133,6 @@ BrowserStreamChild::RecvNPP_DestroyStream(const NPReason& reason)
|
||||
{
|
||||
PLUGIN_LOG_DEBUG_METHOD;
|
||||
|
||||
// Jihad R7: end-of-stream is what tells a Flash player the movie is complete. A player that
|
||||
// is never told stops on frame zero, which looks exactly like a player that cannot draw.
|
||||
fprintf(stderr, "[jihad-npapi-child] RecvNPP_DestroyStream reason=%d state=%d\n",
|
||||
(int)reason, (int)mState);
|
||||
fflush(stderr);
|
||||
|
||||
if (ALIVE != mState)
|
||||
NS_RUNTIMEABORT("Unexpected state: recevied NPP_DestroyStream twice?");
|
||||
|
||||
@@ -238,12 +232,8 @@ BrowserStreamChild::Deliver()
|
||||
if (kStreamOpen == mStreamStatus)
|
||||
mStreamStatus = NPRES_DONE;
|
||||
|
||||
NPError dsrv = mInstance->mPluginIface
|
||||
(void) mInstance->mPluginIface
|
||||
->destroystream(&mInstance->mData, &mStream, mStreamStatus);
|
||||
// Jihad R7: the delivery site, as opposed to RecvNPP_DestroyStream which only queues it.
|
||||
fprintf(stderr, "[jihad-npapi-child] NPP_DestroyStream status=%d rv=%d\n",
|
||||
(int)mStreamStatus, (int)dsrv);
|
||||
fflush(stderr);
|
||||
}
|
||||
if (DESTROYED == mDestroyPending && mNotifyPending) {
|
||||
NS_ASSERTION(mStreamNotify, "mDestroyPending but no mStreamNotify?");
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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/. */
|
||||
|
||||
/*
|
||||
* NPN entry points exported as GLOBAL SYMBOLS, for plugins that expect the old Netscape
|
||||
* convention where the host browser exports them rather than only passing an NPNetscapeFuncs
|
||||
* table to NP_Initialize.
|
||||
*
|
||||
* WHY THIS IS ITS OWN TRANSLATION UNIT. The obvious place for these is PluginModuleChild.cpp,
|
||||
* and it does not work: npapi.h already declares NPN_InvalidateRect, and every header in this
|
||||
* build is compiled under `-include config/gcc_hidden.h`, which pushes hidden visibility. GCC
|
||||
* takes visibility from the FIRST declaration it sees, so a later definition carrying
|
||||
* __attribute__((visibility("default"))) is silently ignored and the symbol lands in the
|
||||
* object as GLOBAL HIDDEN — present to `nm`, absent from the dynamic table, and therefore
|
||||
* impossible to interpose. Verified with readelf. This file deliberately includes no NPAPI
|
||||
* headers at all and describes the functions with void* so there is no prior declaration to
|
||||
* inherit hidden visibility from.
|
||||
*
|
||||
* WHY IT MATTERS. webOS's Flash imports NPN_CreateObject and NPN_InvalidateRect as undefined
|
||||
* dynamic symbols. Left alone they bind into libWebKitLuna.so — another browser engine that
|
||||
* is in this process because Flash (and our Piranha drawing path) pull it in. Two consequences,
|
||||
* both measured:
|
||||
* 1. Flash's repaint requests go to LunaSysMgr's WebKit and never reach our engine, so the
|
||||
* plugin is only ever painted when something else happens to invalidate it. That alone
|
||||
* makes an animating plugin look frozen.
|
||||
* 2. It is a latent crash: UXP stores a PluginInstanceChild* in NPP::ndata, while
|
||||
* libWebKitLuna's NPN_InvalidateRect casts that same field to a WebCore::PluginView* and
|
||||
* makes a virtual call on it — a wild vtable dispatch on the first repaint.
|
||||
*
|
||||
* The forwarding pointers are installed by PluginModuleChild once the browser function table
|
||||
* exists; until then these are safe no-ops.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
void (*gJihadNPNInvalidateRect)(void* aNPP, void* aRect) = 0;
|
||||
void* (*gJihadNPNCreateObject)(void* aNPP, void* aClass) = 0;
|
||||
|
||||
// Read by the host repaint clock in PluginInstanceChild.cpp: while this is zero the plugin has
|
||||
// never asked to be repainted, so the host has to drive it.
|
||||
unsigned gJihadNPNInvalidateRectCalls = 0;
|
||||
|
||||
__attribute__((visibility("default"))) void
|
||||
NPN_InvalidateRect(void* aNPP, void* aRect)
|
||||
{
|
||||
// The first call is the signal that the plugin's frame clock is finally running; without
|
||||
// it the plugin is only ever painted by an unrelated invalidation, which is
|
||||
// indistinguishable on screen from a plugin that draws nothing. Log it once, and log the
|
||||
// no-forwarder case too — arriving before PluginModuleChild installs the pointers is a
|
||||
// dropped repaint, not a no-op.
|
||||
gJihadNPNInvalidateRectCalls++;
|
||||
static int sLogged = 0;
|
||||
if (sLogged < 8) {
|
||||
sLogged++;
|
||||
fprintf(stderr, "[jihad-npapi-child] NPN_InvalidateRect npp=%p rect=%p fwd=%p\n",
|
||||
aNPP, aRect, (void*)gJihadNPNInvalidateRect);
|
||||
fflush(stderr);
|
||||
}
|
||||
if (gJihadNPNInvalidateRect) {
|
||||
gJihadNPNInvalidateRect(aNPP, aRect);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) void*
|
||||
NPN_CreateObject(void* aNPP, void* aClass)
|
||||
{
|
||||
return gJihadNPNCreateObject ? gJihadNPNCreateObject(aNPP, aClass) : 0;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -125,29 +125,6 @@ child:
|
||||
// of its parent has changed
|
||||
async ContentsScaleFactorChanged(double aContentsScaleFactor);
|
||||
|
||||
// Jihad (webOS, cavekit-addons-extensions.md R7): ask the plugin for ONE frame, now.
|
||||
//
|
||||
// Palm's host issued npPalmDrawEvent every time the PAGE painted, so Flash can sit on its own
|
||||
// frame clock and expect to be asked. Patch 0026 got it drawing by giving the CHILD a timer,
|
||||
// but that timer free-runs against the daemon's tick, so a frame lands at a drifting phase and
|
||||
// waits up to a whole tick to be published. Measured on device with a 30 fps SWF: 1:1 delivery
|
||||
// and a 31-34 ms average, but a gap spread of 9-62 ms, which is what the eye reads as stutter.
|
||||
//
|
||||
// CAUTION, measured 2026-08-10 and it contradicts what 0026 assumed: the plugin DOES invalidate
|
||||
// itself, ~22 times a second, alongside our ~27. The counter 0026 reads to conclude otherwise
|
||||
// (gJihadNPNInvalidateRectCalls) only sees calls that bind the NPN_InvalidateRect SYMBOL, and
|
||||
// an ordinary NPAPI plugin goes through the NPNetscapeFuncs table instead. So this message is
|
||||
// currently a SECOND producer rather than the only one, and whether it should exist at all for
|
||||
// a self-invalidating plugin is open — see the R7 frame-rate criterion.
|
||||
//
|
||||
// This is the pull that removes the second clock. ASYNC on purpose: the stock host pulls the
|
||||
// plugin synchronously inside its page paint (PluginViewPalm), but out-of-process that shape
|
||||
// is intr NPP_HandleEvent, which would block the daemon's main thread — and therefore its
|
||||
// tick and its YAP socket — for a full IPC round trip plus a Flash paint on every frame.
|
||||
// Async keeps the phase lock and costs nothing: the child coalesces duplicate requests via
|
||||
// mCurrentInvalidateTask, so an extra request while one is in flight is a no-op.
|
||||
async JihadRequestFrame();
|
||||
|
||||
// ********************** Async plugins rendering
|
||||
// see https://wiki.mozilla.org/Gecko:AsyncPluginPainting
|
||||
// **********************
|
||||
|
||||
@@ -31,7 +31,6 @@ using mozilla::gfx::SharedDIBSurface;
|
||||
#endif
|
||||
#endif
|
||||
#include "gfxSharedImageSurface.h"
|
||||
#include <dlfcn.h> // Jihad R7: reach the plugin's own glib for npPalmEventLoopValue
|
||||
#include "gfxUtils.h"
|
||||
#include "gfxAlphaRecovery.h"
|
||||
|
||||
@@ -51,61 +50,6 @@ using namespace mozilla::gfx;
|
||||
using namespace mozilla::widget;
|
||||
using namespace std;
|
||||
|
||||
// Defined next to JihadPumpGlib, far below; declared here because DoNPP_New starts it.
|
||||
static void JihadStartGlibPump(PluginInstanceChild* aInstance);
|
||||
|
||||
// The instance the host clock repaints, and the count of repaints the PLUGIN asked for. Both
|
||||
// live here rather than on the instance because the pump is a process-wide free function.
|
||||
// Cleared in ~PluginInstanceChild so the clock cannot outlive what it points at.
|
||||
static PluginInstanceChild* sJihadPalmInstance = nullptr;
|
||||
static uint64_t gJihadPalmDrawEvents = 0;
|
||||
// Defined in JihadNPNInterpose.cpp, which deliberately includes no NPAPI headers.
|
||||
extern "C" unsigned gJihadNPNInvalidateRectCalls;
|
||||
|
||||
// ── WHICH CALL SITE ACTUALLY DRIVES PLUGIN DRAWS (Jihad, addons R7) ──────────────────────
|
||||
//
|
||||
// Three sessions have now attributed the plugin's draw rate to a specific clock by READING the
|
||||
// code, and every attribution was wrong. Patch 0029 finally put a counter on the request side
|
||||
// and the numbers did not agree with any of them: 3988 draws against 3072 host requests, with
|
||||
// the child's own timer provably stood down (one "frame clock -> HOST" transition, no fallback)
|
||||
// and Flash provably not self-invalidating (gJihadNPNInvalidateRectCalls stays 0). So roughly a
|
||||
// quarter of all draws come from a fourth place, and at an 8 ms tick that share DOMINATES.
|
||||
//
|
||||
// Every path to a draw funnels through AsyncShowPluginFrame, so tagging its call sites answers
|
||||
// the question directly rather than by elimination. Reported next to `palm draw`, which is the
|
||||
// count these have to add up to. Declared up here with the other file statics because the first
|
||||
// tagged site is in AnswerNPP_SetWindow, well above the frame-clock block further down.
|
||||
enum {
|
||||
kJihadShowSetWindow = 0, // AnswerNPP_SetWindow / AsyncSetWindow, accumulated rect
|
||||
kJihadShowInvalidate, // InvalidateRect reached from OUR host repaint tick
|
||||
kJihadShowPluginSelf, // InvalidateRect reached from the PLUGIN's own NPN_InvalidateRect
|
||||
kJihadShowRetry, // InvalidateRectDelayed's retry when ShowPluginFrame refused
|
||||
kJihadShowBgUpdate, // RecvUpdateBackground, background changed
|
||||
kJihadShowBgUpdateTail, // RecvUpdateBackground, unconditional tail
|
||||
kJihadShowBgDestroy, // background destroyer
|
||||
kJihadShowSites
|
||||
};
|
||||
static uint32_t gJihadShowFrom[kJihadShowSites];
|
||||
static const char* const kJihadShowNames[kJihadShowSites] = {
|
||||
"setwin", "hostinval", "plugininval", "retry", "bgupd", "bgtail", "bgdestroy"
|
||||
};
|
||||
|
||||
// True only while JihadPalmRepaintTick's own InvalidateRect call is on the stack, so the two
|
||||
// producers can be told apart AT the shared funnel.
|
||||
//
|
||||
// WHY THIS EXISTS, and it invalidates a premise the port has carried since patch 0026:
|
||||
// gJihadNPNInvalidateRectCalls — the counter used to decide "the plugin never asks to be
|
||||
// repainted, so the host must drive it" — is STRUCTURALLY BLIND. It lives in
|
||||
// JihadNPNInterpose.cpp and only counts calls that bind the NPN_InvalidateRect SYMBOL. An
|
||||
// ordinary NPAPI plugin calls through the NPNetscapeFuncs table instead, which
|
||||
// PluginModuleChild fills with its own _invalidaterect (PluginModuleChild.cpp:986); that
|
||||
// forwards straight to InstCast(aNPP)->InvalidateRect (:1302-1310) and never touches the
|
||||
// interpose. So the counter can read 0 for the whole run while the plugin invalidates
|
||||
// constantly — which is exactly what was measured: 1344 host requests against 2662 invalidates
|
||||
// with the child's timer provably off (one "frame clock -> HOST", no fallback).
|
||||
static bool sJihadInHostRepaint = false;
|
||||
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
@@ -249,8 +193,6 @@ PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface,
|
||||
#endif
|
||||
, mAccumulatedInvalidRect(0,0,0,0)
|
||||
, mIsTransparent(false)
|
||||
, mJihadSentPalmGainFocus(false)
|
||||
, mJihadInputEventsLogged(0)
|
||||
, mSurfaceType(gfxSurfaceType::Max)
|
||||
, mPendingPluginCall(false)
|
||||
, mDoAlphaExtraction(false)
|
||||
@@ -289,11 +231,6 @@ PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface,
|
||||
|
||||
PluginInstanceChild::~PluginInstanceChild()
|
||||
{
|
||||
// The host repaint clock outlives any single instance (it is bound to the process-wide
|
||||
// GMainLoop), so it must not be left holding this pointer.
|
||||
if (sJihadPalmInstance == this) {
|
||||
sJihadPalmInstance = nullptr;
|
||||
}
|
||||
#if defined(OS_WIN)
|
||||
NS_ASSERTION(!mPluginWindowHWND, "Destroying PluginInstanceChild without NPP_Destroy?");
|
||||
if (GetQuirks() & QUIRK_UNITY_FIXUP_MOUSE_CAPTURE) {
|
||||
@@ -323,65 +260,6 @@ PluginInstanceChild::~PluginInstanceChild()
|
||||
#endif
|
||||
}
|
||||
|
||||
// ── THE webOS PLUGIN ABI (Jihad, addons R7) ───────────────────────────────────────────────
|
||||
//
|
||||
// None of this is in our npapi.h; it is Palm's XP_WEBOS block, and every layout below was
|
||||
// checked TWICE — against WebKit/Source/WebCore/plugins/npapi.h, and against the machine code
|
||||
// of the host that actually shipped on the device. That host is NOT the Qt one in our WebKit
|
||||
// checkout (PluginViewQt.cpp passes a QPainter* and Flash has no Qt dependency at all); it is
|
||||
// WebCore::PluginView compiled from PluginViewPalm.cpp into /usr/lib/libWebKitLuna.so, which
|
||||
// is unstripped on the device and can simply be read. Offsets cited below are into that
|
||||
// library. Get these wrong and the plugin reads garbage, silently.
|
||||
//
|
||||
// The event union itself now lives in npapi.h as the real `NPEvent` for this platform, so the
|
||||
// parent (nsPluginInstanceOwner::ProcessEvent) and the child cannot drift and the type survives
|
||||
// the NPRemoteEvent memcpy. What used to be here was a TRUNCATED private mirror —
|
||||
// systemEvent held only `type`, which is enough for gain-focus and silently wrong for a
|
||||
// spotlight event, whose rect lives in the four fields the mirror did not have.
|
||||
//
|
||||
// These assertions are the actual contract. Each offset was read out of the shipped host's
|
||||
// machine code, and a mismatch here means the plugin reads a different field than the one
|
||||
// written — a failure with no symptom other than wrong behaviour.
|
||||
#if defined(XP_UNIX) && defined(MOZ_WIDGET_HEADLESS)
|
||||
static_assert(sizeof(NPEvent) == 48, "webOS NPEvent is 48 bytes");
|
||||
static_assert(offsetof(NPEvent, data) == 4, "eventType is a 4-byte field at 0");
|
||||
// PluginView::handleMouseEvent (libWebKitLuna 0x4e9458/0x4e9460/0x4e9464) stores x, y and
|
||||
// modifiers to [sp+4], [sp+8], [sp+12] with eventType at [sp].
|
||||
static_assert(offsetof(NPEvent, data.penEvent.xCoord) == 4, "penEvent.xCoord @4");
|
||||
static_assert(offsetof(NPEvent, data.penEvent.yCoord) == 8, "penEvent.yCoord @8");
|
||||
static_assert(offsetof(NPEvent, data.penEvent.modifiers) == 12, "penEvent.modifiers @12");
|
||||
// PluginView::handleKeyboardEvent (0x4e8ebc/0x4e8e80/0x4e8ef4/0x4e8f04).
|
||||
static_assert(offsetof(NPEvent, data.keyEvent.chr) == 4, "keyEvent.chr @4");
|
||||
static_assert(offsetof(NPEvent, data.keyEvent.modifiers) == 8, "keyEvent.modifiers @8");
|
||||
static_assert(offsetof(NPEvent, data.keyEvent.rawkeyCode) == 12, "keyEvent.rawkeyCode @12");
|
||||
static_assert(offsetof(NPEvent, data.keyEvent.rawModifier) == 16, "keyEvent.rawModifier @16");
|
||||
// PluginView::handlePluginSpotlightStart (0x4e6cec-0x4e6d20) writes 0x100 at [sp], 11 at
|
||||
// [sp+4], then left/right/top/bottom at [sp+12]/[sp+16]/[sp+20]/[sp+24] — note it never
|
||||
// initialises `value` at [sp+8].
|
||||
static_assert(offsetof(NPEvent, data.systemEvent.type) == 4, "systemEvent.type @4");
|
||||
static_assert(offsetof(NPEvent, data.systemEvent.viewLeft) == 12, "systemEvent.viewLeft @12");
|
||||
static_assert(offsetof(NPEvent, data.systemEvent.viewRight) == 16, "systemEvent.viewRight @16");
|
||||
static_assert(offsetof(NPEvent, data.systemEvent.viewTop) == 20, "systemEvent.viewTop @20");
|
||||
static_assert(offsetof(NPEvent, data.systemEvent.viewBottom) == 24, "systemEvent.viewBottom @24");
|
||||
// PluginView::paint (0x4e7e30-0x4e7e44) stores platformContext() at frame+52 and the four
|
||||
// scaled dst coords at +56/+60/+64/+68, i.e. +24/+28/+32/+36 inside the draw member.
|
||||
static_assert(offsetof(NPEvent, data.drawEvent.graphicsContext) == 28, "drawEvent.gc @28");
|
||||
static_assert(offsetof(NPEvent, data.drawEvent.dstLeft) == 32, "drawEvent.dstLeft @32");
|
||||
#endif // XP_UNIX && MOZ_WIDGET_HEADLESS
|
||||
|
||||
// NPWindow.window for a webOS plugin. Palm's host never calls setwindow without one:
|
||||
// PluginView::setNPWindowRect (libWebKitLuna 0x4e74e0) builds it as a STACK LOCAL, points
|
||||
// m_npWindow.window at it (0x4e7570 `str sp, [r4, #520]`), calls setwindow, and immediately
|
||||
// re-nulls the pointer (0x4e7614) so the dangling stack address is never left behind.
|
||||
// Field offsets read straight off that function: visible is a byte at +0 (0x4e755c
|
||||
// `strbne r3, [sp]`), bpp is 32 at +4 (0x4e75dc `mov ip,#32; str ip,[sp,#4]`), and scaleFactor
|
||||
// is the double from ScrollView::getScale() at +8 (0x4e7588 `strd r6, [sp, #8]`).
|
||||
struct JihadNpPalmWindow {
|
||||
bool visible;
|
||||
uint32_t bpp;
|
||||
double scaleFactor;
|
||||
};
|
||||
|
||||
NPError
|
||||
PluginInstanceChild::DoNPP_New()
|
||||
{
|
||||
@@ -408,44 +286,6 @@ PluginInstanceChild::DoNPP_New()
|
||||
return rv;
|
||||
}
|
||||
|
||||
// WEBOS EVENT LOOP (Jihad, addons R7).
|
||||
//
|
||||
// Palm's own host does this right after NPP_New and before anything else
|
||||
// (WebKit/Source/WebCore/plugins/qt/PluginViewQt.cpp, PluginView::platformStart):
|
||||
// it hands the plugin a GMainLoop* through npPalmEventLoopValue. A webOS plugin drives
|
||||
// its own frame scheduling off that loop, so a plugin that never receives one is a
|
||||
// plugin that instantiates and then never asks to paint — exactly the symptom this port
|
||||
// had.
|
||||
//
|
||||
// glib is not linked into this process; it arrives as a dependency of the plugin, so it
|
||||
// can only be reached by name once the plugin is loaded — which it is by now. A plugin
|
||||
// that does not know the variable just returns an error, so this is safe for any plugin.
|
||||
if (mPluginIface->setvalue) {
|
||||
static void* sMainLoop = nullptr;
|
||||
if (!sMainLoop) {
|
||||
if (void* glib = dlopen("libglib-2.0.so.0", RTLD_NOW | RTLD_GLOBAL)) {
|
||||
typedef void* (*GMainLoopNewFn)(void*, int);
|
||||
GMainLoopNewFn newLoop = (GMainLoopNewFn)dlsym(glib, "g_main_loop_new");
|
||||
if (newLoop) {
|
||||
sMainLoop = newLoop(nullptr, 1 /* is_running */);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sMainLoop) {
|
||||
const uint32_t kNpPalmEventLoopValue = 10000;
|
||||
NPError lerr = mPluginIface->setvalue(npp, (NPNVariable)kNpPalmEventLoopValue,
|
||||
sMainLoop);
|
||||
fprintf(stderr, "[jihad-npapi-child] npPalmEventLoopValue loop=%p err=%d\n",
|
||||
sMainLoop, (int)lerr);
|
||||
// The plugin now has a loop to hang sources off; start running it. See
|
||||
// JihadStartGlibPump for why this cannot wait for the first paint.
|
||||
JihadStartGlibPump(this);
|
||||
} else {
|
||||
fprintf(stderr, "[jihad-npapi-child] npPalmEventLoopValue: no glib main loop\n");
|
||||
}
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
Initialize();
|
||||
|
||||
#if defined(XP_MACOSX) && defined(__i386__)
|
||||
@@ -877,8 +717,8 @@ PluginInstanceChild::NPN_SetValue(NPPVariable aVar, void* aValue)
|
||||
// on the chrome process. This is because our audio
|
||||
// API calls should run the chrome proc, not content.
|
||||
NPError rv = NPERR_GENERIC_ERROR;
|
||||
PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome();
|
||||
if (chromeInstance) {
|
||||
PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome();
|
||||
if (chromeInstance) {
|
||||
rv = chromeInstance->PluginRequiresAudioDeviceChanges(this,
|
||||
(NPBool)(intptr_t)aValue);
|
||||
}
|
||||
@@ -1124,35 +964,6 @@ PluginInstanceChild::AnswerNPP_HandleEvent(const NPRemoteEvent& event,
|
||||
else
|
||||
*handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy));
|
||||
|
||||
// Guard MUST match the one npapi.h uses to define NPEvent as Palm's union. Any other
|
||||
// predicate compiles this against `typedef void* NPEvent` and does not build.
|
||||
#if defined(XP_UNIX) && defined(MOZ_WIDGET_HEADLESS)
|
||||
// JIHAD (webOS R7): the only field-visible evidence that host input reached the plugin.
|
||||
// A closed-source plugin acknowledges nothing — Flash returns its `handled` flag before it
|
||||
// even looks at the event (its NPP_HandleEvent sets the return, then dispatches), so this
|
||||
// line proves DELIVERY and nothing more. Capped, because a pen-move stream would otherwise
|
||||
// flood the daemon log, which is the only channel this device has.
|
||||
if (mJihadInputEventsLogged < 24) {
|
||||
mJihadInputEventsLogged++;
|
||||
if (evcopy.eventType == npPalmKeyDownEvent ||
|
||||
evcopy.eventType == npPalmKeyUpEvent ||
|
||||
evcopy.eventType == npPalmKeyPressEvent) {
|
||||
fprintf(stderr, "[jihad-npapi-child] palm key 0x%x chr=%d raw=%d mods=0x%x "
|
||||
"handled=%d\n",
|
||||
(unsigned)evcopy.eventType, (int)evcopy.data.keyEvent.chr,
|
||||
(int)evcopy.data.keyEvent.rawkeyCode,
|
||||
(unsigned)evcopy.data.keyEvent.modifiers, (int)*handled);
|
||||
} else {
|
||||
fprintf(stderr, "[jihad-npapi-child] palm event 0x%x at %d,%d mods=0x%x "
|
||||
"handled=%d\n",
|
||||
(unsigned)evcopy.eventType, (int)evcopy.data.penEvent.xCoord,
|
||||
(int)evcopy.data.penEvent.yCoord,
|
||||
(unsigned)evcopy.data.penEvent.modifiers, (int)*handled);
|
||||
}
|
||||
fflush(stderr);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// Release any reference counted objects created in the child process.
|
||||
if (evcopy.type == NPCocoaEventKeyDown ||
|
||||
@@ -1638,8 +1449,6 @@ PluginInstanceChild::AnswerNPP_SetWindow(const NPRemoteWindow& aWindow)
|
||||
|
||||
if (mPluginIface->setwindow)
|
||||
(void) mPluginIface->setwindow(&mData, &mWindow);
|
||||
#elif defined(MOZ_WIDGET_HEADLESS)
|
||||
// Jihad headless: no windowed plugins; nothing to do.
|
||||
#else
|
||||
# error Implement me for your OS
|
||||
#endif
|
||||
@@ -3018,12 +2827,6 @@ PluginInstanceChild::DoNPP_NewStream(BrowserStreamChild* actor,
|
||||
AssertPluginThread();
|
||||
AutoStackHelper guard(this);
|
||||
NPError rv = actor->StreamConstructed(mimeType, seekable, stype);
|
||||
// JIHAD DIAGNOSTIC (R7): does content actually reach the plugin? A plugin that was
|
||||
// never handed its stream draws nothing for a completely different reason than a
|
||||
// plugin that cannot draw, and on screen the two are identical (an empty box).
|
||||
fprintf(stderr, "[jihad-npapi-child] NPP_NewStream mime=%s seekable=%d rv=%d stype=%d\n",
|
||||
mimeType.get(), (int)seekable, (int)rv, (int)*stype);
|
||||
fflush(stderr);
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -3614,7 +3417,6 @@ PluginInstanceChild::DoAsyncSetWindow(const gfxSurfaceType& aSurfaceType,
|
||||
#endif
|
||||
|
||||
if (!mAccumulatedInvalidRect.IsEmpty()) {
|
||||
gJihadShowFrom[kJihadShowSetWindow]++;
|
||||
AsyncShowPluginFrame();
|
||||
}
|
||||
}
|
||||
@@ -3939,467 +3741,10 @@ PluginInstanceChild::UpdateWindowAttributes(bool aForceSetWindow)
|
||||
mWindow.clipRect.left, mWindow.clipRect.top, mWindow.clipRect.right, mWindow.clipRect.bottom));
|
||||
|
||||
if (mPluginIface->setwindow) {
|
||||
// WEBOS: hand the plugin an NpPalmWindow, exactly as Palm's host does.
|
||||
//
|
||||
// Out of the box this embedder passes NPWindow.window = nullptr for every windowless
|
||||
// plugin (DoAsyncSetWindow hard-nulls it, and the MOZ_WIDGET_HEADLESS branch of
|
||||
// AnswerNPP_SetWindow is a no-op), which is correct for an X11 windowless plugin — the
|
||||
// slot means "native window handle" there, and there isn't one. On webOS the same slot
|
||||
// means something completely different: it carries the plugin's visibility, the
|
||||
// surface depth and the page scale. A webOS plugin told window=NULL has been told
|
||||
// nothing about the surface it is supposed to draw into.
|
||||
//
|
||||
// Scoped to the call and re-nulled afterwards, because this is a stack address and
|
||||
// Palm's host is careful about exactly that (libWebKitLuna 0x4e7570 / 0x4e7614).
|
||||
//
|
||||
// scaleFactor is 1.0 rather than the card's fit-zoom: Palm passes ScrollView::getScale()
|
||||
// because its plugin drew into the page's own scaled surface, whereas here the plugin
|
||||
// owns an unscaled CSS-sized buffer and the compositor applies the zoom afterwards.
|
||||
JihadNpPalmWindow palmWindow;
|
||||
palmWindow.visible = true;
|
||||
palmWindow.bpp = 32;
|
||||
palmWindow.scaleFactor = 1.0;
|
||||
void* savedWindow = mWindow.window;
|
||||
mWindow.window = &palmWindow;
|
||||
|
||||
mPluginIface->setwindow(&mData, &mWindow);
|
||||
|
||||
mWindow.window = savedWindow;
|
||||
|
||||
static int sLoggedSetWindow = 0;
|
||||
if (sLoggedSetWindow < 3) {
|
||||
sLoggedSetWindow++;
|
||||
fprintf(stderr, "[jihad-npapi-child] NPP_SetWindow %dx%d at %d,%d type=%d "
|
||||
"palmWindow{visible=1 bpp=32 scale=1.0}\n",
|
||||
(int)mWindow.width, (int)mWindow.height, (int)mWindow.x, (int)mWindow.y,
|
||||
(int)mWindow.type);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
// WEBOS: the last thing Palm's PluginView::platformStart does is tell the plugin it has
|
||||
// focus — handlePageGainFocus (libWebKitLuna 0x4e6df0) builds
|
||||
// {eventType = 0x100 (npPalmSystemEvent), systemEvent.type = 3 (npPalmGainFocusEvent)}
|
||||
// and dispatches it. A plugin that is never told it is focused and visible has no
|
||||
// reason to start animating.
|
||||
//
|
||||
// Only gain-focus: Flash's own system-event dispatcher (libflashplayer.so 0x44da0)
|
||||
// routes npPalmPageLoadingEvent(8), npPalmPageLoadCompleteEvent(9) and
|
||||
// npPalmViewPortChangedEvent(10) to a bare `mov r0,#0; pop {pc}`, so those three are
|
||||
// inert in this build and sending them would prove nothing.
|
||||
//
|
||||
// After setwindow, not before, and once: the plugin needs its geometry first, and
|
||||
// UpdateWindowAttributes runs on every resize and every scroll.
|
||||
if (mPluginIface->event && !mJihadSentPalmGainFocus) {
|
||||
mJihadSentPalmGainFocus = true;
|
||||
NPEvent ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.eventType = npPalmSystemEvent;
|
||||
ev.data.systemEvent.type = npPalmGainFocusEvent;
|
||||
int16_t handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&ev));
|
||||
fprintf(stderr, "[jihad-npapi-child] npPalmSystemEvent gainFocus handled=%d\n",
|
||||
(int)handled);
|
||||
fflush(stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run any work the plugin scheduled on glib.
|
||||
//
|
||||
// A webOS plugin is handed a GMainLoop through npPalmEventLoopValue and hangs its timers and
|
||||
// idle sources off it — but this process's main loop is chromium's MessageLoop, so unless
|
||||
// glib is pumped explicitly those sources NEVER RUN. The visible symptom is a plugin that
|
||||
// initialises, clears its stage, and then sits on frame zero forever: it is not stuck, it is
|
||||
// simply never being given the chance to advance.
|
||||
//
|
||||
// Bounded so a busy plugin cannot starve the paint it is being pumped for.
|
||||
static int
|
||||
JihadPumpGlib(int aMaxIterations)
|
||||
{
|
||||
typedef int (*GMainContextIterationFn)(void*, int);
|
||||
typedef int (*GMainContextPendingFn)(void*);
|
||||
static bool sTried = false;
|
||||
static GMainContextIterationFn sIterate = nullptr;
|
||||
static GMainContextPendingFn sPending = nullptr;
|
||||
|
||||
if (!sTried) {
|
||||
sTried = true;
|
||||
if (void* glib = dlopen("libglib-2.0.so.0", RTLD_NOW | RTLD_GLOBAL)) {
|
||||
sIterate = (GMainContextIterationFn)dlsym(glib, "g_main_context_iteration");
|
||||
sPending = (GMainContextPendingFn)dlsym(glib, "g_main_context_pending");
|
||||
}
|
||||
fprintf(stderr, "[jihad-npapi-child] glib pump iterate=%p pending=%p\n",
|
||||
(void*)sIterate, (void*)sPending);
|
||||
fflush(stderr);
|
||||
}
|
||||
if (!sIterate) {
|
||||
return 0;
|
||||
}
|
||||
int n = 0;
|
||||
while (n < aMaxIterations) {
|
||||
if (sPending && !sPending(nullptr)) {
|
||||
break;
|
||||
}
|
||||
sIterate(nullptr, 0 /* may_block = FALSE */);
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// ── RUN THE PLUGIN'S EVENT LOOP CONTINUOUSLY (Jihad, addons R7) ───────────────────────────
|
||||
//
|
||||
// Pumping glib only just before a paint deadlocks a webOS plugin, because its startup is
|
||||
// asynchronous: Flash registers com.palm.flashgraphics on the LS2 bus, attaches that socket to
|
||||
// the loop it was handed, and then waits on round-trips to com.palm.systemservice
|
||||
// (getPreferences), com.palm.display (status) and com.palm.db (find) before it starts its frame
|
||||
// clock. Until the clock runs it never calls NPN_InvalidateRect, so nothing ever asks it to
|
||||
// paint — and the only pump was on the paint path. No paint, no pump; no pump, no paint.
|
||||
//
|
||||
// So the pump has to be independent of painting. This reposts itself on the child's own
|
||||
// MessageLoop, which is the SAME thread every other NPAPI call into the plugin runs on — the
|
||||
// arrangement Palm's host gave it, since BrowserServer's main loop simply WAS a GMainLoop.
|
||||
// g_main_loop_run() on a dedicated thread would be the wrong shape: the plugin's timer
|
||||
// callbacks would then fire off the NPAPI thread and reenter us from it.
|
||||
//
|
||||
// MEASURED: before the LS2 role allowed com.palm.flashgraphics, LSRegisterPalmService failed
|
||||
// with -1027, nothing was ever attached to the loop, and g_main_context_pending() returned
|
||||
// FALSE forever. That is fixed in packaging/gen-variant-scripts.sh; the pump only has anything
|
||||
// to run because of it.
|
||||
static const int kJihadGlibPumpIntervalMs = 16; // ~60Hz, the rate a frame clock wants
|
||||
static const int kJihadGlibPumpMaxIters = 20; // bounded: never starve the MessageLoop
|
||||
|
||||
// Host-driven repaint divisor: one repaint every Nth 16 ms tick. 2 is ~30 Hz, which is what
|
||||
// ordinary Flash content is authored at; 4 was ~15 Hz and was the measured ceiling on the
|
||||
// WHOLE pipeline — device 2026-08-10, single instance: plugin draw 21.6-24 fps and RecvShow
|
||||
// 21.3-24.2 fps while the daemon was already compositing 28.4-29.4 fps, i.e. the host was
|
||||
// asking for frames more slowly than it could consume them.
|
||||
//
|
||||
// Env-tunable because the right value is a property of the device under load, not of the
|
||||
// source, and a rebuild per candidate costs a 44 MB push. Clamped: 0 or a negative divisor
|
||||
// would be a modulo-by-zero, and anything past 8 is slower than the pump itself.
|
||||
// Disable the child's OWN repaint timer entirely. $JIHAD_PLUGIN_SELF_DRIVE=0.
|
||||
//
|
||||
// This exists to isolate ONE producer. Measured 2026-08-10, there are two: our host pull and the
|
||||
// plugin invalidating itself (~26/s and ~23/s). Turning off only the daemon's pull
|
||||
// (JIHAD_PLUGIN_PULL=0) does NOT isolate the plugin, because the child's host-drive lease then
|
||||
// expires and this timer takes over — that run reproduces patch 0026 rather than answering the
|
||||
// question, and it measured WORSE than the pull on both rate (24-29 vs 30-33 fps) and spread.
|
||||
// With both switches off the plugin's own NPN_InvalidateRect is the only frame source left, which
|
||||
// is the experiment that decides whether a host clock should exist here at all.
|
||||
static bool JihadChildSelfDriveEnabled()
|
||||
{
|
||||
static int s_on = -1;
|
||||
if (s_on < 0) {
|
||||
const char* e = getenv("JIHAD_PLUGIN_SELF_DRIVE");
|
||||
s_on = (e && *e && atoi(e) == 0) ? 0 : 1; // default ON; only an explicit 0 disables
|
||||
fprintf(stderr, "[jihad-npapi-child] self-drive timer %s\n", s_on ? "enabled" : "DISABLED");
|
||||
fflush(stderr);
|
||||
}
|
||||
return s_on != 0;
|
||||
}
|
||||
|
||||
static int JihadRepaintEveryTicks()
|
||||
{
|
||||
static int sDiv = 0;
|
||||
if (!sDiv) {
|
||||
sDiv = 2;
|
||||
const char* e = getenv("JIHAD_PLUGIN_REPAINT_DIV");
|
||||
if (e && *e) {
|
||||
int v = atoi(e);
|
||||
if (v >= 1 && v <= 8) sDiv = v;
|
||||
}
|
||||
fprintf(stderr, "[jihad-npapi-child] host repaint divisor=%d (~%d Hz)\n",
|
||||
sDiv, 1000 / (kJihadGlibPumpIntervalMs * sDiv));
|
||||
fflush(stderr);
|
||||
}
|
||||
return sDiv;
|
||||
}
|
||||
|
||||
// ── REPAINT A webOS PLUGIN FROM THE HOST'S CLOCK (Jihad, addons R7) ──────────────────────
|
||||
//
|
||||
// Gecko paints a windowless plugin only when something invalidates it, and for an ordinary
|
||||
// NPAPI plugin that is the plugin itself calling NPN_InvalidateRect. A webOS plugin is under
|
||||
// no such obligation, because the host it was written for never made it one: Palm's
|
||||
// PluginView::paint (libWebKitLuna 0x4e7a3c) issues npPalmDrawEvent every time the PAGE
|
||||
// paints, and BrowserServer's compositor painted continuously. Flash on that host could sit
|
||||
// on its frame clock and simply expect to be asked.
|
||||
//
|
||||
// So this drives the repaint until the plugin proves it drives itself, then gets out of the
|
||||
// way — a plugin that calls NPN_InvalidateRect gets Gecko's normal damage-driven painting and
|
||||
// none of this cost. Measured: with no host clock, Flash produced exactly 3 draw events in
|
||||
// 30 s and 0 invalidates.
|
||||
void
|
||||
PluginInstanceChild::JihadPalmRepaintTick()
|
||||
{
|
||||
if (!mLayersRendering || mWindow.width <= 0 || mWindow.height <= 0) {
|
||||
return;
|
||||
}
|
||||
NPRect windowRect = { 0, 0,
|
||||
uint16_t(mWindow.height), uint16_t(mWindow.width) };
|
||||
sJihadInHostRepaint = true;
|
||||
InvalidateRect(&windowRect);
|
||||
sJihadInHostRepaint = false;
|
||||
}
|
||||
|
||||
// ── HOST-DRIVEN FRAME CLOCK (Jihad, addons R7) ───────────────────────────────────────────
|
||||
//
|
||||
// When the daemon is pulling (it calls jihad_plugin_request_frame after each card frame it
|
||||
// publishes), the timer above must get out of the way, or the plugin has TWO drivers and the
|
||||
// extra draws are exactly the drift the pull exists to remove.
|
||||
//
|
||||
// The handoff is timestamp-based rather than a latch because there is no unregister: the daemon
|
||||
// can stop asking at any moment — the card is backgrounded (BrowserPageGoanna::freeze returns
|
||||
// early from maybePaint), the page navigates, the daemon is stopped or crashes — and a latched
|
||||
// plugin would then never paint again. So host-driven is a LEASE that must be renewed. If no
|
||||
// request arrives for kJihadHostDriveLeaseMs the timer resumes on its own.
|
||||
//
|
||||
// THE LEASE MUST EXCEED THE DAEMON'S WORST-CASE GAP BETWEEN REQUESTS, and the first version of
|
||||
// this got that wrong in a way that is worth keeping written down, because the symptom looked
|
||||
// like a mysterious third party rather than like a bug.
|
||||
//
|
||||
// The daemon does not ask again while a request is outstanding; it waits out
|
||||
// kJihadPluginReqTimeoutMs (250 ms) before re-asking. The lease here was 150 ms — SHORTER — so a
|
||||
// single late frame guaranteed the lease expired, the child resumed self-driving, and the two
|
||||
// clocks this patch exists to collapse were both running again. Measured on device before the
|
||||
// fix: the child logged HOST -> SELF -> HOST, and the invalidate counter split almost exactly in
|
||||
// half, 1856 requests from the host against 1846 from the child's own timer, summing to the 3702
|
||||
// total. The 23% of "unexplained" draws that sent an earlier session hunting through
|
||||
// RecvUpdateBackground were this, and the background paths were measured at exactly ZERO.
|
||||
//
|
||||
// So the invariant, not a tuned number: lease > request timeout + one grid period + margin.
|
||||
// 500 ms is 250 + 33 + slack. A card that really stopped publishing still resumes self-drawing
|
||||
// in half a second, which is well inside what a resume can absorb.
|
||||
static const uint32_t kJihadHostDriveLeaseMs = 500;
|
||||
static uint32_t sJihadLastHostRequestMs = 0;
|
||||
static bool sJihadEverHostDriven = false;
|
||||
|
||||
static bool JihadHostIsDrivingFrames()
|
||||
{
|
||||
bool driving = false;
|
||||
if (sJihadEverHostDriven) {
|
||||
uint32_t nowMs = uint32_t(PR_IntervalToMilliseconds(PR_IntervalNow()));
|
||||
// Unsigned subtraction, so the 32-bit PR_IntervalNow wrap is handled without a signed
|
||||
// compare that would read as "lease expired" for one interval every wrap.
|
||||
driving = (nowMs - sJihadLastHostRequestMs) < kJihadHostDriveLeaseMs;
|
||||
}
|
||||
|
||||
// LOG EVERY TRANSITION. A watchdog that silently resumes self-driving would make a BROKEN
|
||||
// pull look like "no change" in the frame numbers instead of "the feature is off" — the same
|
||||
// trap family as an empty log passing every grep, and a wedged plugin reading as a pacing
|
||||
// regression. Both of those cost real time in this tree already; this one is cheap to close.
|
||||
static bool sWas = false;
|
||||
if (driving != sWas) {
|
||||
sWas = driving;
|
||||
fprintf(stderr, "[jihad-npapi-child] frame clock -> %s\n",
|
||||
driving ? "HOST (daemon pull)" : "SELF (timer; host lease expired)");
|
||||
fflush(stderr);
|
||||
}
|
||||
return driving;
|
||||
}
|
||||
|
||||
bool
|
||||
PluginInstanceChild::RecvJihadRequestFrame()
|
||||
{
|
||||
sJihadEverHostDriven = true;
|
||||
sJihadLastHostRequestMs = uint32_t(PR_IntervalToMilliseconds(PR_IntervalNow()));
|
||||
|
||||
// A plugin that drives itself gets Gecko's normal damage-driven painting, exactly as the
|
||||
// timer path decides below; asking such a plugin for frames would double its draw rate.
|
||||
if (gJihadNPNInvalidateRectCalls != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint64_t sHostFrames = 0;
|
||||
static uint32_t sReportMs = 0;
|
||||
if (!(++sHostFrames % 64)) {
|
||||
uint32_t nowMs = uint32_t(PR_IntervalToMilliseconds(PR_IntervalNow()));
|
||||
if (sReportMs) {
|
||||
fprintf(stderr, "[jihad-npapi-child] host-driven: 64 requests in %ums\n",
|
||||
nowMs - sReportMs);
|
||||
fflush(stderr);
|
||||
}
|
||||
sReportMs = nowMs;
|
||||
}
|
||||
|
||||
JihadPalmRepaintTick();
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
class JihadGlibPumpTask final : public Runnable
|
||||
{
|
||||
public:
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
int n = JihadPumpGlib(kJihadGlibPumpMaxIters);
|
||||
|
||||
// The pump itself keeps running at 16 ms whatever drives the frames — Flash's LS2
|
||||
// socket, its timers and its audio sources all live on this GMainLoop, and starving
|
||||
// them is what deadlocked startup before patch 0026. Only the REPAINT defers.
|
||||
static uint64_t sRepaintTicks = 0;
|
||||
if (sJihadPalmInstance && gJihadNPNInvalidateRectCalls == 0 &&
|
||||
JihadChildSelfDriveEnabled() && !JihadHostIsDrivingFrames() &&
|
||||
(++sRepaintTicks % JihadRepaintEveryTicks()) == 0) {
|
||||
sJihadPalmInstance->JihadPalmRepaintTick();
|
||||
}
|
||||
|
||||
// One-shot liveness, because "the plugin is idle" and "the pump died" look
|
||||
// identical on screen. Report the first tick that actually ran a source.
|
||||
static bool sSawFirstSource = false;
|
||||
static uint64_t sTicks = 0;
|
||||
sTicks++;
|
||||
if (n && !sSawFirstSource) {
|
||||
sSawFirstSource = true;
|
||||
fprintf(stderr, "[jihad-npapi-child] glib pump: first source ran on tick %llu\n",
|
||||
(unsigned long long)sTicks);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
MessageLoop::current()->PostDelayedTask(
|
||||
RefPtr<Runnable>(new JihadGlibPumpTask()).forget(),
|
||||
kJihadGlibPumpIntervalMs);
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Idempotent: several plugin instances share one GMainLoop, so they share one pump.
|
||||
static void
|
||||
JihadStartGlibPump(PluginInstanceChild* aInstance)
|
||||
{
|
||||
sJihadPalmInstance = aInstance;
|
||||
static bool sStarted = false;
|
||||
if (sStarted) {
|
||||
return;
|
||||
}
|
||||
MessageLoop* loop = MessageLoop::current();
|
||||
if (!loop) {
|
||||
fprintf(stderr, "[jihad-npapi-child] glib pump: no MessageLoop, not starting\n");
|
||||
fflush(stderr);
|
||||
return;
|
||||
}
|
||||
sStarted = true;
|
||||
fprintf(stderr, "[jihad-npapi-child] glib pump: starting, every %dms\n",
|
||||
kJihadGlibPumpIntervalMs);
|
||||
fflush(stderr);
|
||||
loop->PostDelayedTask(RefPtr<Runnable>(new JihadGlibPumpTask()).forget(),
|
||||
kJihadGlibPumpIntervalMs);
|
||||
}
|
||||
|
||||
// ── PIRANHA CONTEXT FOR A WEBOS PLUGIN (Jihad, addons R7) ────────────────────────────────
|
||||
//
|
||||
// webOS plugins that answer npPalmUseGraphicsContext=true expect NpPalmDrawEvent to carry a
|
||||
// Piranha PGContext* rather than a raster pointer. Piranha ships on the device inside
|
||||
// libWebKitLuna.so (PGContext/PGSurface) and libPiranha.so; neither is linked into this
|
||||
// process, so everything is resolved by dlsym on the mangled names — no webOS headers, and
|
||||
// nothing breaks on a desktop build where the libraries simply are not there.
|
||||
//
|
||||
// Feasibility was established separately before any of this was written; see
|
||||
// render/goanna/test/piranha_offscreen_probe.c, which does exactly this sequence in a bare
|
||||
// process and confirms the pixels land in caller-owned memory.
|
||||
//
|
||||
// The context is cached per (buffer,size) because a plugin repaints constantly and both
|
||||
// objects would otherwise leak once per frame.
|
||||
static void*
|
||||
JihadPiranhaContextFor(unsigned char* aData, int aWidth, int aHeight, int aStride)
|
||||
{
|
||||
// PGSurface::wrap() has no stride parameter, so it can only describe tightly packed
|
||||
// rows. Refuse rather than hand Piranha a buffer it would misread into a sheared image.
|
||||
if (!aData || aWidth <= 0 || aHeight <= 0 || aStride != aWidth * 4) {
|
||||
static bool sWarnedStride = false;
|
||||
if (!sWarnedStride) {
|
||||
sWarnedStride = true;
|
||||
fprintf(stderr, "[jihad-npapi-child] piranha: stride %d != %d*4, not wrapping\n",
|
||||
aStride, aWidth);
|
||||
fflush(stderr);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
typedef void* (*PGSurfaceWrapFn)(unsigned, unsigned, const unsigned char*, bool);
|
||||
typedef void* (*PGContextCreateFn)(void);
|
||||
typedef void (*PGContextSetSurfaceFn)(void*, void*);
|
||||
|
||||
static bool sTried = false;
|
||||
static PGSurfaceWrapFn sWrap = nullptr;
|
||||
static PGContextCreateFn sCreate = nullptr;
|
||||
static PGContextSetSurfaceFn sSetSurface = nullptr;
|
||||
|
||||
if (!sTried) {
|
||||
sTried = true;
|
||||
// g_thread_init() MUST run before libWebKitLuna loads or its constructors abort with
|
||||
// "GLib-ERROR **: The thread system is not yet initialized". It lives in
|
||||
// libgthread-2.0.so.0, NOT libglib-2.0.so.0. Measured, not guessed.
|
||||
if (void* gthread = dlopen("libgthread-2.0.so.0", RTLD_NOW | RTLD_GLOBAL)) {
|
||||
typedef void (*GThreadInitFn)(void*);
|
||||
if (GThreadInitFn init = (GThreadInitFn)dlsym(gthread, "g_thread_init")) {
|
||||
typedef int (*GThreadGetInitFn)(void);
|
||||
GThreadGetInitFn got =
|
||||
(GThreadGetInitFn)dlsym(gthread, "g_thread_get_initialized");
|
||||
if (!got || !got()) {
|
||||
init(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
void* wk = dlopen("libWebKitLuna.so", RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!wk) {
|
||||
fprintf(stderr, "[jihad-npapi-child] piranha: no libWebKitLuna (%s)\n", dlerror());
|
||||
fflush(stderr);
|
||||
} else {
|
||||
sWrap = (PGSurfaceWrapFn)dlsym(wk, "_ZN9PGSurface4wrapEjjPKhb");
|
||||
sCreate = (PGContextCreateFn)dlsym(wk, "_ZN9PGContext6createEv");
|
||||
sSetSurface = (PGContextSetSurfaceFn)dlsym(wk, "_ZN9PGContext10setSurfaceEP9PGSurface");
|
||||
fprintf(stderr, "[jihad-npapi-child] piranha syms wrap=%p create=%p setSurface=%p\n",
|
||||
(void*)sWrap, (void*)sCreate, (void*)sSetSurface);
|
||||
fflush(stderr);
|
||||
}
|
||||
}
|
||||
if (!sWrap || !sCreate || !sSetSurface) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// A SET of cached contexts, not one. Palm got away with a single context because its
|
||||
// buffer was one process-global screen-sized allocation that never moved; here the child
|
||||
// rotates between a front and a back gfxSharedImageSurface, so a one-entry cache keyed on
|
||||
// the buffer address misses on EVERY frame. That was invisible while the plugin was only
|
||||
// painted twice, and turned into a PGSurface + PGContext leaked per frame the moment the
|
||||
// host started driving repaints at 15Hz.
|
||||
//
|
||||
// Four entries: two buffers is the observed case, and a small fixed array keeps this
|
||||
// allocation-free and bounded even if the surface pool grows. On overflow the oldest entry
|
||||
// is dropped — leaking that one context rather than freeing an object the plugin may still
|
||||
// be holding, which is the safer of the two mistakes and is bounded by the buffer count.
|
||||
struct CacheEntry { unsigned char* data; int w, h; void* ctx; };
|
||||
static CacheEntry sCache[4] = {};
|
||||
static unsigned sCacheNext = 0;
|
||||
|
||||
for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sCache); ++i) {
|
||||
if (sCache[i].ctx && sCache[i].data == aData &&
|
||||
sCache[i].w == aWidth && sCache[i].h == aHeight) {
|
||||
return sCache[i].ctx;
|
||||
}
|
||||
}
|
||||
|
||||
void* surf = sWrap((unsigned)aWidth, (unsigned)aHeight, aData, true);
|
||||
void* ctx = surf ? sCreate() : nullptr;
|
||||
if (ctx) {
|
||||
sSetSurface(ctx, surf);
|
||||
}
|
||||
// Rate-limited: this is now a cache MISS log, and a miss storm is exactly the symptom
|
||||
// worth seeing — but only the first few, or it floods the daemon log at frame rate.
|
||||
static int sLoggedCtx = 0;
|
||||
if (sLoggedCtx < 8) {
|
||||
sLoggedCtx++;
|
||||
fprintf(stderr, "[jihad-npapi-child] piranha ctx %dx%d buf=%p surface=%p context=%p\n",
|
||||
aWidth, aHeight, aData, surf, ctx);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
CacheEntry& slot = sCache[sCacheNext % MOZ_ARRAY_LENGTH(sCache)];
|
||||
sCacheNext++;
|
||||
slot.data = aData; slot.w = aWidth; slot.h = aHeight; slot.ctx = ctx;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void
|
||||
PluginInstanceChild::PaintRectToPlatformSurface(const nsIntRect& aRect,
|
||||
gfxASurface* aSurface)
|
||||
@@ -4454,187 +3799,7 @@ PluginInstanceChild::PaintRectToPlatformSurface(const nsIntRect& aRect,
|
||||
::IntersectClipRect((HDC) mWindow.window, rect.left, rect.top, rect.right, rect.bottom);
|
||||
mPluginIface->event(&mData, reinterpret_cast<void*>(&paintEvent));
|
||||
#else
|
||||
// No platform surface to paint onto.
|
||||
//
|
||||
// This is the CLASSIC windowless paint path, and on Unix it is X11 and nothing else: the
|
||||
// browser hands the plugin a Drawable and sends it an XGraphicsExposeEvent. A build with
|
||||
// no X (a headless toolkit) has nothing it can hand over, and NPAPI defines no
|
||||
// alternative for Unix — a plugin that wants to draw without X has to opt into a bitmap
|
||||
// drawing model (NPDrawingModelAsyncBitmapSurface), which is a different path entirely
|
||||
// and is fully supported here.
|
||||
//
|
||||
// Aborting is wrong for an embedder: the plugin process dies, the parent respawns it,
|
||||
// and the whole cycle repeats for every paint, so one plugin that cannot draw becomes an
|
||||
// endless crash loop. Warn once and leave the surface untouched instead — the plugin
|
||||
// stays loaded and scriptable and simply shows nothing, which is a bounded failure the
|
||||
// embedder can report.
|
||||
// fprintf, not NS_WARNING: NS_WARNING compiles to nothing in a release build, and this is
|
||||
// the ONLY signal distinguishing "the plugin drew nothing" from "the plugin was never
|
||||
// asked to draw" — a distinction that is invisible on screen (both are a blank box).
|
||||
// WEBOS RASTER DRAW EVENT (Jihad, addons R7).
|
||||
//
|
||||
// webOS plugins do not use the X11 protocol at all — they are painted by an
|
||||
// npPalmDrawEvent, and NpPalmDrawEvent carries BOTH a raster destination (dstBuffer +
|
||||
// dstRowBytes, "only valid in API version 1.0") and a Piranha PGContext
|
||||
// ("must be used to draw in API version 2.0"). See Palm's own npapi.h, XP_WEBOS block.
|
||||
//
|
||||
// The device's Flash answers npPalmUseGraphicsContext = true, which per that header means
|
||||
// the HOST may then pass a null raster — it is a statement about host behaviour, NOT a
|
||||
// promise that the plugin refuses a non-null dstBuffer. There is no API-version
|
||||
// negotiation anywhere in the contract, so the only way to find out whether a plugin
|
||||
// honours the raster path is to hand it one and look. That is what this does.
|
||||
//
|
||||
// The surface here is already an ordinary shared-memory image (gfxSharedImageSurface,
|
||||
// created a few hundred lines up), living in this process, whose pixels the parent
|
||||
// already composites — so the raster the plugin needs costs nothing to produce.
|
||||
if (aSurface->GetType() == gfxSurfaceType::Image) {
|
||||
gfxImageSurface* img = static_cast<gfxImageSurface*>(aSurface);
|
||||
uint8_t* data = img->Data();
|
||||
int32_t stride = img->Stride();
|
||||
|
||||
if (data && stride > 0) {
|
||||
// NPEvent is Palm's own event union on this platform (npapi.h); the offsets it
|
||||
// depends on are asserted at the top of this file against libWebKitLuna.
|
||||
// THE DRAW ALWAYS COVERS THE WHOLE SURFACE, whatever sub-rect was requested.
|
||||
//
|
||||
// Two things forced this. The PGContext below is built for the FULL surface
|
||||
// (PGSurface::wrap takes width/height and assumes tightly packed rows), so pairing
|
||||
// it with a dstBuffer offset into the middle of that surface makes the plugin's
|
||||
// blit start partway in and run off the end — measured as `FAULT sig=7` (SIGBUS)
|
||||
// in the plugin child the moment the host began sending partial invalidations.
|
||||
// And Palm's own host never sent a partial rect either: PluginView::paint passes
|
||||
// the plugin's whole frame rect, which is why a 2.0 plugin is entitled to assume
|
||||
// the two agree. Redrawing the whole plugin box costs the plugin nothing it was
|
||||
// not already doing every frame.
|
||||
nsIntRect drawRect(0, 0, img->Width(), img->Height());
|
||||
NPEvent ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.eventType = npPalmDrawEvent;
|
||||
// "points to top-left of area to paint"
|
||||
ev.data.drawEvent.dstBuffer = data;
|
||||
ev.data.drawEvent.dstRowBytes = (uint32_t)stride;
|
||||
ev.data.drawEvent.srcLeft = drawRect.x;
|
||||
ev.data.drawEvent.srcTop = drawRect.y;
|
||||
ev.data.drawEvent.srcRight = drawRect.XMost();
|
||||
ev.data.drawEvent.srcBottom = drawRect.YMost();
|
||||
ev.data.drawEvent.dstLeft = ev.data.drawEvent.srcLeft;
|
||||
ev.data.drawEvent.dstTop = ev.data.drawEvent.srcTop;
|
||||
ev.data.drawEvent.dstRight = ev.data.drawEvent.srcRight;
|
||||
ev.data.drawEvent.dstBottom = ev.data.drawEvent.srcBottom;
|
||||
// API 2.0: a real Piranha PGContext bound to the very same pixels.
|
||||
//
|
||||
// The device's Flash answers npPalmUseGraphicsContext = true, and although it
|
||||
// accepts a raster draw event (returns handled=1) it writes nothing into the
|
||||
// buffer — so "handled" evidently means "consumed", not "drew". This gives it
|
||||
// what it actually asked for. Proven possible by
|
||||
// render/goanna/test/piranha_offscreen_probe.c: a PGContext can be created over
|
||||
// wrapped memory in an ordinary process with no display and no compositor.
|
||||
//
|
||||
// PGSurface::wrap() takes no stride, so it assumes tightly-packed w*4 rows. That
|
||||
// holds here (a 320-wide surface has stride 1280) but is asserted rather than
|
||||
// assumed, because a padded stride would silently shear the image.
|
||||
ev.data.drawEvent.graphicsContext = JihadPiranhaContextFor(data, img->Width(),
|
||||
img->Height(), stride);
|
||||
// BOTH fields stay populated, including for a plugin that answered
|
||||
// npPalmUseGraphicsContext = true.
|
||||
//
|
||||
// npapi.h says the raster pointer "is then passed in as null" for such plugins, and
|
||||
// this code used to believe it. The host that actually shipped does not: PluginView
|
||||
// ::paint (libWebKitLuna 0x4e7c70 / 0x4e7c5c) stores a live dstBuffer and a live
|
||||
// dstRowBytes into the event on the SAME branch that stores the PGContext — the
|
||||
// graphics-context case is precisely the one where it computes
|
||||
// dstBuffer = <shared buffer> + (y*screenWidth + x)*4 and
|
||||
// dstRowBytes = screenWidth*4 (0x4e7e04-0x4e7e18). The header comment describes
|
||||
// what a 2.0 plugin is expected to USE, not what the host is allowed to omit, and
|
||||
// handing Flash a null raster is a difference from the reference host that costs
|
||||
// nothing to remove.
|
||||
|
||||
// Let the plugin's own scheduled work run before we ask it to draw, so the
|
||||
// frame it paints is current rather than whatever it had at instantiation.
|
||||
JihadPumpGlib(50);
|
||||
|
||||
int16_t handled = mPluginIface->event
|
||||
? mPluginIface->event(&mData, reinterpret_cast<void*>(&ev)) : -1;
|
||||
gJihadPalmDrawEvents++;
|
||||
|
||||
static int sLogged = 0;
|
||||
// FRAME-RATE READOUT. A static SWF and a 30fps game are indistinguishable from
|
||||
// a per-event log (capped) or a screenshot (one instant), and the difference is
|
||||
// the whole question for animated content. One line every 2s costs nothing and
|
||||
// is the only way to see the plugin's real cadence in the field.
|
||||
{
|
||||
static uint32_t sDraws = 0;
|
||||
static uint32_t sLastReport = 0;
|
||||
sDraws++;
|
||||
uint32_t nowMs = uint32_t(PR_IntervalToMilliseconds(PR_IntervalNow()));
|
||||
if (!sLastReport) {
|
||||
sLastReport = nowMs;
|
||||
} else if (nowMs - sLastReport >= 2000) {
|
||||
fprintf(stderr, "[jihad-npapi-child] palm draw: %u in %ums (%.1f fps)\n",
|
||||
sDraws, nowMs - sLastReport,
|
||||
sDraws * 1000.0 / double(nowMs - sLastReport));
|
||||
// WHICH SITE ASKED FOR THOSE DRAWS. These are requests to draw, not draws:
|
||||
// AsyncShowPluginFrame coalesces while an invalidate task is already pending,
|
||||
// so the sum is an UPPER bound on sDraws and the interesting number is the
|
||||
// SHARE each site holds, not the total. Printed on the same 2 s boundary so a
|
||||
// reader can line them up without correlating timestamps.
|
||||
{
|
||||
char buf[192];
|
||||
size_t n = 0;
|
||||
buf[0] = '\0';
|
||||
for (int i = 0; i < kJihadShowSites; ++i) {
|
||||
// Clamp rather than trust the running total: snprintf returns the
|
||||
// length it WANTED, so an overflow would make the next call pass a
|
||||
// negative size as size_t and write off the end.
|
||||
if (n < sizeof(buf) - 1) {
|
||||
int w = snprintf(buf + n, sizeof(buf) - n, " %s=%u",
|
||||
kJihadShowNames[i], gJihadShowFrom[i]);
|
||||
if (w > 0) {
|
||||
n += size_t(w);
|
||||
if (n >= sizeof(buf)) n = sizeof(buf) - 1;
|
||||
}
|
||||
}
|
||||
gJihadShowFrom[i] = 0;
|
||||
}
|
||||
fprintf(stderr, "[jihad-npapi-child] show req:%s\n", buf);
|
||||
}
|
||||
fflush(stderr);
|
||||
sDraws = 0;
|
||||
sLastReport = nowMs;
|
||||
}
|
||||
}
|
||||
if (sLogged < 3) {
|
||||
sLogged++;
|
||||
// handled= is reported but means nothing on its own: Flash's NPP_HandleEvent
|
||||
// sets its return to 1 (libflashplayer.so 0x45274) BEFORE calling the draw
|
||||
// handler and returns it unconditionally, so handled=1 is emitted even when
|
||||
// the handler bails without touching the surface. Only pixels are evidence.
|
||||
fprintf(stderr, "[jihad-npapi-child] npPalmDrawEvent %s %d,%d %dx%d "
|
||||
"stride=%d ctx=%p handled=%d\n",
|
||||
ev.data.drawEvent.graphicsContext ? "pgcontext" : "raster",
|
||||
drawRect.x, drawRect.y, drawRect.width, drawRect.height,
|
||||
(int)stride, ev.data.drawEvent.graphicsContext, (int)handled);
|
||||
fflush(stderr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing we can paint onto at all.
|
||||
//
|
||||
// Aborting is wrong for an embedder: the plugin process dies, the parent respawns it,
|
||||
// and the whole cycle repeats for every paint, so one plugin that cannot draw becomes an
|
||||
// endless crash loop. Warn once and leave the surface untouched instead.
|
||||
// fprintf, not NS_WARNING: NS_WARNING compiles to nothing in a release build, and this is
|
||||
// the ONLY signal distinguishing "the plugin drew nothing" from "the plugin was never
|
||||
// asked to draw" — a distinction that is invisible on screen (both are a blank box).
|
||||
static bool sWarned = false;
|
||||
if (!sWarned) {
|
||||
sWarned = true;
|
||||
fprintf(stderr, "[jihad-npapi-child] no paintable surface for this windowless "
|
||||
"plugin (type=%d); it will NOT paint.\n", (int)aSurface->GetType());
|
||||
fflush(stderr);
|
||||
}
|
||||
NS_RUNTIMEABORT("Surface type not implemented.");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -5099,7 +4264,6 @@ PluginInstanceChild::InvalidateRectDelayed(void)
|
||||
}
|
||||
|
||||
if (!ShowPluginFrame()) {
|
||||
gJihadShowFrom[kJihadShowRetry]++;
|
||||
AsyncShowPluginFrame();
|
||||
}
|
||||
}
|
||||
@@ -5153,8 +4317,6 @@ PluginInstanceChild::InvalidateRect(NPRect* aInvalidRect)
|
||||
mAccumulatedInvalidRect.UnionRect(r, mAccumulatedInvalidRect);
|
||||
// If we are able to paint and invalidate sent, then reset
|
||||
// accumulated rectangle
|
||||
gJihadShowFrom[sJihadInHostRepaint ? kJihadShowInvalidate
|
||||
: kJihadShowPluginSelf]++;
|
||||
AsyncShowPluginFrame();
|
||||
return;
|
||||
}
|
||||
@@ -5198,7 +4360,6 @@ PluginInstanceChild::RecvUpdateBackground(const SurfaceDescriptor& aBackground,
|
||||
IntSize bgSize = mBackground->GetSize();
|
||||
mAccumulatedInvalidRect.UnionRect(mAccumulatedInvalidRect,
|
||||
nsIntRect(0, 0, bgSize.width, bgSize.height));
|
||||
gJihadShowFrom[kJihadShowBgUpdate]++;
|
||||
AsyncShowPluginFrame();
|
||||
return true;
|
||||
}
|
||||
@@ -5208,7 +4369,6 @@ PluginInstanceChild::RecvUpdateBackground(const SurfaceDescriptor& aBackground,
|
||||
|
||||
// This must be asynchronous, because we may be nested within RPC messages
|
||||
// which do not expect to receiving paint events.
|
||||
gJihadShowFrom[kJihadShowBgUpdateTail]++;
|
||||
AsyncShowPluginFrame();
|
||||
|
||||
return true;
|
||||
@@ -5241,7 +4401,6 @@ PluginInstanceChild::RecvPPluginBackgroundDestroyerConstructor(
|
||||
// NB: we don't have to XSync here because only ShowPluginFrame()
|
||||
// uses mBackground, and it always XSyncs after finishing.
|
||||
mBackground = nullptr;
|
||||
gJihadShowFrom[kJihadShowBgDestroy]++;
|
||||
AsyncShowPluginFrame();
|
||||
}
|
||||
|
||||
@@ -5520,7 +4679,7 @@ PluginInstanceChild::Destroy()
|
||||
|
||||
mPendingAsyncCalls.Clear();
|
||||
|
||||
#ifdef MOZ_ENABLE_NPAPI_GTK2
|
||||
#if defined(MOZ_WIDGET_GTK) && defined(MOZ_ENABLE_NPAPI_GTK2)
|
||||
if (mWindow.type == NPWindowTypeWindow && !mXEmbed) {
|
||||
xt_client_xloop_destroy();
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
#ifdef MOZ_ENABLE_NPAPI_GTK2
|
||||
#if defined(MOZ_WIDGET_GTK) && defined(MOZ_ENABLE_NPAPI_GTK2)
|
||||
#include "gtk2xtbin.h"
|
||||
#endif
|
||||
|
||||
@@ -141,10 +141,6 @@ protected:
|
||||
virtual bool
|
||||
RecvContentsScaleFactorChanged(const double& aContentsScaleFactor) override;
|
||||
|
||||
// Jihad: the host is driving the frame clock — draw one now. See PPluginInstance.ipdl.
|
||||
virtual bool
|
||||
RecvJihadRequestFrame() override;
|
||||
|
||||
virtual bool
|
||||
AnswerNPP_Destroy(NPError* result) override;
|
||||
|
||||
@@ -645,22 +641,6 @@ private:
|
||||
// and p->getvalue return always false
|
||||
bool mIsTransparent;
|
||||
|
||||
// Jihad R7 (webOS): npPalmGainFocusEvent is a once-per-instance startup signal, but the
|
||||
// setwindow that carries it runs again on every resize and scroll.
|
||||
bool mJihadSentPalmGainFocus;
|
||||
|
||||
// Jihad R7 (webOS): cap on the input-delivery breadcrumb in AnswerNPP_HandleEvent. A
|
||||
// pen-move stream is unbounded; the daemon log is the only field-debug channel there is.
|
||||
uint32_t mJihadInputEventsLogged;
|
||||
|
||||
public:
|
||||
// Jihad R7 (webOS): repaint this instance from the host's own clock. See
|
||||
// JihadGlibPumpTask::Run — a webOS plugin is not obliged to ask for repaints, because the
|
||||
// host it was written for painted it as part of every page paint.
|
||||
void JihadPalmRepaintTick();
|
||||
|
||||
private:
|
||||
|
||||
// Surface type optimized of parent process
|
||||
gfxSurfaceType mSurfaceType;
|
||||
|
||||
|
||||
@@ -115,16 +115,6 @@ PluginInstanceParent::LookupPluginInstanceByID(uintptr_t aId)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Jihad: every live instance, so a C entry point can reach one. jihad_plugin_frame_seq needs no
|
||||
// instance because it only counts; a frame REQUEST has to be sent on an actual actor.
|
||||
//
|
||||
// A plain file-static array maintained in the ctor/dtor below, not a hashtable: this is at most a
|
||||
// couple of entries on a single-card embedding, it is only ever touched on the main thread (the
|
||||
// daemon calls the export from inside its own tick, on that same thread), and the alternative —
|
||||
// walking nsPluginHost's nsNPAPIPluginInstance list the way jihad_plugin_palm_spotlight does —
|
||||
// would still have to get from there to the PluginInstanceParent actor to call Send on it.
|
||||
static nsTArray<PluginInstanceParent*>* gJihadLiveInstances = nullptr;
|
||||
|
||||
PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent,
|
||||
NPP npp,
|
||||
const nsCString& aMimeType,
|
||||
@@ -155,24 +145,10 @@ PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent,
|
||||
sPluginInstanceList = new nsClassHashtable<nsVoidPtrHashKey, PluginInstanceParent>();
|
||||
}
|
||||
#endif
|
||||
// Jihad: join the live list that jihad_plugin_request_frame walks. Leaked on purpose — it
|
||||
// outlives every instance and there is no shutdown hook here that is guaranteed to run
|
||||
// after the last dtor.
|
||||
if (!gJihadLiveInstances) {
|
||||
gJihadLiveInstances = new nsTArray<PluginInstanceParent*>();
|
||||
}
|
||||
gJihadLiveInstances->AppendElement(this);
|
||||
}
|
||||
|
||||
PluginInstanceParent::~PluginInstanceParent()
|
||||
{
|
||||
// Jihad: leave the live list FIRST. Anything below can run script or spin the loop, and a
|
||||
// frame request delivered to a half-destroyed actor is a use-after-free rather than a
|
||||
// dropped frame.
|
||||
if (gJihadLiveInstances) {
|
||||
gJihadLiveInstances->RemoveElement(this);
|
||||
}
|
||||
|
||||
if (mNPP)
|
||||
mNPP->pdata = nullptr;
|
||||
|
||||
@@ -293,8 +269,6 @@ PluginInstanceParent::AnswerNPN_GetValue_NPNVnetscapeWindow(NativeWindowHandle*
|
||||
XID id;
|
||||
#elif defined(XP_DARWIN)
|
||||
intptr_t id;
|
||||
#elif defined(MOZ_WIDGET_HEADLESS)
|
||||
intptr_t id; // Jihad headless: no native window id
|
||||
#else
|
||||
#warning Implement me
|
||||
#endif
|
||||
@@ -795,51 +769,6 @@ PluginInstanceParent::RecvShowDirectBitmap(Shmem&& buffer,
|
||||
return false;
|
||||
}
|
||||
|
||||
// NO COMPOSITOR PROCESS: composite the plugin's bitmap directly.
|
||||
//
|
||||
// Everything below this point routes the plugin's pixels through a TextureClient to a
|
||||
// compositor, and that requires ImageBridgeChild — which only exists with off-main-thread
|
||||
// composition. This embedder deliberately runs OMTC off (the offscreen renderer uses the
|
||||
// in-process BasicLayerManager and hands finished frames to the browser adapter itself),
|
||||
// so ImageBridgeChild::GetSingleton() is null and TextureClientRecycleAllocator is built
|
||||
// around a null KnowsCompositor. The first plugin paint then dereferenced it and took the
|
||||
// whole daemon down with it — upstream never sees this because the DXGI path a few
|
||||
// hundred lines up checks the forwarder and the direct-bitmap path does not.
|
||||
//
|
||||
// A BasicLayerManager does not need a TextureClient at all: it draws an ImageLayer from
|
||||
// the image's own SourceSurface. So wrap the pixels in a plain SourceSurfaceImage and
|
||||
// hand that to the container.
|
||||
//
|
||||
// The pixels are COPIED, not wrapped. `source` above wraps the shmem the plugin drew
|
||||
// into, and the plugin owns that buffer and may draw the next frame into it as soon as
|
||||
// this call returns; the texture path's UpdateFromSurface copies for the same reason.
|
||||
if (!ImageBridgeChild::GetSingleton()) {
|
||||
RefPtr<gfx::DataSourceSurface> copy =
|
||||
gfx::Factory::CreateDataSourceSurface(size, format);
|
||||
if (!copy) {
|
||||
NS_WARNING("Could not allocate a surface for plugin bitmap!");
|
||||
return false;
|
||||
}
|
||||
{
|
||||
gfx::DataSourceSurface::ScopedMap srcMap(source, gfx::DataSourceSurface::READ);
|
||||
gfx::DataSourceSurface::ScopedMap dstMap(copy, gfx::DataSourceSurface::WRITE);
|
||||
if (!srcMap.IsMapped() || !dstMap.IsMapped()) {
|
||||
return false;
|
||||
}
|
||||
const int32_t bpp = BytesPerPixel(format);
|
||||
const int32_t rowBytes = size.width * bpp;
|
||||
for (int32_t y = 0; y < size.height; y++) {
|
||||
memcpy(dstMap.GetData() + (ptrdiff_t)y * dstMap.GetStride(),
|
||||
srcMap.GetData() + (ptrdiff_t)y * srcMap.GetStride(),
|
||||
rowBytes);
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(size, copy);
|
||||
SetCurrentImage(image);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allocate a texture for the compositor.
|
||||
RefPtr<TextureClientRecycleAllocator> allocator = mParent->EnsureTextureAllocatorForDirectBitmap();
|
||||
RefPtr<TextureClient> texture = allocator->CreateOrRecycle(
|
||||
@@ -935,55 +864,6 @@ PluginInstanceParent::RecvShowDirectDXGISurface(const WindowsHandle& handle,
|
||||
}
|
||||
#endif // MOZ_ENABLE_NPAPI
|
||||
|
||||
// Jihad: monotonic count of plugin frames that have reached the parent. Process-wide rather
|
||||
// than per-instance, matching how the daemon consumes it — the embedding is single-page and the
|
||||
// question it answers is "has ANY plugin produced a new frame since I last published". A plain
|
||||
// uint32_t incremented and read on the same (main) thread; the daemon reads it from the same
|
||||
// thread inside its tick, so no atomics are needed and none are used elsewhere in this bridge.
|
||||
static uint32_t gJihadPluginFrameSeq = 0;
|
||||
|
||||
extern "C" {
|
||||
// Weak-linked from the daemon (render/goanna/GoannaRenderPage.cpp), the same way
|
||||
// jihad_offscreen_take_dirty is: an older libxul simply does not export it, and the daemon then
|
||||
// falls back to its dirty-flag behaviour instead of failing to load.
|
||||
MOZ_EXPORT uint32_t jihad_plugin_frame_seq(void)
|
||||
{
|
||||
return gJihadPluginFrameSeq;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// Ask every live plugin for one frame. Returns how many were asked, so the daemon can tell "no
|
||||
// plugin" from "delivered" — the same contract as jihad_plugin_palm_spotlight.
|
||||
//
|
||||
// WEAK on the daemon side: a libxul without this symbol keeps the child's own timer as the frame
|
||||
// clock (patch 0026 behaviour), which is slower to settle but never wrong.
|
||||
MOZ_EXPORT int jihad_plugin_request_frame(void)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_NPAPI
|
||||
if (!gJihadLiveInstances) {
|
||||
return 0;
|
||||
}
|
||||
// Copy first: SendJihadRequestFrame can fail and tear the actor down, which would mutate the
|
||||
// array underneath the loop. Same reasoning as the instance-list copy in
|
||||
// jihad_plugin_palm_spotlight (nsPluginHost.cpp).
|
||||
nsTArray<PluginInstanceParent*> snap(*gJihadLiveInstances);
|
||||
int asked = 0;
|
||||
for (uint32_t i = 0; i < snap.Length(); ++i) {
|
||||
if (!gJihadLiveInstances->Contains(snap[i])) {
|
||||
continue; // torn down by an earlier Send in this same loop
|
||||
}
|
||||
if (snap[i]->SendJihadRequestFrame()) {
|
||||
asked++;
|
||||
}
|
||||
}
|
||||
return asked;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
PluginInstanceParent::RecvShow(const NPRect& updatedRect,
|
||||
const SurfaceDescriptor& newSurface,
|
||||
@@ -997,27 +877,6 @@ PluginInstanceParent::RecvShow(const NPRect& updatedRect,
|
||||
|
||||
MOZ_ASSERT(!IsUsingDirectDrawing());
|
||||
|
||||
// FRAME-RATE READOUT, parent side. Paired with the child's `palm draw:` line: the child
|
||||
// reports how often the plugin was ASKED to draw, this reports how many of those frames
|
||||
// actually crossed the process boundary. A gap between the two numbers is the difference
|
||||
// between "the plugin is slow" and "we are dropping its frames", which are opposite bugs.
|
||||
{
|
||||
static uint32_t sShows = 0;
|
||||
static uint32_t sLastReport = 0;
|
||||
sShows++;
|
||||
uint32_t nowMs = uint32_t(PR_IntervalToMilliseconds(PR_IntervalNow()));
|
||||
if (!sLastReport) {
|
||||
sLastReport = nowMs;
|
||||
} else if (nowMs - sLastReport >= 2000) {
|
||||
fprintf(stderr, "[jihad-npapi] RecvShow: %u in %ums (%.1f fps)\n",
|
||||
sShows, nowMs - sLastReport,
|
||||
sShows * 1000.0 / double(nowMs - sLastReport));
|
||||
fflush(stderr);
|
||||
sShows = 0;
|
||||
sLastReport = nowMs;
|
||||
}
|
||||
}
|
||||
|
||||
// XXXjwatt rewrite to use Moz2D
|
||||
RefPtr<gfxASurface> surface;
|
||||
if (newSurface.type() == SurfaceDescriptor::TShmem) {
|
||||
@@ -1100,75 +959,14 @@ PluginInstanceParent::RecvShow(const NPRect& updatedRect,
|
||||
updatedRect.bottom - updatedRect.top);
|
||||
surface->MarkDirty(ur);
|
||||
|
||||
// JIHAD (2026-08-09): route the legacy windowless show through the SAME
|
||||
// representation the direct-bitmap path uses, for the SAME reasons.
|
||||
//
|
||||
// Background: the webOS npPalmDrawEvent path (PaintRectToPlatformSurface) makes Flash
|
||||
// blit its stage into mCurrentSurface — measured full-magenta in the child — and ships
|
||||
// it here via SendShow. The control plugin's pixels reach the screen through
|
||||
// RecvShowDirectBitmap; Flash's did not, although both feed the one mImageContainer the
|
||||
// BasicLayerManager reads. Two concrete differences made the legacy path invisible:
|
||||
// 1. It WRAPPED the shmem (GetSourceSurfaceForSurface(nullptr,…)) instead of copying.
|
||||
// The child double-buffers and SwapSurfaces() immediately after SendShow returns,
|
||||
// so a wrapped front buffer is the very buffer the child repaints next — the image
|
||||
// the layer later samples is racing the child. RecvShowDirectBitmap copies for
|
||||
// exactly this reason ("the plugin owns that buffer and may draw the next frame
|
||||
// into it as soon as this call returns").
|
||||
// 2. It stamped no frameID. SetCurrentImage() on the working path assigns a monotonic
|
||||
// ++mFrameID; the ImageContainer/ImageLayer treats a repeated frameID as "no new
|
||||
// frame". A fixed 0 can be coalesced away.
|
||||
// Copying into a fresh DataSourceSurface and stamping a monotonic frameID makes this
|
||||
// path identical in substance to the one already proven to composite here.
|
||||
IntSize ssize = surface->GetSize();
|
||||
RefPtr<Image> image;
|
||||
RefPtr<gfxImageSurface> imgSurf = surface->GetAsImageSurface();
|
||||
if (imgSurf) {
|
||||
// Always present the copy as B8G8R8A8, and force the alpha channel opaque when the
|
||||
// source carries no alpha (RGB24 / B8G8R8X8). This is Bug 1196927 in this file's own
|
||||
// words (see PluginInstanceChild::PaintRectToSurface): Cairo/Skia leave the X byte of
|
||||
// a BGRX surface zeroed, and a BasicImageLayer then composites those pixels multiplied
|
||||
// by alpha 0 — i.e. fully transparent. That is exactly why Flash's magenta stage
|
||||
// arrived here (RecvShow logged px0=0xffff00ff) yet BasicImageLayer::Paint drew
|
||||
// nothing visible, while the control plugin — which ships B8G8R8A8 — composited fine
|
||||
// through the identical layer path. webOS windowless plugins are opaque
|
||||
// (wmode=opaque forces the windowless instance in the first place), so forcing alpha
|
||||
// opaque is correct here.
|
||||
const SurfaceFormat srcFmt = surface->GetSurfaceFormat();
|
||||
const bool srcHasAlpha =
|
||||
srcFmt != SurfaceFormat::B8G8R8X8 &&
|
||||
srcFmt != SurfaceFormat::R8G8B8X8;
|
||||
RefPtr<gfx::DataSourceSurface> copy =
|
||||
gfx::Factory::CreateDataSourceSurface(ssize, SurfaceFormat::B8G8R8A8);
|
||||
if (copy) {
|
||||
gfx::DataSourceSurface::ScopedMap dstMap(copy, gfx::DataSourceSurface::WRITE);
|
||||
if (dstMap.IsMapped()) {
|
||||
const int32_t rowBytes = ssize.width * 4;
|
||||
for (int32_t y = 0; y < ssize.height; y++) {
|
||||
uint8_t* dstRow = dstMap.GetData() + (ptrdiff_t)y * dstMap.GetStride();
|
||||
memcpy(dstRow, imgSurf->Data() + (ptrdiff_t)y * imgSurf->Stride(),
|
||||
rowBytes);
|
||||
if (!srcHasAlpha) {
|
||||
for (int32_t x = 0; x < ssize.width; x++) {
|
||||
dstRow[x * 4 + 3] = 0xff; // BGRA: byte 3 is alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
image = new SourceSurfaceImage(ssize, copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!image) {
|
||||
// Fallback to the original wrap if the copy path could not be taken.
|
||||
bool isPlugin = true;
|
||||
RefPtr<gfx::SourceSurface> sourceSurface =
|
||||
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface, isPlugin);
|
||||
image = new SourceSurfaceImage(ssize, sourceSurface);
|
||||
}
|
||||
bool isPlugin = true;
|
||||
RefPtr<gfx::SourceSurface> sourceSurface =
|
||||
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface, isPlugin);
|
||||
RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), sourceSurface);
|
||||
|
||||
ImageContainer::NonOwningImage holder(image);
|
||||
holder.mFrameID = ++mFrameID;
|
||||
AutoTArray<ImageContainer::NonOwningImage,1> imageList;
|
||||
imageList.AppendElement(holder);
|
||||
imageList.AppendElement(
|
||||
ImageContainer::NonOwningImage(image));
|
||||
|
||||
ImageContainer *container = GetImageContainer();
|
||||
container->SetCurrentImages(imageList);
|
||||
@@ -1178,17 +976,6 @@ PluginInstanceParent::RecvShow(const NPRect& updatedRect,
|
||||
}
|
||||
|
||||
mFrontSurface = surface;
|
||||
// Jihad: count DELIVERED plugin frames so the embedder can tell a new one from a repeat.
|
||||
// The daemon's only frame signal is PuppetWidget's sticky dirty boolean, which carries no
|
||||
// identity: the refresh driver and ordinary page damage set the same flag, so "there is
|
||||
// damage" cannot answer "did the plugin draw again". Measured 2026-08-10 on a 30 fps SWF,
|
||||
// that ambiguity is what made both available policies wrong — publishing on every dirty
|
||||
// tick sent ~78 card frames for ~60 plugin frames (duplicates, and 22-44 deferred because
|
||||
// the adapter could not drain them), while pacing on a fixed 33 ms grid sent ~57 for ~68
|
||||
// (dropped frames, so the animation stepped two positions at once). The stock webOS host
|
||||
// never had to choose: it pulls the plugin synchronously inside the page paint, so it is
|
||||
// 1:1 by construction. This counter is what lets the daemon be 1:1 too.
|
||||
++gJihadPluginFrameSeq;
|
||||
RecvNPN_InvalidateRect(updatedRect);
|
||||
|
||||
PLUGIN_LOG_DEBUG((" (RecvShow invalidated for surface %p)",
|
||||
@@ -1514,11 +1301,7 @@ PluginInstanceParent::GetImageContainer()
|
||||
return mImageContainer;
|
||||
}
|
||||
|
||||
if (IsUsingDirectDrawing() && ImageBridgeChild::GetSingleton()) {
|
||||
// ASYNCHRONOUS means "shared with the compositor through ImageBridge". Without an
|
||||
// ImageBridge the async container silently gets no image client and never publishes
|
||||
// anything, so with OMTC off this has to be an ordinary local container — which is
|
||||
// what the BasicLayerManager reads from. See RecvShowDirectBitmap.
|
||||
if (IsUsingDirectDrawing()) {
|
||||
mImageContainer = LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS);
|
||||
} else {
|
||||
mImageContainer = LayerManager::CreateImageContainer();
|
||||
|
||||
@@ -117,8 +117,6 @@ typedef HWND NativeWindowHandle;
|
||||
typedef XID NativeWindowHandle;
|
||||
#elif defined(XP_DARWIN)
|
||||
typedef intptr_t NativeWindowHandle; // never actually used, will always be 0
|
||||
#elif defined(MOZ_WIDGET_HEADLESS)
|
||||
typedef intptr_t NativeWindowHandle; // Jihad headless: no native window handle
|
||||
#else
|
||||
#error Need NativeWindowHandle for this platform
|
||||
#endif
|
||||
|
||||
@@ -60,9 +60,6 @@ using namespace mozilla::ipc;
|
||||
using namespace mozilla::plugins;
|
||||
using namespace mozilla::widget;
|
||||
|
||||
// Defined at the bottom of this file; see the NPN SYMBOL INTERPOSITION block there.
|
||||
namespace mozilla { namespace plugins { void JihadInstallNPNInterposition(); } }
|
||||
|
||||
#if defined(XP_WIN)
|
||||
const wchar_t * kFlashFullscreenClass = L"ShockwaveFlashFullScreen";
|
||||
const wchar_t * kMozillaWindowClass = L"MozillaWindowClass";
|
||||
@@ -235,10 +232,6 @@ PluginModuleChild::InitForChrome(const std::string& aPluginFilename,
|
||||
{
|
||||
NS_ASSERTION(aChannel, "need a channel");
|
||||
|
||||
// Point the exported NPN_* globals at the real implementations before any plugin can be
|
||||
// loaded, so a plugin that resolves them as symbols reaches us rather than libWebKitLuna.
|
||||
JihadInstallNPNInterposition();
|
||||
|
||||
if (!InitGraphics())
|
||||
return false;
|
||||
|
||||
@@ -258,11 +251,7 @@ PluginModuleChild::InitForChrome(const std::string& aPluginFilename,
|
||||
nsPluginFile pluginFile(localFile);
|
||||
|
||||
nsPluginInfo info = nsPluginInfo();
|
||||
nsresult jihadRv = pluginFile.GetPluginInfo(info, &mLibrary);
|
||||
fprintf(stderr, "[jihad-npapi-child] GetPluginInfo rv=0x%x lib=%d\n",
|
||||
(unsigned)jihadRv, (int)!!mLibrary);
|
||||
fflush(stderr);
|
||||
if (NS_FAILED(jihadRv)) {
|
||||
if (NS_FAILED(pluginFile.GetPluginInfo(info, &mLibrary))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1854,8 +1843,6 @@ bool
|
||||
PluginModuleChild::AnswerNP_Initialize(const PluginSettings& aSettings, NPError* rv)
|
||||
{
|
||||
*rv = DoNP_Initialize(aSettings);
|
||||
fprintf(stderr, "[jihad-npapi-child] AnswerNP_Initialize rv=%d\n", (int)*rv);
|
||||
fflush(stderr);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1895,8 +1882,6 @@ PluginModuleChild::DoNP_Initialize(const PluginSettings& aSettings)
|
||||
# error Please implement me for your platform
|
||||
#endif
|
||||
|
||||
fprintf(stderr, "[jihad-npapi-child] DoNP_Initialize plugin returned %d\n", (int)result);
|
||||
fflush(stderr);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2632,31 +2617,3 @@ PluginModuleChild::RecvNPP_SetValue_NPNVaudioDeviceChangeDetails(
|
||||
#endif
|
||||
}
|
||||
#endif // MOZ_ENABLE_NPAPI
|
||||
|
||||
// ── NPN SYMBOL INTERPOSITION (Jihad, addons R7) ──────────────────────────────────────────
|
||||
//
|
||||
// The exported NPN_* globals themselves live in JihadNPNInterpose.cpp (they must, to escape
|
||||
// hidden visibility — see the long comment there). All that is needed here is to point them
|
||||
// at the same implementations the browser function table uses, which is done once the table
|
||||
// exists. Installed from a static initializer-free helper called by the child at startup.
|
||||
extern "C" {
|
||||
extern void (*gJihadNPNInvalidateRect)(void*, void*);
|
||||
extern void* (*gJihadNPNCreateObject)(void*, void*);
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
namespace plugins {
|
||||
|
||||
void JihadInstallNPNInterposition()
|
||||
{
|
||||
const NPNetscapeFuncs& f = PluginModuleChild::sBrowserFuncs;
|
||||
gJihadNPNInvalidateRect = (void(*)(void*, void*))f.invalidaterect;
|
||||
gJihadNPNCreateObject = (void*(*)(void*, void*))f.createobject;
|
||||
fprintf(stderr, "[jihad-npapi-child] NPN interposition installed invalidaterect=%p "
|
||||
"createobject=%p\n",
|
||||
(void*)f.invalidaterect, (void*)f.createobject);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
} // namespace plugins
|
||||
} // namespace mozilla
|
||||
@@ -444,8 +444,6 @@ PluginModuleChromeParent::LoadModule(const char* aFilePath, uint32_t aPluginId,
|
||||
parent->mSubprocess->SetCallRunnableImmediately(!parent->mIsStartingAsync);
|
||||
bool launched = parent->mSubprocess->Launch(Move(onLaunchedRunnable),
|
||||
aPluginTag->mSandboxLevel);
|
||||
fprintf(stderr, "[jihad-npapi] LoadModule path=%s async=%d launched=%d\n",
|
||||
aFilePath, (int)parent->mIsStartingAsync, (int)launched);
|
||||
if (!launched) {
|
||||
// We never reached open
|
||||
parent->mShutdown = true;
|
||||
@@ -457,10 +455,7 @@ PluginModuleChromeParent::LoadModule(const char* aFilePath, uint32_t aPluginId,
|
||||
parent->mIsBlocklisted = NS_FAILED(rv) || blocklistState != 0;
|
||||
if (!parent->mIsStartingAsync) {
|
||||
int32_t launchTimeoutSecs = Preferences::GetInt(kLaunchTimeoutPref, 0);
|
||||
bool connected = parent->mSubprocess->WaitUntilConnected(launchTimeoutSecs * 1000);
|
||||
fprintf(stderr, "[jihad-npapi] LoadModule WaitUntilConnected(%ds) = %d\n",
|
||||
(int)launchTimeoutSecs, (int)connected);
|
||||
if (!connected) {
|
||||
if (!parent->mSubprocess->WaitUntilConnected(launchTimeoutSecs * 1000)) {
|
||||
parent->mShutdown = true;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1139,20 +1134,9 @@ PluginModuleChromeParent::OnHangUIContinue()
|
||||
}
|
||||
#endif // XP_WIN
|
||||
|
||||
// Defined with the rest of the Jihad CPU-boost helpers further down this file.
|
||||
namespace { void JihadCpuBoostReleaseAll(); }
|
||||
|
||||
void
|
||||
PluginModuleParent::ActorDestroy(ActorDestroyReason why)
|
||||
{
|
||||
// Release the CPU boost here as well as in NPP_Destroy, because on this port NPP_Destroy is
|
||||
// NOT the path a plugin usually leaves by: navigating away from a Flash page ends in the
|
||||
// child's own mozalloc_abort, so the instance never gets an orderly destroy and the boost
|
||||
// would leak — device-observed 2026-08-10, tunables still at 40/50000 after navigation with
|
||||
// no release logged. This runs for every ActorDestroyReason, normal and abnormal, which also
|
||||
// covers a plugin crash.
|
||||
JihadCpuBoostReleaseAll();
|
||||
|
||||
switch (why) {
|
||||
case AbnormalShutdown: {
|
||||
mShutdown = true;
|
||||
@@ -1287,172 +1271,6 @@ NP_BEGIN_MACRO \
|
||||
return i->func; \
|
||||
NP_END_MACRO
|
||||
|
||||
// ── Jihad: hold a CPU-frequency boost while a plugin instance is alive ──────────────────────
|
||||
//
|
||||
// WHY. The TouchPad's governor is Palm's `ondemandtcl`, whose stock `up_threshold` is 95 — it
|
||||
// only raises the clock when a single core exceeds 95% load. Flash never gets there: measured on
|
||||
// device, the daemon runs ~26% and plugin-container ~11% of two cores, so a passively-watched
|
||||
// animation sits at the FLOOR. Sampling `scaling_cur_freq` once a second during playback gave
|
||||
// 192000-384000 out of a 1188000 maximum, snapping back to 1188000 the moment the page was
|
||||
// navigated away. The whole system was only 69% busy, so this was never a capacity wall.
|
||||
//
|
||||
// Dropping `up_threshold` to 40 and `sampling_rate` to 50000 pins 1188000 and lifts the card
|
||||
// composite from 20.4-27.5 fps to 30.7-35.2, daemon paints 24.0-28.7 to 31.4-33.0, and the frame
|
||||
// gap MAXIMUM — the number that reads as stutter — from 63-81 ms to 49-56. It also clears the
|
||||
// audio xruns, because MP3 decode at 192 MHz underruns.
|
||||
//
|
||||
// NEVER WRITE `scaling_governor`. Switching away from `ondemandtcl` DEADLOCKS cpufreq in the
|
||||
// kernel: every later reader of any cpufreq node blocks in unkillable D state — the daemon,
|
||||
// `powerlog`, and the restore path itself — so the boost is never released and only a reboot
|
||||
// clears it (and a clean `reboot` will not run, because init waits on those D-state tasks).
|
||||
// Governor TUNABLE writes, which is all this does, are safe and return instantly.
|
||||
//
|
||||
// These are SYSTEM-WIDE tunables, so they are held only while a plugin instance exists and the
|
||||
// ORIGINAL values are read back and restored on teardown. The upstart job restores them again
|
||||
// from `post-stop`, because a daemon that dies holding the boost would otherwise leave the whole
|
||||
// device tuned for battery burn. Both hooks run on the main thread, so the counter needs no lock.
|
||||
namespace {
|
||||
|
||||
const char kJihadOndemandDir[] = "/sys/devices/system/cpu/cpufreq/ondemandtcl";
|
||||
|
||||
// The stock values, used ONLY as the restore fallback when the current value already equals what
|
||||
// we are about to write — which happens when another variant's daemon is holding the boost.
|
||||
// Saving the boosted value as "original" there would make the last release pin the device.
|
||||
const char kJihadStockUpThreshold[] = "95";
|
||||
const char kJihadStockSamplingRate[] = "200000";
|
||||
|
||||
// The instances that actually TOOK a boost, not a bare count. NPP_Destroy also runs for instances
|
||||
// that failed before the boost was taken, and decrementing a count there would release the boost
|
||||
// out from under a plugin that is still playing.
|
||||
nsTArray<NPP> gJihadBoostedInstances;
|
||||
bool gJihadBoostHeld = false;
|
||||
char gJihadSavedUpThreshold[32];
|
||||
char gJihadSavedSamplingRate[32];
|
||||
|
||||
bool
|
||||
JihadReadTunable(const char* aName, char* aOut, size_t aOutLen)
|
||||
{
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "%s/%s", kJihadOndemandDir, aName);
|
||||
FILE* f = fopen(path, "r");
|
||||
if (!f) return false;
|
||||
char* got = fgets(aOut, aOutLen, f);
|
||||
fclose(f);
|
||||
if (!got) return false;
|
||||
// Trim the trailing newline so a value read back can be compared and rewritten verbatim.
|
||||
size_t n = strlen(aOut);
|
||||
while (n > 0 && (aOut[n - 1] == '\n' || aOut[n - 1] == '\r')) aOut[--n] = '\0';
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
bool
|
||||
JihadWriteTunable(const char* aName, const char* aValue)
|
||||
{
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "%s/%s", kJihadOndemandDir, aName);
|
||||
FILE* f = fopen(path, "w");
|
||||
if (!f) return false;
|
||||
bool ok = fputs(aValue, f) >= 0;
|
||||
ok = (fclose(f) == 0) && ok;
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Env-tunable for the same reason the repaint divisor is: the right value is a property of the
|
||||
// device under load, not of the source, and a rebuild per candidate costs a 44 MB push.
|
||||
void
|
||||
JihadCpuBoostTargets(const char** aUpThreshold, const char** aSamplingRate)
|
||||
{
|
||||
static char sUp[32] = "40";
|
||||
static char sRate[32] = "50000";
|
||||
static bool sInit = false;
|
||||
if (!sInit) {
|
||||
sInit = true;
|
||||
const char* e = getenv("JIHAD_PLUGIN_CPU_UP_THRESHOLD");
|
||||
if (e && *e) {
|
||||
int v = atoi(e);
|
||||
if (v >= 5 && v <= 95) snprintf(sUp, sizeof(sUp), "%d", v);
|
||||
}
|
||||
e = getenv("JIHAD_PLUGIN_CPU_SAMPLING_RATE");
|
||||
if (e && *e) {
|
||||
int v = atoi(e);
|
||||
if (v >= 10000 && v <= 1000000) snprintf(sRate, sizeof(sRate), "%d", v);
|
||||
}
|
||||
}
|
||||
*aUpThreshold = sUp;
|
||||
*aSamplingRate = sRate;
|
||||
}
|
||||
|
||||
bool
|
||||
JihadCpuBoostEnabled()
|
||||
{
|
||||
const char* e = getenv("JIHAD_PLUGIN_CPU_BOOST");
|
||||
return !(e && e[0] == '0' && e[1] == '\0');
|
||||
}
|
||||
|
||||
void
|
||||
JihadCpuBoostAcquire(NPP aInstance)
|
||||
{
|
||||
if (!JihadCpuBoostEnabled() || gJihadBoostedInstances.Contains(aInstance)) return;
|
||||
gJihadBoostedInstances.AppendElement(aInstance);
|
||||
if (gJihadBoostedInstances.Length() != 1 || gJihadBoostHeld) return;
|
||||
|
||||
const char* wantUp;
|
||||
const char* wantRate;
|
||||
JihadCpuBoostTargets(&wantUp, &wantRate);
|
||||
|
||||
if (!JihadReadTunable("up_threshold", gJihadSavedUpThreshold,
|
||||
sizeof(gJihadSavedUpThreshold)) ||
|
||||
!strcmp(gJihadSavedUpThreshold, wantUp)) {
|
||||
strcpy(gJihadSavedUpThreshold, kJihadStockUpThreshold);
|
||||
}
|
||||
if (!JihadReadTunable("sampling_rate", gJihadSavedSamplingRate,
|
||||
sizeof(gJihadSavedSamplingRate)) ||
|
||||
!strcmp(gJihadSavedSamplingRate, wantRate)) {
|
||||
strcpy(gJihadSavedSamplingRate, kJihadStockSamplingRate);
|
||||
}
|
||||
|
||||
bool ok = JihadWriteTunable("up_threshold", wantUp);
|
||||
ok = JihadWriteTunable("sampling_rate", wantRate) && ok;
|
||||
gJihadBoostHeld = ok;
|
||||
fprintf(stderr, "[jihad-npapi] cpu boost acquire: up_threshold %s->%s sampling_rate %s->%s%s\n",
|
||||
gJihadSavedUpThreshold, wantUp, gJihadSavedSamplingRate, wantRate,
|
||||
ok ? "" : " FAILED (not root, or no ondemandtcl)");
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
void
|
||||
JihadCpuBoostRelease(NPP aInstance)
|
||||
{
|
||||
if (!gJihadBoostedInstances.RemoveElement(aInstance)) return;
|
||||
if (!gJihadBoostedInstances.IsEmpty() || !gJihadBoostHeld) return;
|
||||
|
||||
JihadWriteTunable("up_threshold", gJihadSavedUpThreshold);
|
||||
JihadWriteTunable("sampling_rate", gJihadSavedSamplingRate);
|
||||
gJihadBoostHeld = false;
|
||||
fprintf(stderr, "[jihad-npapi] cpu boost release: up_threshold=%s sampling_rate=%s\n",
|
||||
gJihadSavedUpThreshold, gJihadSavedSamplingRate);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
// Drop every outstanding boost at once. Used when the plugin module itself goes away, which on
|
||||
// this port is the NORMAL exit path (see ActorDestroy), not just the crash path.
|
||||
void
|
||||
JihadCpuBoostReleaseAll()
|
||||
{
|
||||
if (gJihadBoostedInstances.IsEmpty()) return;
|
||||
gJihadBoostedInstances.Clear();
|
||||
if (!gJihadBoostHeld) return;
|
||||
|
||||
JihadWriteTunable("up_threshold", gJihadSavedUpThreshold);
|
||||
JihadWriteTunable("sampling_rate", gJihadSavedSamplingRate);
|
||||
gJihadBoostHeld = false;
|
||||
fprintf(stderr, "[jihad-npapi] cpu boost release (module gone): up_threshold=%s sampling_rate=%s\n",
|
||||
gJihadSavedUpThreshold, gJihadSavedSamplingRate);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
NPError
|
||||
PluginModuleParent::NPP_Destroy(NPP instance,
|
||||
NPSavedData** saved)
|
||||
@@ -1478,7 +1296,6 @@ PluginModuleParent::NPP_Destroy(NPP instance,
|
||||
instance->pdata = nullptr;
|
||||
|
||||
Unused << PluginInstanceParent::Call__delete__(parentInstance);
|
||||
JihadCpuBoostRelease(instance);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -2475,12 +2292,6 @@ PluginModuleParent::NPP_NewInternal(NPMIMEType pluginType, NPP instance,
|
||||
|
||||
UpdatePluginTimeout();
|
||||
|
||||
// Taken ONLY here, on the single success path, and released in NPP_Destroy. Every failure
|
||||
// return above this point either deletes |parentInstance| itself or hands it to NPP_Destroy,
|
||||
// and a boost acquired on one of those paths would never be released — which on this device
|
||||
// means leaving the WHOLE system tuned for battery burn with nothing owning it.
|
||||
JihadCpuBoostAcquire(instance);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,14 +111,9 @@ PluginProcessChild::Init()
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[jihad-npapi-child] InitForChrome plugin=%s parentPid=%d\n",
|
||||
pluginFilename.c_str(), (int)ParentPid());
|
||||
fflush(stderr);
|
||||
bool retval = mPlugin.InitForChrome(pluginFilename, ParentPid(),
|
||||
IOThreadChild::message_loop(),
|
||||
IOThreadChild::channel());
|
||||
fprintf(stderr, "[jihad-npapi-child] InitForChrome = %d\n", (int)retval);
|
||||
fflush(stderr);
|
||||
#if defined(XP_MACOSX)
|
||||
if (nsCocoaFeatures::OnYosemiteOrLater()) {
|
||||
// Explicitly turn off CGEvent logging. This works around bug 1092855.
|
||||
|
||||
@@ -88,10 +88,6 @@ UNIFIED_SOURCES += [
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
# Jihad: must NOT be unified — it deliberately includes no NPAPI headers so its exported
|
||||
# NPN_* symbols escape the hidden-visibility pragma. Merging it with a TU that includes
|
||||
# npapi.h would reintroduce the prior hidden declaration and silently un-export them.
|
||||
'JihadNPNInterpose.cpp',
|
||||
'PluginInstanceChild.cpp', # 'PluginThreadCallback' : ambiguous symbol
|
||||
'PluginModuleChild.cpp', # Redefinition of mozilla::WindowsDllInterceptor sUser32Intercept
|
||||
]
|
||||
|
||||
+14
-5
@@ -1789,17 +1789,26 @@ URL::IsValidURL(const GlobalObject& aGlobal, const nsAString& aURL,
|
||||
return URLWorker::IsValidURL(aGlobal, aURL, aRv);
|
||||
}
|
||||
|
||||
bool
|
||||
URL::CanParse(const GlobalObject& aGlobal, const nsAString& aURL,
|
||||
const Optional<nsAString>& aBase) {
|
||||
already_AddRefed<URL>
|
||||
URL::Parse(const GlobalObject& aGlobal, const nsAString& aURL,
|
||||
const Optional<nsAString>& aBase)
|
||||
{
|
||||
ErrorResult rv;
|
||||
RefPtr<URL> parsed = URL::Constructor(aGlobal, aURL, aBase, rv);
|
||||
if (rv.Failed() || !parsed) {
|
||||
rv.SuppressException();
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
return parsed.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
URL::CanParse(const GlobalObject& aGlobal, const nsAString& aURL,
|
||||
const Optional<nsAString>& aBase)
|
||||
{
|
||||
RefPtr<URL> parsed = URL::Parse(aGlobal, aURL, aBase);
|
||||
return !!parsed;
|
||||
}
|
||||
|
||||
already_AddRefed<URLSearchParams>
|
||||
|
||||
@@ -88,6 +88,10 @@ public:
|
||||
IsValidURL(const GlobalObject& aGlobal, const nsAString& aURL,
|
||||
ErrorResult& aRv);
|
||||
|
||||
static already_AddRefed<URL>
|
||||
Parse(const GlobalObject& aGlobal, const nsAString& aURL,
|
||||
const Optional<nsAString>& aBase);
|
||||
|
||||
static bool
|
||||
CanParse(const GlobalObject& aGlobal, const nsAString& aURL,
|
||||
const Optional<nsAString>& aBase);
|
||||
|
||||
@@ -448,6 +448,34 @@
|
||||
!URL.canParse("/static/client/runtime.js", "mailto:test@example.com"),
|
||||
"URL.canParse rejects root-relative input against opaque mailto: base",
|
||||
);
|
||||
ok("parse" in URL, "URL.parse exists");
|
||||
is(URL.parse.length, 1, "URL.parse has one required argument");
|
||||
var parsed = URL.parse("/static/client/runtime.js", "x:/");
|
||||
ok(parsed instanceof URL, "URL.parse returns a URL object on success");
|
||||
is(parsed.href, "x:/static/client/runtime.js", "URL.parse resolves against a base");
|
||||
is(URL.parse("/static/client/runtime.js"), null, "URL.parse returns null for invalid input");
|
||||
is(
|
||||
URL.parse("/static/client/runtime.js", "mailto:test@example.com"),
|
||||
null,
|
||||
"URL.parse returns null for an invalid base/input combination",
|
||||
);
|
||||
|
||||
var conversionOrder = [];
|
||||
parsed = URL.parse(
|
||||
{ toString: function() { conversionOrder.push("url"); return "child"; } },
|
||||
{ toString: function() { conversionOrder.push("base"); return "https://example.com/root/"; } },
|
||||
);
|
||||
is(conversionOrder.join(","), "url,base", "URL.parse converts its arguments in order");
|
||||
is(parsed.href, "https://example.com/root/child", "URL.parse uses converted arguments");
|
||||
|
||||
var sentinel = {};
|
||||
var thrown;
|
||||
try {
|
||||
URL.parse({ toString: function() { throw sentinel; } });
|
||||
} catch (e) {
|
||||
thrown = e;
|
||||
}
|
||||
is(thrown, sentinel, "URL.parse propagates argument conversion exceptions");
|
||||
SimpleTest.doesThrow(() => {
|
||||
new URL("/static/client/runtime.js", "mailto:test@example.com");
|
||||
}, "constructor rejects root-relative input against opaque mailto: base");
|
||||
|
||||
@@ -301,6 +301,64 @@ onmessage = function () {
|
||||
!URL.canParse("/static/client/runtime.js", "mailto:test@example.com"),
|
||||
"URL.canParse rejects root-relative input against opaque mailto: base",
|
||||
);
|
||||
ok("parse" in URL, "URL.parse exists");
|
||||
is(URL.parse.length, 1, "URL.parse has one required argument");
|
||||
let parsed = URL.parse("/static/client/runtime.js", "x:/");
|
||||
ok(parsed instanceof URL, "URL.parse returns a URL object on success");
|
||||
is(
|
||||
parsed.href,
|
||||
"x:/static/client/runtime.js",
|
||||
"URL.parse resolves against a base",
|
||||
);
|
||||
is(
|
||||
URL.parse("/static/client/runtime.js"),
|
||||
null,
|
||||
"URL.parse returns null for invalid input",
|
||||
);
|
||||
is(
|
||||
URL.parse("/static/client/runtime.js", "mailto:test@example.com"),
|
||||
null,
|
||||
"URL.parse returns null for an invalid base/input combination",
|
||||
);
|
||||
|
||||
const conversionOrder = [];
|
||||
parsed = URL.parse(
|
||||
{
|
||||
toString() {
|
||||
conversionOrder.push("url");
|
||||
return "child";
|
||||
},
|
||||
},
|
||||
{
|
||||
toString() {
|
||||
conversionOrder.push("base");
|
||||
return "https://example.com/root/";
|
||||
},
|
||||
},
|
||||
);
|
||||
is(
|
||||
conversionOrder.join(","),
|
||||
"url,base",
|
||||
"URL.parse converts its arguments in order",
|
||||
);
|
||||
is(
|
||||
parsed.href,
|
||||
"https://example.com/root/child",
|
||||
"URL.parse uses converted arguments",
|
||||
);
|
||||
|
||||
const sentinel = {};
|
||||
let thrown;
|
||||
try {
|
||||
URL.parse({
|
||||
toString() {
|
||||
throw sentinel;
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
thrown = e;
|
||||
}
|
||||
is(thrown, sentinel, "URL.parse propagates argument conversion exceptions");
|
||||
let mailtoRelativeFailed = false;
|
||||
try {
|
||||
new URL("/static/client/runtime.js", "mailto:test@example.com");
|
||||
|
||||
@@ -23,6 +23,7 @@ interface URL {
|
||||
[Throws]
|
||||
stringifier;
|
||||
|
||||
static URL? parse(USVString url, optional USVString base);
|
||||
static boolean canParse(USVString url, optional USVString base);
|
||||
|
||||
[Throws]
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
// Local Includes
|
||||
#include "nsDocShellTreeOwner.h"
|
||||
#include "nsIBadCertListener2.h" // JIHAD: route cert errors to the embedder chrome
|
||||
#include "nsWebBrowser.h"
|
||||
|
||||
// Helper Classes
|
||||
@@ -144,15 +143,6 @@ nsDocShellTreeOwner::GetInterface(const nsIID& aIID, void** aSink)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (aIID.Equals(NS_GET_IID(nsIBadCertListener2))) {
|
||||
// JIHAD: the embedder's chrome is what implements this — see the matching comment in
|
||||
// nsDocShell::GetInterface. Same forwarding shape as nsIWebBrowserChromeFocus below.
|
||||
if (mWebBrowserChromeWeak != nullptr) {
|
||||
return mWebBrowserChromeWeak->QueryReferent(aIID, aSink);
|
||||
}
|
||||
return mOwnerWin ? mOwnerWin->QueryInterface(aIID, aSink) : NS_NOINTERFACE;
|
||||
}
|
||||
|
||||
if (aIID.Equals(NS_GET_IID(nsIWebBrowserChromeFocus))) {
|
||||
if (mWebBrowserChromeWeak != nullptr) {
|
||||
return mWebBrowserChromeWeak->QueryReferent(aIID, aSink);
|
||||
|
||||
@@ -390,14 +390,6 @@ nsWebBrowserFind::SetSelectionAndScroll(nsPIDOMWindowOuter* aWindow,
|
||||
|
||||
nsCOMPtr<nsISelection> selection;
|
||||
|
||||
// JIHAD: frame->GetSelectionController can leave selCon NULL, and this dereferenced it
|
||||
// unconditionally — a null deref that took the whole process down. It is reachable in an
|
||||
// offscreen embedding with no chrome: this is the ONLY thing that made find-in-page
|
||||
// unusable there (faultaddr=0x0, measured repeatedly). Guarding costs nothing and the
|
||||
// rest of the function already tolerates a null `selection`.
|
||||
if (!selCon) {
|
||||
return;
|
||||
}
|
||||
selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
|
||||
selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(selection));
|
||||
@@ -715,24 +707,7 @@ nsWebBrowserFind::SearchInFrame(nsPIDOMWindowOuter* aWindow, bool aWrapping,
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// JIHAD: this same-origin check is why find-in-page took the whole process down in this
|
||||
// embedding, and the engine says why itself. nsContentUtils::SubjectPrincipal() begins:
|
||||
// JSContext* cx = GetCurrentJSContext();
|
||||
// if (!cx) {
|
||||
// MOZ_CRASH("Accessing the Subject Principal without an AutoJSAPI on the stack is forbidden");
|
||||
// }
|
||||
// An EMBEDDER calls nsIWebBrowserFind::FindNext from C++ with no script on the stack, so
|
||||
// there is no JSContext and MOZ_CRASH fires — a deliberate abort that presents as SIGSEGV at
|
||||
// address 0, which is precisely the fault this port recorded for two weeks and attributed to
|
||||
// "a frame-selection controller the offscreen browser doesn't set up". That was wrong: the
|
||||
// selection machinery is fine and the crash happens BEFORE any of it, on the security check.
|
||||
//
|
||||
// The check is meaningful only when there IS a caller to check. With no JS on the stack the
|
||||
// caller is native embedding code, which is already fully trusted — it is inside libxul's own
|
||||
// process, holding the nsIWebBrowserFind for that very docShell. So: consult the subject
|
||||
// principal only when a JSContext exists, and otherwise proceed.
|
||||
if (nsContentUtils::GetCurrentJSContext() &&
|
||||
!nsContentUtils::SubjectPrincipal()->Subsumes(theDoc->NodePrincipal())) {
|
||||
if (!nsContentUtils::SubjectPrincipal()->Subsumes(theDoc->NodePrincipal())) {
|
||||
return NS_ERROR_DOM_PROP_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
@@ -834,11 +809,8 @@ nsWebBrowserFind::GetFrameSelection(nsPIDOMWindowOuter* aWindow)
|
||||
nsCOMPtr<nsISelection> sel;
|
||||
if (frame) {
|
||||
frame->GetSelectionController(presContext, getter_AddRefs(selCon));
|
||||
// JIHAD: same unguarded deref as above — GetSelectionController can yield null.
|
||||
if (selCon) {
|
||||
selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(sel));
|
||||
}
|
||||
selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(sel));
|
||||
if (sel) {
|
||||
int32_t count = -1;
|
||||
sel->GetRangeCount(&count);
|
||||
@@ -848,12 +820,7 @@ nsWebBrowserFind::GetFrameSelection(nsPIDOMWindowOuter* aWindow)
|
||||
}
|
||||
}
|
||||
|
||||
// JIHAD: and the fallback QI is not guaranteed either — if the presShell does not answer
|
||||
// nsISelectionController we must return nothing rather than crash.
|
||||
selCon = do_QueryInterface(presShell);
|
||||
if (!selCon) {
|
||||
return nullptr;
|
||||
}
|
||||
selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(sel));
|
||||
return sel.forget();
|
||||
|
||||
@@ -12,6 +12,5 @@ if CONFIG['NS_PRINTING']:
|
||||
DIRS += ['win']
|
||||
elif toolkit == 'cocoa':
|
||||
DIRS += ['mac']
|
||||
elif CONFIG['MOZ_PDF_PRINTING'] or toolkit == 'headless':
|
||||
# Jihad headless: reuse the gtk-free PDF printing prompt service.
|
||||
elif CONFIG['MOZ_PDF_PRINTING']:
|
||||
DIRS += ['unixshared']
|
||||
+1
-1
@@ -1374,7 +1374,7 @@ public:
|
||||
static already_AddRefed<ScaledFont>
|
||||
CreateScaledFontForNativeFont(const NativeFont &aNativeFont, Float aSize);
|
||||
|
||||
#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_HEADLESS)
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
static already_AddRefed<ScaledFont>
|
||||
CreateScaledFontForFontconfigFont(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern, Float aSize);
|
||||
#endif
|
||||
|
||||
@@ -1117,6 +1117,7 @@ DrawTargetSkia::BorrowCGContext(const DrawOptions &aOptions)
|
||||
kCGImageAlphaOnly :
|
||||
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
|
||||
|
||||
#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
|
||||
mCG = CGBitmapContextCreateWithData(mCanvasData,
|
||||
mCGSize.width,
|
||||
mCGSize.height,
|
||||
@@ -1126,6 +1127,15 @@ DrawTargetSkia::BorrowCGContext(const DrawOptions &aOptions)
|
||||
bitmapInfo,
|
||||
NULL, /* Callback when released */
|
||||
NULL);
|
||||
#else
|
||||
mCG = CGBitmapContextCreate(mCanvasData,
|
||||
mCGSize.width,
|
||||
mCGSize.height,
|
||||
8, /* bits per component */
|
||||
stride,
|
||||
mColorSpace,
|
||||
bitmapInfo);
|
||||
#endif
|
||||
if (!mCG) {
|
||||
ReleaseBits(mCanvasData);
|
||||
NS_WARNING("Could not create bitmap around skia data\n");
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@
|
||||
#include "NativeFontResourceMac.h"
|
||||
#endif
|
||||
|
||||
#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_HEADLESS)
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
#include "ScaledFontFontconfig.h"
|
||||
#endif
|
||||
|
||||
@@ -584,7 +584,7 @@ Factory::CreateScaledFontWithCairo(const NativeFont& aNativeFont, Float aSize, c
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_HEADLESS)
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
already_AddRefed<ScaledFont>
|
||||
Factory::CreateScaledFontForFontconfigFont(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern, Float aSize)
|
||||
{
|
||||
|
||||
@@ -115,7 +115,7 @@ ScaledFontMac::GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aT
|
||||
return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget);
|
||||
}
|
||||
|
||||
#if !defined(MAC_OS_X_VERSION_10_7) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7)
|
||||
#ifndef USE_SKIA
|
||||
void
|
||||
ScaledFontMac::CopyGlyphsToBuilder(const GlyphBuffer &aBuffer, PathBuilder *aBuilder, const Matrix *aTransformHint)
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
virtual SkTypeface* GetSkTypeface();
|
||||
#endif
|
||||
virtual already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget);
|
||||
#if !defined(MAC_OS_X_VERSION_10_7) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7)
|
||||
#ifndef USE_SKIA
|
||||
virtual void CopyGlyphsToBuilder(const GlyphBuffer &aBuffer, PathBuilder *aBuilder, const Matrix *aTransformHint);
|
||||
#endif
|
||||
virtual bool GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton);
|
||||
|
||||
+3
-3
@@ -91,7 +91,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'windows':
|
||||
'JobScheduler_posix.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'headless'):
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
|
||||
SOURCES += [
|
||||
'ScaledFontFontconfig.cpp',
|
||||
]
|
||||
@@ -209,12 +209,12 @@ FINAL_LIBRARY = 'xul'
|
||||
for var in ('USE_CAIRO', 'MOZ2D_HAS_MOZ_CAIRO'):
|
||||
DEFINES[var] = True
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'headless'):
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
|
||||
DEFINES['MOZ_ENABLE_FREETYPE'] = True
|
||||
|
||||
CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'headless'):
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
|
||||
CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS']
|
||||
|
||||
LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES']
|
||||
|
||||
@@ -87,17 +87,10 @@ public:
|
||||
bool
|
||||
BasicPlanarYCbCrImage::CopyData(const Data& aData)
|
||||
{
|
||||
if (!RecyclingPlanarYCbCrImage::CopyData(aData)) {
|
||||
return false;
|
||||
}
|
||||
RecyclingPlanarYCbCrImage::CopyData(aData);
|
||||
|
||||
// The YCbCr copy above succeeded; delaying the RGB conversion is not a
|
||||
// failure. GetAsSourceSurface() converts on demand when mDecodedBuffer is
|
||||
// null. Returning false here made VideoData::CreateAndCopyData report OOM
|
||||
// (MEDIA_ERR_DECODE) for any frame decoded while BasicImageLayer::Paint had
|
||||
// swapped this factory into the video's ImageContainer.
|
||||
if (mDelayedConversion) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do some sanity checks to prevent integer overflow
|
||||
|
||||
+2
-3
@@ -529,7 +529,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] in {'cocoa', 'uikit'}:
|
||||
SOURCES += [
|
||||
'skia/src/ports/SkFontHost_mac.cpp',
|
||||
]
|
||||
if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT'] or CONFIG['MOZ_WIDGET_TOOLKIT'] == 'headless':
|
||||
if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
|
||||
UNIFIED_SOURCES += [
|
||||
'skia/src/ports/SkDebug_stdio.cpp',
|
||||
'skia/src/ports/SkOSFile_posix.cpp',
|
||||
@@ -665,7 +665,6 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] in {
|
||||
'cocoa',
|
||||
'gtk2',
|
||||
'gtk3',
|
||||
'headless',
|
||||
'uikit',
|
||||
}:
|
||||
DEFINES['SK_FONTHOST_DOES_NOT_USE_FONTMGR'] = 1
|
||||
@@ -739,7 +738,7 @@ if CONFIG['CLANG_CXX'] or CONFIG['CLANG_CL']:
|
||||
'-Wno-unused-private-field',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'headless'):
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
|
||||
CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
|
||||
CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS']
|
||||
|
||||
|
||||
@@ -24,15 +24,21 @@
|
||||
* Skia's 32bit backend only supports 1 sizzle order at a time (compile-time).
|
||||
* This is specified by 4 defines SK_A32_SHIFT, SK_R32_SHIFT, ... for G and B.
|
||||
*
|
||||
* For easier compatibility with Skia's GPU backend, we further restrict these
|
||||
* to either (in memory-byte-order) RGBA or BGRA. Note that this "order" does
|
||||
* not directly correspond to the same shift-order, since we have to take endianess
|
||||
* into account.
|
||||
* For easier compatibility with Skia's GPU backend, Skia normally restricts
|
||||
* these to either (in memory-byte-order) RGBA or BGRA. Mozilla's big-endian
|
||||
* PowerPC port uses CoreGraphics' native ARGB pixels, so we also accept ARGB.
|
||||
* Note that this "order" does not directly correspond to the same shift-order,
|
||||
* since we have to take endianess into account.
|
||||
*
|
||||
* Here we enforce this constraint.
|
||||
*/
|
||||
|
||||
#ifdef SK_CPU_BENDIAN
|
||||
#define SK_ARGB_A32_SHIFT 24
|
||||
#define SK_ARGB_R32_SHIFT 16
|
||||
#define SK_ARGB_G32_SHIFT 8
|
||||
#define SK_ARGB_B32_SHIFT 0
|
||||
|
||||
#define SK_RGBA_R32_SHIFT 24
|
||||
#define SK_RGBA_G32_SHIFT 16
|
||||
#define SK_RGBA_B32_SHIFT 8
|
||||
@@ -43,6 +49,11 @@
|
||||
#define SK_BGRA_R32_SHIFT 8
|
||||
#define SK_BGRA_A32_SHIFT 0
|
||||
#else
|
||||
#define SK_ARGB_A32_SHIFT 0
|
||||
#define SK_ARGB_R32_SHIFT 8
|
||||
#define SK_ARGB_G32_SHIFT 16
|
||||
#define SK_ARGB_B32_SHIFT 24
|
||||
|
||||
#define SK_RGBA_R32_SHIFT 0
|
||||
#define SK_RGBA_G32_SHIFT 8
|
||||
#define SK_RGBA_B32_SHIFT 16
|
||||
@@ -54,10 +65,18 @@
|
||||
#define SK_BGRA_A32_SHIFT 24
|
||||
#endif
|
||||
|
||||
#if defined(SK_PMCOLOR_IS_RGBA) && defined(SK_PMCOLOR_IS_BGRA)
|
||||
#error "can't define PMCOLOR to be RGBA and BGRA"
|
||||
#if (defined(SK_PMCOLOR_IS_RGBA) && defined(SK_PMCOLOR_IS_BGRA)) || \
|
||||
(defined(SK_PMCOLOR_IS_RGBA) && defined(SK_PMCOLOR_IS_ARGB)) || \
|
||||
(defined(SK_PMCOLOR_IS_BGRA) && defined(SK_PMCOLOR_IS_ARGB))
|
||||
#error "can't define PMCOLOR to more than one byte order"
|
||||
#endif
|
||||
|
||||
#define LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_ARGB \
|
||||
(SK_A32_SHIFT == SK_ARGB_A32_SHIFT && \
|
||||
SK_R32_SHIFT == SK_ARGB_R32_SHIFT && \
|
||||
SK_G32_SHIFT == SK_ARGB_G32_SHIFT && \
|
||||
SK_B32_SHIFT == SK_ARGB_B32_SHIFT)
|
||||
|
||||
#define LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_RGBA \
|
||||
(SK_A32_SHIFT == SK_RGBA_A32_SHIFT && \
|
||||
SK_R32_SHIFT == SK_RGBA_R32_SHIFT && \
|
||||
@@ -76,6 +95,10 @@
|
||||
#define SK_G_INDEX (SK_G32_SHIFT/8)
|
||||
#define SK_B_INDEX (SK_B32_SHIFT/8)
|
||||
|
||||
#if defined(SK_PMCOLOR_IS_ARGB) && !LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_ARGB
|
||||
#error "SK_PMCOLOR_IS_ARGB does not match SK_*32_SHIFT values"
|
||||
#endif
|
||||
|
||||
#if defined(SK_PMCOLOR_IS_RGBA) && !LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_RGBA
|
||||
#error "SK_PMCOLOR_IS_RGBA does not match SK_*32_SHIFT values"
|
||||
#endif
|
||||
@@ -84,19 +107,22 @@
|
||||
#error "SK_PMCOLOR_IS_BGRA does not match SK_*32_SHIFT values"
|
||||
#endif
|
||||
|
||||
#if !defined(SK_PMCOLOR_IS_RGBA) && !defined(SK_PMCOLOR_IS_BGRA)
|
||||
#if !defined(SK_PMCOLOR_IS_ARGB) && !defined(SK_PMCOLOR_IS_RGBA) && !defined(SK_PMCOLOR_IS_BGRA)
|
||||
// deduce which to define from the _SHIFT defines
|
||||
|
||||
#if LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_RGBA
|
||||
#if LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_ARGB
|
||||
#define SK_PMCOLOR_IS_ARGB
|
||||
#elif LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_RGBA
|
||||
#define SK_PMCOLOR_IS_RGBA
|
||||
#elif LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_BGRA
|
||||
#define SK_PMCOLOR_IS_BGRA
|
||||
#else
|
||||
#error "need 32bit packing to be either RGBA or BGRA"
|
||||
#error "need 32bit packing to be either ARGB, RGBA, or BGRA"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// hide these now that we're done
|
||||
#undef LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_ARGB
|
||||
#undef LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_RGBA
|
||||
#undef LOCAL_PMCOLOR_SHIFTS_EQUIVALENT_TO_BGRA
|
||||
|
||||
@@ -131,19 +157,38 @@ static inline uint32_t SkPackARGB_as_BGRA(U8CPU a, U8CPU r, U8CPU g, U8CPU b) {
|
||||
(g << SK_BGRA_G32_SHIFT) | (b << SK_BGRA_B32_SHIFT);
|
||||
}
|
||||
|
||||
static inline SkPMColor SkPackARGB_as_PMColor(U8CPU a, U8CPU r, U8CPU g, U8CPU b) {
|
||||
SkASSERT_IS_BYTE(a);
|
||||
SkASSERT_IS_BYTE(r);
|
||||
SkASSERT_IS_BYTE(g);
|
||||
SkASSERT_IS_BYTE(b);
|
||||
return (a << SK_A32_SHIFT) | (r << SK_R32_SHIFT) |
|
||||
(g << SK_G32_SHIFT) | (b << SK_B32_SHIFT);
|
||||
}
|
||||
|
||||
static inline SkPMColor SkSwizzle_RGBA_to_PMColor(uint32_t c) {
|
||||
#ifdef SK_PMCOLOR_IS_RGBA
|
||||
return c;
|
||||
#else
|
||||
#elif defined(SK_PMCOLOR_IS_BGRA)
|
||||
return SkSwizzle_RB(c);
|
||||
#else
|
||||
return SkPackARGB_as_PMColor((c >> SK_RGBA_A32_SHIFT) & 0xFF,
|
||||
(c >> SK_RGBA_R32_SHIFT) & 0xFF,
|
||||
(c >> SK_RGBA_G32_SHIFT) & 0xFF,
|
||||
(c >> SK_RGBA_B32_SHIFT) & 0xFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline SkPMColor SkSwizzle_BGRA_to_PMColor(uint32_t c) {
|
||||
#ifdef SK_PMCOLOR_IS_BGRA
|
||||
return c;
|
||||
#else
|
||||
#elif defined(SK_PMCOLOR_IS_RGBA)
|
||||
return SkSwizzle_RB(c);
|
||||
#else
|
||||
return SkPackARGB_as_PMColor((c >> SK_BGRA_A32_SHIFT) & 0xFF,
|
||||
(c >> SK_BGRA_R32_SHIFT) & 0xFF,
|
||||
(c >> SK_BGRA_G32_SHIFT) & 0xFF,
|
||||
(c >> SK_BGRA_B32_SHIFT) & 0xFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -412,8 +457,7 @@ static inline SkPMColor SkPackARGB32(U8CPU a, U8CPU r, U8CPU g, U8CPU b) {
|
||||
SkASSERT(g <= a);
|
||||
SkASSERT(b <= a);
|
||||
|
||||
return (a << SK_A32_SHIFT) | (r << SK_R32_SHIFT) |
|
||||
(g << SK_G32_SHIFT) | (b << SK_B32_SHIFT);
|
||||
return SkPackARGB_as_PMColor(a, r, g, b);
|
||||
}
|
||||
|
||||
static inline uint32_t SkPackPMColor_as_RGBA(SkPMColor c) {
|
||||
@@ -547,8 +591,7 @@ static inline SkPMColor SkFastFourByteInterp(SkPMColor src,
|
||||
* values are premultiplied in the debug version.
|
||||
*/
|
||||
static inline SkPMColor SkPackARGB32NoCheck(U8CPU a, U8CPU r, U8CPU g, U8CPU b) {
|
||||
return (a << SK_A32_SHIFT) | (r << SK_R32_SHIFT) |
|
||||
(g << SK_G32_SHIFT) | (b << SK_B32_SHIFT);
|
||||
return SkPackARGB_as_PMColor(a, r, g, b);
|
||||
}
|
||||
|
||||
static inline
|
||||
|
||||
@@ -82,8 +82,13 @@ enum SkColorType {
|
||||
kN32_SkColorType = kBGRA_8888_SkColorType,
|
||||
#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
|
||||
kN32_SkColorType = kRGBA_8888_SkColorType,
|
||||
#elif defined(MOZ_SKIA) && SK_PMCOLOR_BYTE_ORDER(A,R,G,B)
|
||||
// Big-endian PowerPC Mozilla surfaces use native uint32 ARGB memory.
|
||||
// Moz2D's legacy A8R8G8B8_UINT32 format aliases to B8G8R8A8, so keep
|
||||
// kN32 on Skia's BGRA path while the channel shifts above describe ARGB.
|
||||
kN32_SkColorType = kBGRA_8888_SkColorType,
|
||||
#else
|
||||
#error "SK_*32_SHFIT values must correspond to BGRA or RGBA byte order"
|
||||
#error "SK_*32_SHIFT values must correspond to BGRA, RGBA, or Mozilla ARGB byte order"
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -275,17 +275,19 @@ enum GrPixelConfig {
|
||||
static const int kGrPixelConfigCnt = kLast_GrPixelConfig + 1;
|
||||
|
||||
// Aliases for pixel configs that match skia's byte order.
|
||||
#ifndef SK_CPU_LENDIAN
|
||||
#error "Skia gpu currently assumes little endian"
|
||||
#endif
|
||||
#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
|
||||
static const GrPixelConfig kSkia8888_GrPixelConfig = kBGRA_8888_GrPixelConfig;
|
||||
static const GrPixelConfig kSkiaGamma8888_GrPixelConfig = kSBGRA_8888_GrPixelConfig;
|
||||
#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
|
||||
static const GrPixelConfig kSkia8888_GrPixelConfig = kRGBA_8888_GrPixelConfig;
|
||||
static const GrPixelConfig kSkiaGamma8888_GrPixelConfig = kSRGBA_8888_GrPixelConfig;
|
||||
#elif defined(MOZ_SKIA) && SK_PMCOLOR_BYTE_ORDER(A,R,G,B)
|
||||
// Mozilla's big-endian PowerPC path stores native 32-bit pixels as ARGB,
|
||||
// while Moz2D routes those surfaces through Skia's BGRA color type.
|
||||
static const GrPixelConfig kSkia8888_GrPixelConfig = kBGRA_8888_GrPixelConfig;
|
||||
static const GrPixelConfig kSkiaGamma8888_GrPixelConfig = kSBGRA_8888_GrPixelConfig;
|
||||
#else
|
||||
#error "SK_*32_SHIFT values must correspond to GL_BGRA or GL_RGBA format."
|
||||
#error "SK_*32_SHIFT values must correspond to GL_BGRA, GL_RGBA, or Mozilla ARGB format."
|
||||
#endif
|
||||
|
||||
// Returns true if the pixel config is a GPU-specific compressed format
|
||||
|
||||
@@ -306,7 +306,11 @@ static inline SkPMColor premultiply_argb_as_bgra(U8CPU a, U8CPU r, U8CPU g, U8CP
|
||||
b = SkMulDiv255Round(b, a);
|
||||
}
|
||||
|
||||
#ifdef SK_PMCOLOR_IS_ARGB
|
||||
return SkPackARGB_as_PMColor(a, r, g, b);
|
||||
#else
|
||||
return SkPackARGB_as_BGRA(a, r, g, b);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool is_rgba(SkColorType colorType) {
|
||||
|
||||
@@ -51,10 +51,16 @@ struct SkPM4f {
|
||||
Sk4f to4f_pmorder() const { return swizzle_rb_if_bgra(this->to4f()); }
|
||||
|
||||
SkPMColor toPMColor() const {
|
||||
#ifdef SK_PMCOLOR_IS_ARGB
|
||||
Sk4f value = this->to4f();
|
||||
Sk4b bytes = SkNx_cast<uint8_t>(value * Sk4f(255) + Sk4f(0.5f));
|
||||
return SkPackARGB_as_PMColor(bytes[3], bytes[0], bytes[1], bytes[2]);
|
||||
#else
|
||||
Sk4f value = swizzle_rb_if_bgra(this->to4f());
|
||||
SkPMColor result;
|
||||
SkNx_cast<uint8_t>(value * Sk4f(255) + Sk4f(0.5f)).store(&result);
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
void toF16(uint16_t[4]) const;
|
||||
|
||||
@@ -22,29 +22,50 @@ static inline float get_alpha(const Sk4f& px) {
|
||||
|
||||
|
||||
static inline Sk4f Sk4f_fromL32(uint32_t px) {
|
||||
#ifdef SK_PMCOLOR_IS_ARGB
|
||||
return Sk4f(SkGetPackedR32(px), SkGetPackedG32(px),
|
||||
SkGetPackedB32(px), SkGetPackedA32(px)) * (1/255.0f);
|
||||
#else
|
||||
return SkNx_cast<float>(Sk4b::Load(&px)) * (1/255.0f);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline Sk4f Sk4f_fromS32(uint32_t px) {
|
||||
#ifdef SK_PMCOLOR_IS_ARGB
|
||||
return { sk_linear_from_srgb[SkGetPackedR32(px)],
|
||||
sk_linear_from_srgb[SkGetPackedG32(px)],
|
||||
sk_linear_from_srgb[SkGetPackedB32(px)],
|
||||
(1/255.0f) * SkGetPackedA32(px) };
|
||||
#else
|
||||
return { sk_linear_from_srgb[(px >> 0) & 0xff],
|
||||
sk_linear_from_srgb[(px >> 8) & 0xff],
|
||||
sk_linear_from_srgb[(px >> 16) & 0xff],
|
||||
(1/255.0f) * (px >> 24) };
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t Sk4f_toL32(const Sk4f& px) {
|
||||
#ifdef SK_PMCOLOR_IS_ARGB
|
||||
Sk4b bytes = SkNx_cast<uint8_t>(Sk4f_round(px * 255.0f));
|
||||
return SkPackARGB_as_PMColor(bytes[3], bytes[0], bytes[1], bytes[2]);
|
||||
#else
|
||||
uint32_t l32;
|
||||
SkNx_cast<uint8_t>(Sk4f_round(px * 255.0f)).store(&l32);
|
||||
return l32;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t Sk4f_toS32(const Sk4f& px) {
|
||||
Sk4i rgb = sk_linear_to_srgb(px),
|
||||
srgb = { rgb[0], rgb[1], rgb[2], (int)(255.0f * px[3] + 0.5f) };
|
||||
|
||||
#ifdef SK_PMCOLOR_IS_ARGB
|
||||
return SkPackARGB_as_PMColor(srgb[3], srgb[0], srgb[1], srgb[2]);
|
||||
#else
|
||||
uint32_t s32;
|
||||
SkNx_cast<uint8_t>(srgb).store(&s32);
|
||||
return s32;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -121,9 +121,10 @@ SkDEBUGCODE(static const unsigned kRunRecordMagic = 0xb10bcafe;)
|
||||
namespace {
|
||||
struct RunRecordStorageEquivalent {
|
||||
RunFont fFont;
|
||||
SkPoint fOffset;
|
||||
uint32_t fCount;
|
||||
uint32_t fFlags;
|
||||
SkPoint fOffset;
|
||||
SkTextBlob::GlyphPositioning fPositioning;
|
||||
bool fExtended;
|
||||
SkDEBUGCODE(unsigned fMagic;)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,6 +44,25 @@ static Sk4x4f load_4_srgb(const void* vptr) {
|
||||
|
||||
Sk4x4f rgba;
|
||||
|
||||
#ifdef SK_PMCOLOR_IS_ARGB
|
||||
rgba.r = { sk_linear_from_srgb[SkGetPackedR32(ptr[0])],
|
||||
sk_linear_from_srgb[SkGetPackedR32(ptr[1])],
|
||||
sk_linear_from_srgb[SkGetPackedR32(ptr[2])],
|
||||
sk_linear_from_srgb[SkGetPackedR32(ptr[3])] };
|
||||
|
||||
rgba.g = { sk_linear_from_srgb[SkGetPackedG32(ptr[0])],
|
||||
sk_linear_from_srgb[SkGetPackedG32(ptr[1])],
|
||||
sk_linear_from_srgb[SkGetPackedG32(ptr[2])],
|
||||
sk_linear_from_srgb[SkGetPackedG32(ptr[3])] };
|
||||
|
||||
rgba.b = { sk_linear_from_srgb[SkGetPackedB32(ptr[0])],
|
||||
sk_linear_from_srgb[SkGetPackedB32(ptr[1])],
|
||||
sk_linear_from_srgb[SkGetPackedB32(ptr[2])],
|
||||
sk_linear_from_srgb[SkGetPackedB32(ptr[3])] };
|
||||
|
||||
rgba.a = SkNx_cast<float>(Sk4i(SkGetPackedA32(ptr[0]), SkGetPackedA32(ptr[1]),
|
||||
SkGetPackedA32(ptr[2]), SkGetPackedA32(ptr[3]))) * (1/255.0f);
|
||||
#else
|
||||
rgba.r = { sk_linear_from_srgb[(ptr[0] >> 0) & 0xff],
|
||||
sk_linear_from_srgb[(ptr[1] >> 0) & 0xff],
|
||||
sk_linear_from_srgb[(ptr[2] >> 0) & 0xff],
|
||||
@@ -60,15 +79,28 @@ static Sk4x4f load_4_srgb(const void* vptr) {
|
||||
sk_linear_from_srgb[(ptr[3] >> 16) & 0xff] };
|
||||
|
||||
rgba.a = SkNx_cast<float>((Sk4i::Load(ptr) >> 24) & 0xff) * (1/255.0f);
|
||||
#endif
|
||||
|
||||
return rgba;
|
||||
}
|
||||
|
||||
static void store_4_srgb(void* ptr, const Sk4x4f& p) {
|
||||
#ifdef SK_PMCOLOR_IS_ARGB
|
||||
uint32_t* dst = (uint32_t*)ptr;
|
||||
Sk4i r = sk_linear_to_srgb(p.r);
|
||||
Sk4i g = sk_linear_to_srgb(p.g);
|
||||
Sk4i b = sk_linear_to_srgb(p.b);
|
||||
Sk4i a = Sk4f_round(255.0f*p.a);
|
||||
dst[0] = SkPackARGB_as_PMColor(a[0], r[0], g[0], b[0]);
|
||||
dst[1] = SkPackARGB_as_PMColor(a[1], r[1], g[1], b[1]);
|
||||
dst[2] = SkPackARGB_as_PMColor(a[2], r[2], g[2], b[2]);
|
||||
dst[3] = SkPackARGB_as_PMColor(a[3], r[3], g[3], b[3]);
|
||||
#else
|
||||
( sk_linear_to_srgb(p.r) << 0
|
||||
| sk_linear_to_srgb(p.g) << 8
|
||||
| sk_linear_to_srgb(p.b) << 16
|
||||
| Sk4f_round(255.0f*p.a) << 24).store(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -391,7 +423,7 @@ SkXfermode::D32Proc SkXfermode::GetD32Proc(SkXfermode* xfer, uint32_t flags) {
|
||||
#include "SkColorPriv.h"
|
||||
|
||||
static Sk4f lcd16_to_unit_4f(uint16_t rgb) {
|
||||
#ifdef SK_PMCOLOR_IS_RGBA
|
||||
#if defined(SK_PMCOLOR_IS_RGBA) || defined(SK_PMCOLOR_IS_ARGB)
|
||||
Sk4i rgbi = Sk4i(SkGetPackedR16(rgb), SkGetPackedG16(rgb), SkGetPackedB16(rgb), 0);
|
||||
#else
|
||||
Sk4i rgbi = Sk4i(SkGetPackedB16(rgb), SkGetPackedG16(rgb), SkGetPackedR16(rgb), 0);
|
||||
|
||||
@@ -251,7 +251,7 @@ bool GrDrawTarget::drawBatches(GrBatchFlushState* flushState) {
|
||||
if (currentRT) {
|
||||
static const GrGpuCommandBuffer::LoadAndStoreInfo kBasicLoadStoreInfo
|
||||
{ GrGpuCommandBuffer::LoadOp::kLoad,GrGpuCommandBuffer::StoreOp::kStore,
|
||||
GrColor_ILLEGAL };
|
||||
static_cast<GrColor>(GrColor_ILLEGAL) };
|
||||
commandBuffer.reset(fGpu->createCommandBuffer(currentRT,
|
||||
kBasicLoadStoreInfo, // Color
|
||||
kBasicLoadStoreInfo)); // Stencil
|
||||
|
||||
@@ -69,11 +69,18 @@ inline Sk4px Sk4px::Wide::div255() const {
|
||||
}
|
||||
|
||||
inline Sk4px Sk4px::alphas() const {
|
||||
#ifdef SK_PMCOLOR_IS_ARGB
|
||||
return Sk16b((*this)[ 0], (*this)[ 0], (*this)[ 0], (*this)[ 0],
|
||||
(*this)[ 4], (*this)[ 4], (*this)[ 4], (*this)[ 4],
|
||||
(*this)[ 8], (*this)[ 8], (*this)[ 8], (*this)[ 8],
|
||||
(*this)[12], (*this)[12], (*this)[12], (*this)[12]);
|
||||
#else
|
||||
static_assert(SK_A32_SHIFT == 24, "This method assumes little-endian.");
|
||||
return Sk16b((*this)[ 3], (*this)[ 3], (*this)[ 3], (*this)[ 3],
|
||||
(*this)[ 7], (*this)[ 7], (*this)[ 7], (*this)[ 7],
|
||||
(*this)[11], (*this)[11], (*this)[11], (*this)[11],
|
||||
(*this)[15], (*this)[15], (*this)[15], (*this)[15]);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline Sk4px Sk4px::Load4Alphas(const SkAlpha a[4]) {
|
||||
@@ -91,19 +98,33 @@ inline Sk4px Sk4px::Load2Alphas(const SkAlpha a[2]) {
|
||||
}
|
||||
|
||||
inline Sk4px Sk4px::zeroAlphas() const {
|
||||
#ifdef SK_PMCOLOR_IS_ARGB
|
||||
return Sk16b(0, (*this)[ 1], (*this)[ 2], (*this)[ 3],
|
||||
0, (*this)[ 5], (*this)[ 6], (*this)[ 7],
|
||||
0, (*this)[ 9], (*this)[10], (*this)[11],
|
||||
0, (*this)[13], (*this)[14], (*this)[15]);
|
||||
#else
|
||||
static_assert(SK_A32_SHIFT == 24, "This method assumes little-endian.");
|
||||
return Sk16b((*this)[ 0], (*this)[ 1], (*this)[ 2], 0,
|
||||
(*this)[ 4], (*this)[ 5], (*this)[ 6], 0,
|
||||
(*this)[ 8], (*this)[ 9], (*this)[10], 0,
|
||||
(*this)[12], (*this)[13], (*this)[14], 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline Sk4px Sk4px::zeroColors() const {
|
||||
#ifdef SK_PMCOLOR_IS_ARGB
|
||||
return Sk16b((*this)[ 0], 0,0,0,
|
||||
(*this)[ 4], 0,0,0,
|
||||
(*this)[ 8], 0,0,0,
|
||||
(*this)[12], 0,0,0);
|
||||
#else
|
||||
static_assert(SK_A32_SHIFT == 24, "This method assumes little-endian.");
|
||||
return Sk16b(0,0,0, (*this)[ 3],
|
||||
0,0,0, (*this)[ 7],
|
||||
0,0,0, (*this)[11],
|
||||
0,0,0, (*this)[15]);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -72,6 +72,10 @@ static void color_cube_filter_span(const SkPMColor src[],
|
||||
// color is BGRA (SkColor order), dst is SkPMColor order, so may need to swap R+B.
|
||||
#if defined(SK_PMCOLOR_IS_RGBA)
|
||||
color = SkNx_shuffle<2,1,0,3>(color);
|
||||
#elif defined(SK_PMCOLOR_IS_ARGB)
|
||||
Sk4b bytes = SkNx_cast<uint8_t>(color);
|
||||
dst[i] = SkPackARGB32NoCheck(a, bytes[2], bytes[1], bytes[0]);
|
||||
continue;
|
||||
#endif
|
||||
uint8_t* dstBytes = (uint8_t*)(dst+i);
|
||||
SkNx_cast<uint8_t>(color).store(dstBytes);
|
||||
|
||||
@@ -73,7 +73,39 @@ static CFArrayRef SkCTFontManagerCopyAvailableFontFamilyNames() {
|
||||
#ifdef SK_BUILD_FOR_IOS
|
||||
return CFArrayCreate(nullptr, nullptr, 0, nullptr);
|
||||
#else
|
||||
#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
|
||||
return CTFontManagerCopyAvailableFontFamilyNames();
|
||||
#else
|
||||
CFMutableArrayRef familyNames =
|
||||
CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
|
||||
if (!familyNames) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ATSFontFamilyIterator iterator;
|
||||
OSStatus status = ATSFontFamilyIteratorCreate(kATSFontContextGlobal,
|
||||
nullptr,
|
||||
nullptr,
|
||||
kATSOptionFlagsDefaultScope,
|
||||
&iterator);
|
||||
if (status != noErr) {
|
||||
CFRelease(familyNames);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ATSFontFamilyRef family;
|
||||
while (ATSFontFamilyIteratorNext(iterator, &family) == noErr) {
|
||||
CFStringRef familyName = nullptr;
|
||||
if (ATSFontFamilyGetName(family, kATSOptionFlagsDefault, &familyName) == noErr &&
|
||||
familyName) {
|
||||
CFArrayAppendValue(familyNames, familyName);
|
||||
CFRelease(familyName);
|
||||
}
|
||||
}
|
||||
|
||||
ATSFontFamilyIteratorRelease(&iterator);
|
||||
return familyNames;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -908,6 +940,7 @@ CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph&
|
||||
|
||||
// Skia handles quantization and subpixel positioning,
|
||||
// so disable quantization and enabe subpixel positioning in CG.
|
||||
#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
|
||||
CGContextSetAllowsFontSubpixelQuantization(fCG, false);
|
||||
CGContextSetShouldSubpixelQuantizeFonts(fCG, false);
|
||||
|
||||
@@ -916,6 +949,7 @@ CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph&
|
||||
// then CG cannot draw the glyph in the correct location without subpixel positioning.
|
||||
CGContextSetAllowsFontSubpixelPositioning(fCG, true);
|
||||
CGContextSetShouldSubpixelPositionFonts(fCG, true);
|
||||
#endif
|
||||
|
||||
CGContextSetTextDrawingMode(fCG, kCGTextFill);
|
||||
|
||||
@@ -971,7 +1005,13 @@ CGRGBPixel* Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph&
|
||||
// So always make the font transform identity and place the transform on the context.
|
||||
point = CGPointApplyAffineTransform(point, context.fInvTransform);
|
||||
|
||||
#if defined(MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
|
||||
CTFontDrawGlyphs(context.fCTFont, &glyphID, &point, 1, fCG);
|
||||
#else
|
||||
CGContextSetFont(fCG, CTFontCopyGraphicsFont(context.fCTFont, NULL));
|
||||
CGContextSetFontSize(fCG, CTFontGetSize(context.fCTFont));
|
||||
CGContextShowGlyphsAtPositions(fCG, &glyphID, &point, 1);
|
||||
#endif
|
||||
|
||||
SkASSERT(rowBytesPtr);
|
||||
*rowBytesPtr = rowBytes;
|
||||
@@ -1682,6 +1722,7 @@ SkAdvancedTypefaceMetrics* SkTypeface_Mac::onGetAdvancedTypefaceMetrics(
|
||||
|
||||
static SK_SFNT_ULONG get_font_type_tag(const SkTypeface_Mac* typeface) {
|
||||
CTFontRef ctFont = typeface->fFontRef.get();
|
||||
#if defined(MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
|
||||
AutoCFRelease<CFNumberRef> fontFormatRef(
|
||||
static_cast<CFNumberRef>(CTFontCopyAttribute(ctFont, kCTFontFormatAttribute)));
|
||||
if (!fontFormatRef) {
|
||||
@@ -1711,6 +1752,35 @@ static SK_SFNT_ULONG get_font_type_tag(const SkTypeface_Mac* typeface) {
|
||||
//Just the presence of the FontForge 'FFTM' table seems to throw it off.
|
||||
return SkSFNTHeader::fontType_WindowsTrueType::TAG;
|
||||
}
|
||||
#else
|
||||
AutoCFRelease<CFStringRef> postScriptName(CTFontCopyPostScriptName(ctFont));
|
||||
if (!postScriptName) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ATSFontRef atsFont =
|
||||
ATSFontFindFromPostScriptName(postScriptName, kATSOptionFlagsDefault);
|
||||
if (atsFont == kInvalidFont) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SkSFNTHeader header;
|
||||
ByteCount headerSize = sizeof(header);
|
||||
if (ATSFontGetTableDirectory(atsFont, headerSize, &header, &headerSize) != noErr ||
|
||||
headerSize < sizeof(header.fontType)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (header.fontType) {
|
||||
case SkSFNTHeader::fontType_OpenTypeCFF::TAG:
|
||||
case SkSFNTHeader::fontType_WindowsTrueType::TAG:
|
||||
case SkSFNTHeader::fontType_MacTrueType::TAG:
|
||||
case SkSFNTHeader::fontType_PostScript::TAG:
|
||||
return header.fontType;
|
||||
default:
|
||||
return SkSFNTHeader::fontType_WindowsTrueType::TAG;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SkStreamAsset* SkTypeface_Mac::onOpenStream(int* ttcIndex) const {
|
||||
|
||||
@@ -32,9 +32,6 @@
|
||||
#include <gdk/gdk.h>
|
||||
#include "gfxPlatformGtk.h"
|
||||
#endif
|
||||
#ifdef MOZ_WIDGET_HEADLESS
|
||||
#include "gfxPlatformHeadless.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_X11
|
||||
#include "mozilla/X11Util.h"
|
||||
@@ -1767,11 +1764,7 @@ gfxFcPlatformFontList::FindGenericFamilies(const nsAString& aGeneric,
|
||||
|
||||
// -- select the fonts to be used for the generic
|
||||
prefFonts = new PrefFontList; // can be empty but in practice won't happen
|
||||
#ifdef MOZ_WIDGET_HEADLESS
|
||||
uint32_t limit = gfxPlatformHeadless::GetPlatform()->MaxGenericSubstitions();
|
||||
#else
|
||||
uint32_t limit = gfxPlatformGtk::GetPlatform()->MaxGenericSubstitions();
|
||||
#endif
|
||||
bool foundFontWithLang = false;
|
||||
for (int i = 0; i < faces->nfont; i++) {
|
||||
FcPattern* font = faces->fonts[i];
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
#elif defined(XP_MACOSX)
|
||||
#include "gfxPlatformMac.h"
|
||||
#include "gfxQuartzSurface.h"
|
||||
#elif defined(MOZ_WIDGET_HEADLESS)
|
||||
#include "gfxPlatformHeadless.h"
|
||||
#elif defined(MOZ_WIDGET_GTK)
|
||||
#include "gfxPlatformGtk.h"
|
||||
#elif defined(ANDROID)
|
||||
@@ -574,8 +572,6 @@ gfxPlatform::Init()
|
||||
gPlatform = new gfxWindowsPlatform;
|
||||
#elif defined(XP_MACOSX)
|
||||
gPlatform = new gfxPlatformMac;
|
||||
#elif defined(MOZ_WIDGET_HEADLESS)
|
||||
gPlatform = new gfxPlatformHeadless;
|
||||
#elif defined(MOZ_WIDGET_GTK)
|
||||
gPlatform = new gfxPlatformGtk;
|
||||
#elif defined(ANDROID)
|
||||
@@ -2170,13 +2166,6 @@ gfxPlatform::UsesOffMainThreadCompositing()
|
||||
result |= gfxPrefs::LayersAccelerationForceEnabledDoNotUseDirectly();
|
||||
|
||||
#endif
|
||||
// Jihad Browser: the headless BrowserServer embedder has no compositor
|
||||
// process; allow forcing off-main-thread compositing off (use the in-process
|
||||
// BasicLayerManager) via env, since the gfxPrefs "Once" snapshot can miss a
|
||||
// grepref set during XRE_InitEmbedding2 init ordering.
|
||||
if (getenv("JIHAD_DISABLE_OMTC")) {
|
||||
result = false;
|
||||
}
|
||||
firstTime = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,17 +72,7 @@ static cairo_user_data_key_t cairo_gdk_drawable_key;
|
||||
|
||||
gfxPlatformGtk::gfxPlatformGtk()
|
||||
{
|
||||
// Jihad: in JIHAD_OFFSCREEN (headless daemon) mode there is no X server on the
|
||||
// device. gtk_init() calls exit() if it cannot open a display; gtk_init_check()
|
||||
// returns false instead and leaves GTK usable for the non-X paths (fontconfig/
|
||||
// freetype font list, image/CPU surfaces via the BasicLayerManager). The X code
|
||||
// below is already guarded by GDK_IS_X11_DISPLAY(gdk_display_get_default()),
|
||||
// which is false with no display, so it is skipped cleanly.
|
||||
if (getenv("JIHAD_OFFSCREEN")) {
|
||||
gtk_init_check(nullptr, nullptr);
|
||||
} else {
|
||||
gtk_init(nullptr, nullptr);
|
||||
}
|
||||
gtk_init(nullptr, nullptr);
|
||||
|
||||
mMaxGenericSubstitutions = UNINITIALIZED_VALUE;
|
||||
|
||||
@@ -335,10 +325,8 @@ gfxPlatformGtk::GetDPI()
|
||||
if (!sDPI) {
|
||||
// Make sure init is run so we have a resolution
|
||||
GdkScreen *screen = gdk_screen_get_default();
|
||||
if (screen) { // null when headless (no X display)
|
||||
gtk_settings_get_for_screen(screen);
|
||||
sDPI = int32_t(round(gdk_screen_get_resolution(screen)));
|
||||
}
|
||||
gtk_settings_get_for_screen(screen);
|
||||
sDPI = int32_t(round(gdk_screen_get_resolution(screen)));
|
||||
if (sDPI <= 0) {
|
||||
// Fall back to something sane
|
||||
sDPI = 96;
|
||||
|
||||
@@ -1,834 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* 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/. */
|
||||
|
||||
#define PANGO_ENABLE_BACKEND
|
||||
#define PANGO_ENABLE_ENGINE
|
||||
|
||||
#include "gfxPlatformHeadless.h"
|
||||
#include "prenv.h"
|
||||
|
||||
#include "nsUnicharUtils.h"
|
||||
#include "nsUnicodeProperties.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "gfxFcPlatformFontList.h"
|
||||
#include "gfxConfig.h"
|
||||
#include "gfxContext.h"
|
||||
#include "gfxUserFontSet.h"
|
||||
#include "gfxUtils.h"
|
||||
#include "gfxFT2FontBase.h"
|
||||
#include "gfxPrefs.h"
|
||||
#include "gfxTextRun.h"
|
||||
#include "VsyncSource.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "base/task.h"
|
||||
#include "base/thread.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "mozilla/gfx/Logging.h"
|
||||
|
||||
#include "mozilla/gfx/2D.h"
|
||||
|
||||
#include "cairo.h"
|
||||
|
||||
|
||||
#include "gfxImageSurface.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#ifdef MOZ_X11
|
||||
|
||||
#include "gfxXlibSurface.h"
|
||||
#include "cairo-xlib.h"
|
||||
#include "mozilla/X11Util.h"
|
||||
|
||||
#ifdef GL_PROVIDER_GLX
|
||||
#include "GLContextProvider.h"
|
||||
#include "GLContextGLX.h"
|
||||
#include "GLXLibrary.h"
|
||||
#endif
|
||||
|
||||
/* Undefine the Status from Xlib since it will conflict with system headers on OSX */
|
||||
#if defined(__APPLE__) && defined(Status)
|
||||
#undef Status
|
||||
#endif
|
||||
|
||||
#endif /* MOZ_X11 */
|
||||
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
#include "nsMathUtils.h"
|
||||
|
||||
#define GDK_PIXMAP_SIZE_MAX 32767
|
||||
|
||||
#define GFX_PREF_MAX_GENERIC_SUBSTITUTIONS "gfx.font_rendering.fontconfig.max_generic_substitutions"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::unicode;
|
||||
|
||||
#if (MOZ_WIDGET_GTK == 2)
|
||||
static cairo_user_data_key_t cairo_gdk_drawable_key;
|
||||
#endif
|
||||
|
||||
gfxPlatformHeadless::gfxPlatformHeadless()
|
||||
{
|
||||
// Jihad: in JIHAD_OFFSCREEN (headless daemon) mode there is no X server on the
|
||||
// device. gtk_init() calls exit() if it cannot open a display; gtk_init_check()
|
||||
// returns false instead and leaves GTK usable for the non-X paths (fontconfig/
|
||||
// freetype font list, image/CPU surfaces via the BasicLayerManager). The X code
|
||||
// below is already guarded by GDK_IS_X11_DISPLAY(gdk_display_get_default()),
|
||||
// which is false with no display, so it is skipped cleanly.
|
||||
/* headless: no gtk_init */
|
||||
|
||||
mMaxGenericSubstitutions = UNINITIALIZED_VALUE;
|
||||
|
||||
#ifdef MOZ_X11
|
||||
if (XRE_IsParentProcess()) {
|
||||
if (GDK_IS_X11_DISPLAY(gdk_display_get_default()) &&
|
||||
mozilla::Preferences::GetBool("gfx.xrender.enabled"))
|
||||
{
|
||||
gfxVars::SetUseXRender(true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO);
|
||||
uint32_t contentMask = BackendTypeBit(BackendType::CAIRO);
|
||||
#ifdef USE_SKIA
|
||||
canvasMask |= BackendTypeBit(BackendType::SKIA);
|
||||
contentMask |= BackendTypeBit(BackendType::SKIA);
|
||||
#endif
|
||||
InitBackendPrefs(canvasMask, BackendType::CAIRO,
|
||||
contentMask, BackendType::CAIRO);
|
||||
|
||||
#ifdef MOZ_X11
|
||||
if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
|
||||
mCompositorDisplay = XOpenDisplay(nullptr);
|
||||
MOZ_ASSERT(mCompositorDisplay, "Failed to create compositor display!");
|
||||
} else {
|
||||
mCompositorDisplay = nullptr;
|
||||
}
|
||||
#endif // MOZ_X11
|
||||
}
|
||||
|
||||
gfxPlatformHeadless::~gfxPlatformHeadless()
|
||||
{
|
||||
#ifdef MOZ_X11
|
||||
if (mCompositorDisplay) {
|
||||
XCloseDisplay(mCompositorDisplay);
|
||||
}
|
||||
#endif // MOZ_X11
|
||||
}
|
||||
|
||||
void
|
||||
gfxPlatformHeadless::FlushContentDrawing()
|
||||
{
|
||||
#ifdef MOZ_X11
|
||||
if (gfxVars::UseXRender()) {
|
||||
XFlush(DefaultXDisplay());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
already_AddRefed<gfxASurface>
|
||||
gfxPlatformHeadless::CreateOffscreenSurface(const IntSize& aSize,
|
||||
gfxImageFormat aFormat)
|
||||
{
|
||||
if (!Factory::AllowedSurfaceSize(aSize)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<gfxASurface> newSurface;
|
||||
bool needsClear = true;
|
||||
#ifdef MOZ_X11
|
||||
// XXX we really need a different interface here, something that passes
|
||||
// in more context, including the display and/or target surface type that
|
||||
// we should try to match
|
||||
GdkScreen *gdkScreen = gdk_screen_get_default();
|
||||
if (gdkScreen) {
|
||||
// When forcing PaintedLayers to use image surfaces for content,
|
||||
// force creation of gfxImageSurface surfaces.
|
||||
if (gfxVars::UseXRender()) {
|
||||
Screen *screen = gdk_x11_screen_get_xscreen(gdkScreen);
|
||||
XRenderPictFormat* xrenderFormat =
|
||||
gfxXlibSurface::FindRenderFormat(DisplayOfScreen(screen),
|
||||
aFormat);
|
||||
|
||||
if (xrenderFormat) {
|
||||
newSurface = gfxXlibSurface::Create(screen, xrenderFormat,
|
||||
aSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!newSurface) {
|
||||
// We couldn't create a native surface for whatever reason;
|
||||
// e.g., no display, no RENDER, bad size, etc.
|
||||
// Fall back to image surface for the data.
|
||||
newSurface = new gfxImageSurface(aSize, aFormat);
|
||||
|
||||
// The gfxImageSurface ctor zeroes this for us, no need to
|
||||
// waste time clearing again
|
||||
needsClear = false;
|
||||
}
|
||||
|
||||
if (newSurface->CairoStatus()) {
|
||||
newSurface = nullptr; // surface isn't valid for some reason
|
||||
}
|
||||
|
||||
if (newSurface && needsClear) {
|
||||
gfxUtils::ClearThebesSurface(newSurface);
|
||||
}
|
||||
|
||||
return newSurface.forget();
|
||||
}
|
||||
|
||||
nsresult
|
||||
gfxPlatformHeadless::GetFontList(nsIAtom *aLangGroup,
|
||||
const nsACString& aGenericFamily,
|
||||
nsTArray<nsString>& aListOfFonts)
|
||||
{
|
||||
gfxPlatformFontList::PlatformFontList()->GetFontList(aLangGroup,
|
||||
aGenericFamily,
|
||||
aListOfFonts);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
gfxPlatformHeadless::UpdateFontList()
|
||||
{
|
||||
gfxPlatformFontList::PlatformFontList()->UpdateFontList();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// xxx - this is ubuntu centric, need to go through other distros and flesh
|
||||
// out a more general list
|
||||
static const char kFontDejaVuSans[] = "DejaVu Sans";
|
||||
static const char kFontDejaVuSerif[] = "DejaVu Serif";
|
||||
static const char kFontFreeSans[] = "FreeSans";
|
||||
static const char kFontFreeSerif[] = "FreeSerif";
|
||||
static const char kFontTakaoPGothic[] = "TakaoPGothic";
|
||||
static const char kFontTwemojiMozilla[] = "Twemoji Mozilla";
|
||||
static const char kFontDroidSansFallback[] = "Droid Sans Fallback";
|
||||
static const char kFontWenQuanYiMicroHei[] = "WenQuanYi Micro Hei";
|
||||
static const char kFontNanumGothic[] = "NanumGothic";
|
||||
|
||||
void
|
||||
gfxPlatformHeadless::GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
|
||||
Script aRunScript,
|
||||
nsTArray<const char*>& aFontList)
|
||||
{
|
||||
EmojiPresentation emoji = GetEmojiPresentation(aCh);
|
||||
EmojiPresentation eNext = GetEmojiPresentation(aNextCh, true);
|
||||
if (aNextCh != kVariationSelector15 &&
|
||||
emoji != EmojiPresentation::TextOnly &&
|
||||
(emoji != EmojiPresentation::TextDefault ||
|
||||
eNext == EmojiPresentation::EmojiComponent)) {
|
||||
aFontList.AppendElement(kFontTwemojiMozilla);
|
||||
}
|
||||
|
||||
aFontList.AppendElement(kFontDejaVuSerif);
|
||||
aFontList.AppendElement(kFontFreeSerif);
|
||||
aFontList.AppendElement(kFontDejaVuSans);
|
||||
aFontList.AppendElement(kFontFreeSans);
|
||||
|
||||
// add fonts for CJK ranges
|
||||
// xxx - this isn't really correct, should use the same CJK font ordering
|
||||
// as the pref font code
|
||||
if (aCh >= 0x3000 &&
|
||||
((aCh < 0xe000) ||
|
||||
(aCh >= 0xf900 && aCh < 0xfff0) ||
|
||||
((aCh >> 16) == 2))) {
|
||||
aFontList.AppendElement(kFontTakaoPGothic);
|
||||
aFontList.AppendElement(kFontDroidSansFallback);
|
||||
aFontList.AppendElement(kFontWenQuanYiMicroHei);
|
||||
aFontList.AppendElement(kFontNanumGothic);
|
||||
}
|
||||
}
|
||||
|
||||
gfxPlatformFontList*
|
||||
gfxPlatformHeadless::CreatePlatformFontList()
|
||||
{
|
||||
gfxPlatformFontList* list = new gfxFcPlatformFontList();
|
||||
if (NS_SUCCEEDED(list->InitFontList())) {
|
||||
return list;
|
||||
}
|
||||
gfxPlatformFontList::Shutdown();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsresult
|
||||
gfxPlatformHeadless::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
|
||||
{
|
||||
gfxPlatformFontList::PlatformFontList()->
|
||||
GetStandardFamilyName(aFontName, aFamilyName);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
gfxFontGroup *
|
||||
gfxPlatformHeadless::CreateFontGroup(const FontFamilyList& aFontFamilyList,
|
||||
const gfxFontStyle* aStyle,
|
||||
gfxTextPerfMetrics* aTextPerf,
|
||||
gfxUserFontSet* aUserFontSet,
|
||||
gfxFloat aDevToCssSize)
|
||||
{
|
||||
return new gfxFontGroup(aFontFamilyList, aStyle, aTextPerf,
|
||||
aUserFontSet, aDevToCssSize);
|
||||
}
|
||||
|
||||
gfxFontEntry*
|
||||
gfxPlatformHeadless::LookupLocalFont(const nsAString& aFontName,
|
||||
uint16_t aWeight,
|
||||
int16_t aStretch,
|
||||
uint8_t aStyle)
|
||||
{
|
||||
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
||||
return pfl->LookupLocalFont(aFontName, aWeight, aStretch,
|
||||
aStyle);
|
||||
}
|
||||
|
||||
gfxFontEntry*
|
||||
gfxPlatformHeadless::MakePlatformFont(const nsAString& aFontName,
|
||||
uint16_t aWeight,
|
||||
int16_t aStretch,
|
||||
uint8_t aStyle,
|
||||
const uint8_t* aFontData,
|
||||
uint32_t aLength)
|
||||
{
|
||||
gfxPlatformFontList* pfl = gfxPlatformFontList::PlatformFontList();
|
||||
return pfl->MakePlatformFont(aFontName, aWeight, aStretch,
|
||||
aStyle, aFontData, aLength);
|
||||
}
|
||||
|
||||
bool
|
||||
gfxPlatformHeadless::IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags)
|
||||
{
|
||||
// check for strange format flags
|
||||
NS_ASSERTION(!(aFormatFlags & gfxUserFontSet::FLAG_FORMAT_NOT_USED),
|
||||
"strange font format hint set");
|
||||
|
||||
// accept supported formats
|
||||
// Pango doesn't apply features from AAT TrueType extensions.
|
||||
// Assume that if this is the only SFNT format specified,
|
||||
// then AAT extensions are required for complex script support.
|
||||
if (aFormatFlags & gfxUserFontSet::FLAG_FORMATS_COMMON) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// reject all other formats, known and unknown
|
||||
if (aFormatFlags != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// no format hint set, need to look at data
|
||||
return true;
|
||||
}
|
||||
|
||||
static int32_t sDPI = 0;
|
||||
|
||||
int32_t
|
||||
gfxPlatformHeadless::GetDPI()
|
||||
{
|
||||
if (!sDPI) {
|
||||
// Make sure init is run so we have a resolution
|
||||
sDPI = 96;
|
||||
if (sDPI <= 0) {
|
||||
// Fall back to something sane
|
||||
sDPI = 96;
|
||||
}
|
||||
}
|
||||
return sDPI;
|
||||
}
|
||||
|
||||
double
|
||||
gfxPlatformHeadless::GetDPIScale()
|
||||
{
|
||||
// Integer scale factors work well with GTK window scaling, image scaling,
|
||||
// and pixel alignment, but there is a range where 1 is too small and 2 is
|
||||
// too big. An additional step of 1.5 is added because this is common
|
||||
// scale on WINNT and at this ratio the advantages of larger rendering
|
||||
// outweigh the disadvantages from scaling and pixel mis-alignment.
|
||||
int32_t dpi = GetDPI();
|
||||
if (dpi < 144) {
|
||||
return 1.0;
|
||||
} else if (dpi < 168) {
|
||||
return 1.5;
|
||||
} else {
|
||||
return round(dpi/96.0);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
gfxPlatformHeadless::UseImageOffscreenSurfaces()
|
||||
{
|
||||
return GetDefaultContentBackend() != mozilla::gfx::BackendType::CAIRO ||
|
||||
gfxPrefs::UseImageOffscreenSurfaces();
|
||||
}
|
||||
|
||||
gfxImageFormat
|
||||
gfxPlatformHeadless::GetOffscreenFormat()
|
||||
{
|
||||
// Make sure there is a screen
|
||||
|
||||
|
||||
return SurfaceFormat::X8R8G8B8_UINT32;
|
||||
}
|
||||
|
||||
void gfxPlatformHeadless::FontsPrefsChanged(const char *aPref)
|
||||
{
|
||||
// only checking for generic substitions, pass other changes up
|
||||
if (strcmp(GFX_PREF_MAX_GENERIC_SUBSTITUTIONS, aPref)) {
|
||||
gfxPlatform::FontsPrefsChanged(aPref);
|
||||
return;
|
||||
}
|
||||
|
||||
mMaxGenericSubstitutions = UNINITIALIZED_VALUE;
|
||||
gfxFcPlatformFontList* pfl = gfxFcPlatformFontList::PlatformFontList();
|
||||
pfl->ClearGenericMappings();
|
||||
FlushFontAndWordCaches();
|
||||
}
|
||||
|
||||
uint32_t gfxPlatformHeadless::MaxGenericSubstitions()
|
||||
{
|
||||
if (mMaxGenericSubstitutions == UNINITIALIZED_VALUE) {
|
||||
mMaxGenericSubstitutions =
|
||||
Preferences::GetInt(GFX_PREF_MAX_GENERIC_SUBSTITUTIONS, 3);
|
||||
if (mMaxGenericSubstitutions < 0) {
|
||||
mMaxGenericSubstitutions = 3;
|
||||
}
|
||||
}
|
||||
|
||||
return uint32_t(mMaxGenericSubstitutions);
|
||||
}
|
||||
|
||||
void
|
||||
gfxPlatformHeadless::GetPlatformCMSOutputProfile(void *&mem, size_t &size)
|
||||
{
|
||||
mem = nullptr;
|
||||
size = 0;
|
||||
|
||||
#ifdef MOZ_X11
|
||||
GdkDisplay *display = gdk_display_get_default();
|
||||
if (!GDK_IS_X11_DISPLAY(display))
|
||||
return;
|
||||
|
||||
const char EDID1_ATOM_NAME[] = "XFree86_DDC_EDID1_RAWDATA";
|
||||
const char ICC_PROFILE_ATOM_NAME[] = "_ICC_PROFILE";
|
||||
|
||||
Atom edidAtom, iccAtom;
|
||||
Display *dpy = GDK_DISPLAY_XDISPLAY(display);
|
||||
// In xpcshell tests, we never initialize X and hence don't have a Display.
|
||||
// In this case, there's no output colour management to be done, so we just
|
||||
// return with nullptr.
|
||||
if (!dpy)
|
||||
return;
|
||||
|
||||
Window root = gdk_x11_get_default_root_xwindow();
|
||||
|
||||
Atom retAtom;
|
||||
int retFormat;
|
||||
unsigned long retLength, retAfter;
|
||||
unsigned char *retProperty ;
|
||||
|
||||
iccAtom = XInternAtom(dpy, ICC_PROFILE_ATOM_NAME, TRUE);
|
||||
if (iccAtom) {
|
||||
// read once to get size, once for the data
|
||||
if (Success == XGetWindowProperty(dpy, root, iccAtom,
|
||||
0, INT_MAX /* length */,
|
||||
False, AnyPropertyType,
|
||||
&retAtom, &retFormat, &retLength,
|
||||
&retAfter, &retProperty)) {
|
||||
|
||||
if (retLength > 0) {
|
||||
void *buffer = malloc(retLength);
|
||||
if (buffer) {
|
||||
memcpy(buffer, retProperty, retLength);
|
||||
mem = buffer;
|
||||
size = retLength;
|
||||
}
|
||||
}
|
||||
|
||||
XFree(retProperty);
|
||||
if (size > 0) {
|
||||
#ifdef DEBUG_tor
|
||||
fprintf(stderr,
|
||||
"ICM profile read from %s successfully\n",
|
||||
ICC_PROFILE_ATOM_NAME);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
edidAtom = XInternAtom(dpy, EDID1_ATOM_NAME, TRUE);
|
||||
if (edidAtom) {
|
||||
if (Success == XGetWindowProperty(dpy, root, edidAtom, 0, 32,
|
||||
False, AnyPropertyType,
|
||||
&retAtom, &retFormat, &retLength,
|
||||
&retAfter, &retProperty)) {
|
||||
double gamma;
|
||||
qcms_CIE_xyY whitePoint;
|
||||
qcms_CIE_xyYTRIPLE primaries;
|
||||
|
||||
if (retLength != 128) {
|
||||
#ifdef DEBUG_tor
|
||||
fprintf(stderr, "Short EDID data\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Format documented in "VESA E-EDID Implementation Guide"
|
||||
|
||||
gamma = (100 + retProperty[0x17]) / 100.0;
|
||||
whitePoint.x = ((retProperty[0x21] << 2) |
|
||||
(retProperty[0x1a] >> 2 & 3)) / 1024.0;
|
||||
whitePoint.y = ((retProperty[0x22] << 2) |
|
||||
(retProperty[0x1a] >> 0 & 3)) / 1024.0;
|
||||
whitePoint.Y = 1.0;
|
||||
|
||||
primaries.red.x = ((retProperty[0x1b] << 2) |
|
||||
(retProperty[0x19] >> 6 & 3)) / 1024.0;
|
||||
primaries.red.y = ((retProperty[0x1c] << 2) |
|
||||
(retProperty[0x19] >> 4 & 3)) / 1024.0;
|
||||
primaries.red.Y = 1.0;
|
||||
|
||||
primaries.green.x = ((retProperty[0x1d] << 2) |
|
||||
(retProperty[0x19] >> 2 & 3)) / 1024.0;
|
||||
primaries.green.y = ((retProperty[0x1e] << 2) |
|
||||
(retProperty[0x19] >> 0 & 3)) / 1024.0;
|
||||
primaries.green.Y = 1.0;
|
||||
|
||||
primaries.blue.x = ((retProperty[0x1f] << 2) |
|
||||
(retProperty[0x1a] >> 6 & 3)) / 1024.0;
|
||||
primaries.blue.y = ((retProperty[0x20] << 2) |
|
||||
(retProperty[0x1a] >> 4 & 3)) / 1024.0;
|
||||
primaries.blue.Y = 1.0;
|
||||
|
||||
XFree(retProperty);
|
||||
|
||||
#ifdef DEBUG_tor
|
||||
fprintf(stderr, "EDID gamma: %f\n", gamma);
|
||||
fprintf(stderr, "EDID whitepoint: %f %f %f\n",
|
||||
whitePoint.x, whitePoint.y, whitePoint.Y);
|
||||
fprintf(stderr, "EDID primaries: [%f %f %f] [%f %f %f] [%f %f %f]\n",
|
||||
primaries.Red.x, primaries.Red.y, primaries.Red.Y,
|
||||
primaries.Green.x, primaries.Green.y, primaries.Green.Y,
|
||||
primaries.Blue.x, primaries.Blue.y, primaries.Blue.Y);
|
||||
#endif
|
||||
|
||||
qcms_data_create_rgb_with_gamma(whitePoint, primaries, gamma, &mem, &size);
|
||||
|
||||
#ifdef DEBUG_tor
|
||||
if (size > 0) {
|
||||
fprintf(stderr,
|
||||
"ICM profile read from %s successfully\n",
|
||||
EDID1_ATOM_NAME);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#if (MOZ_WIDGET_GTK == 2)
|
||||
void
|
||||
gfxPlatformHeadless::SetGdkDrawable(cairo_surface_t *target,
|
||||
GdkDrawable *drawable)
|
||||
{
|
||||
if (cairo_surface_status(target))
|
||||
return;
|
||||
|
||||
g_object_ref(drawable);
|
||||
|
||||
cairo_surface_set_user_data (target,
|
||||
&cairo_gdk_drawable_key,
|
||||
drawable,
|
||||
g_object_unref);
|
||||
}
|
||||
|
||||
GdkDrawable *
|
||||
gfxPlatformHeadless::GetGdkDrawable(cairo_surface_t *target)
|
||||
{
|
||||
if (cairo_surface_status(target))
|
||||
return nullptr;
|
||||
|
||||
GdkDrawable *result;
|
||||
|
||||
result = (GdkDrawable*) cairo_surface_get_user_data (target,
|
||||
&cairo_gdk_drawable_key);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
#ifdef MOZ_X11
|
||||
if (cairo_surface_get_type(target) != CAIRO_SURFACE_TYPE_XLIB)
|
||||
return nullptr;
|
||||
|
||||
// try looking it up in gdk's table
|
||||
result = (GdkDrawable*) gdk_xid_table_lookup(cairo_xlib_surface_get_drawable(target));
|
||||
if (result) {
|
||||
SetGdkDrawable(target, result);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef GL_PROVIDER_GLX
|
||||
|
||||
class GLXVsyncSource final : public VsyncSource
|
||||
{
|
||||
public:
|
||||
GLXVsyncSource()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mGlobalDisplay = new GLXDisplay();
|
||||
}
|
||||
|
||||
virtual ~GLXVsyncSource()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
virtual Display& GetGlobalDisplay() override
|
||||
{
|
||||
return *mGlobalDisplay;
|
||||
}
|
||||
|
||||
class GLXDisplay final : public VsyncSource::Display
|
||||
{
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GLXDisplay)
|
||||
|
||||
public:
|
||||
GLXDisplay() : mGLContext(nullptr)
|
||||
, mXDisplay(nullptr)
|
||||
, mSetupLock("GLXVsyncSetupLock")
|
||||
, mVsyncThread("GLXVsyncThread")
|
||||
, mVsyncTask(nullptr)
|
||||
, mVsyncEnabledLock("GLXVsyncEnabledLock")
|
||||
, mVsyncEnabled(false)
|
||||
{
|
||||
}
|
||||
|
||||
// Sets up the display's GL context on a worker thread.
|
||||
// Required as GLContexts may only be used by the creating thread.
|
||||
// Returns true if setup was a success.
|
||||
bool Setup()
|
||||
{
|
||||
MonitorAutoLock lock(mSetupLock);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!mVsyncThread.Start())
|
||||
return false;
|
||||
|
||||
RefPtr<Runnable> vsyncSetup = NewRunnableMethod(this, &GLXDisplay::SetupGLContext);
|
||||
mVsyncThread.message_loop()->PostTask(vsyncSetup.forget());
|
||||
// Wait until the setup has completed.
|
||||
lock.Wait();
|
||||
return mGLContext != nullptr;
|
||||
}
|
||||
|
||||
// Called on the Vsync thread to setup the GL context.
|
||||
void SetupGLContext()
|
||||
{
|
||||
MonitorAutoLock lock(mSetupLock);
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!mGLContext, "GLContext already setup!");
|
||||
|
||||
// Create video sync timer on a separate Display to prevent locking the
|
||||
// main thread X display.
|
||||
mXDisplay = XOpenDisplay(nullptr);
|
||||
if (!mXDisplay) {
|
||||
lock.NotifyAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Most compositors wait for vsync events on the root window.
|
||||
Window root = DefaultRootWindow(mXDisplay);
|
||||
int screen = DefaultScreen(mXDisplay);
|
||||
|
||||
ScopedXFree<GLXFBConfig> cfgs;
|
||||
GLXFBConfig config;
|
||||
int visid;
|
||||
if (!gl::GLContextGLX::FindFBConfigForWindow(mXDisplay, screen, root,
|
||||
&cfgs, &config, &visid)) {
|
||||
lock.NotifyAll();
|
||||
return;
|
||||
}
|
||||
|
||||
mGLContext = gl::GLContextGLX::CreateGLContext(
|
||||
gl::CreateContextFlags::NONE,
|
||||
gl::SurfaceCaps::Any(),
|
||||
nullptr,
|
||||
false,
|
||||
mXDisplay,
|
||||
root,
|
||||
config);
|
||||
|
||||
if (!mGLContext) {
|
||||
lock.NotifyAll();
|
||||
return;
|
||||
}
|
||||
|
||||
mGLContext->MakeCurrent();
|
||||
|
||||
// Test that SGI_video_sync lets us get the counter.
|
||||
unsigned int syncCounter = 0;
|
||||
if (gl::sGLXLibrary.xGetVideoSync(&syncCounter) != 0) {
|
||||
mGLContext = nullptr;
|
||||
}
|
||||
|
||||
lock.NotifyAll();
|
||||
}
|
||||
|
||||
virtual void EnableVsync() override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mGLContext, "GLContext not setup!");
|
||||
|
||||
MonitorAutoLock lock(mVsyncEnabledLock);
|
||||
if (mVsyncEnabled) {
|
||||
return;
|
||||
}
|
||||
mVsyncEnabled = true;
|
||||
|
||||
// If the task has not nulled itself out, it hasn't yet realized
|
||||
// that vsync was disabled earlier, so continue its execution.
|
||||
if (!mVsyncTask) {
|
||||
mVsyncTask = NewRunnableMethod(this, &GLXDisplay::RunVsync);
|
||||
RefPtr<Runnable> addrefedTask = mVsyncTask;
|
||||
mVsyncThread.message_loop()->PostTask(addrefedTask.forget());
|
||||
}
|
||||
}
|
||||
|
||||
virtual void DisableVsync() override
|
||||
{
|
||||
MonitorAutoLock lock(mVsyncEnabledLock);
|
||||
mVsyncEnabled = false;
|
||||
}
|
||||
|
||||
virtual bool IsVsyncEnabled() override
|
||||
{
|
||||
MonitorAutoLock lock(mVsyncEnabledLock);
|
||||
return mVsyncEnabled;
|
||||
}
|
||||
|
||||
virtual void Shutdown() override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
DisableVsync();
|
||||
|
||||
// Cleanup thread-specific resources before shutting down.
|
||||
RefPtr<Runnable> shutdownTask = NewRunnableMethod(this, &GLXDisplay::Cleanup);
|
||||
mVsyncThread.message_loop()->PostTask(shutdownTask.forget());
|
||||
|
||||
// Stop, waiting for the cleanup task to finish execution.
|
||||
mVsyncThread.Stop();
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~GLXDisplay()
|
||||
{
|
||||
}
|
||||
|
||||
void RunVsync()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
mGLContext->MakeCurrent();
|
||||
|
||||
unsigned int syncCounter = 0;
|
||||
gl::sGLXLibrary.xGetVideoSync(&syncCounter);
|
||||
for (;;) {
|
||||
{
|
||||
MonitorAutoLock lock(mVsyncEnabledLock);
|
||||
if (!mVsyncEnabled) {
|
||||
mVsyncTask = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TimeStamp lastVsync = TimeStamp::Now();
|
||||
bool useSoftware = false;
|
||||
|
||||
// Wait until the video sync counter reaches the next value by waiting
|
||||
// until the parity of the counter value changes.
|
||||
unsigned int nextSync = syncCounter + 1;
|
||||
int status;
|
||||
if ((status = gl::sGLXLibrary.xWaitVideoSync(2, nextSync % 2, &syncCounter)) != 0) {
|
||||
gfxWarningOnce() << "glXWaitVideoSync returned " << status;
|
||||
useSoftware = true;
|
||||
}
|
||||
|
||||
if (syncCounter == (nextSync - 1)) {
|
||||
gfxWarningOnce() << "glXWaitVideoSync failed to increment the sync counter.";
|
||||
useSoftware = true;
|
||||
}
|
||||
|
||||
if (useSoftware) {
|
||||
double remaining = (1000.f / 60.f) -
|
||||
(TimeStamp::Now() - lastVsync).ToMilliseconds();
|
||||
if (remaining > 0) {
|
||||
PlatformThread::Sleep(remaining);
|
||||
}
|
||||
}
|
||||
|
||||
lastVsync = TimeStamp::Now();
|
||||
NotifyVsync(lastVsync);
|
||||
}
|
||||
}
|
||||
|
||||
void Cleanup() {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
mGLContext = nullptr;
|
||||
XCloseDisplay(mXDisplay);
|
||||
}
|
||||
|
||||
// Owned by the vsync thread.
|
||||
RefPtr<gl::GLContextGLX> mGLContext;
|
||||
_XDisplay* mXDisplay;
|
||||
Monitor mSetupLock;
|
||||
base::Thread mVsyncThread;
|
||||
RefPtr<Runnable> mVsyncTask;
|
||||
Monitor mVsyncEnabledLock;
|
||||
bool mVsyncEnabled;
|
||||
};
|
||||
private:
|
||||
// We need a refcounted VsyncSource::Display to use chromium IPC runnables.
|
||||
RefPtr<GLXDisplay> mGlobalDisplay;
|
||||
};
|
||||
|
||||
already_AddRefed<gfx::VsyncSource>
|
||||
gfxPlatformHeadless::CreateHardwareVsyncSource()
|
||||
{
|
||||
// Only use GLX vsync when the OpenGL compositor is being used.
|
||||
// The extra cost of initializing a GLX context while blocking the main
|
||||
// thread is not worth it when using basic composition.
|
||||
if (gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
|
||||
if (gl::sGLXLibrary.SupportsVideoSync()) {
|
||||
RefPtr<VsyncSource> vsyncSource = new GLXVsyncSource();
|
||||
VsyncSource::Display& display = vsyncSource->GetGlobalDisplay();
|
||||
if (!static_cast<GLXVsyncSource::GLXDisplay&>(display).Setup()) {
|
||||
NS_WARNING("Failed to setup GLContext, falling back to software vsync.");
|
||||
return gfxPlatform::CreateHardwareVsyncSource();
|
||||
}
|
||||
return vsyncSource.forget();
|
||||
}
|
||||
NS_WARNING("SGI_video_sync unsupported. Falling back to software vsync.");
|
||||
}
|
||||
return gfxPlatform::CreateHardwareVsyncSource();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Not GLX-specific: must be defined regardless of GL_PROVIDER_GLX so the
|
||||
// vtable slot resolves in a headless (no X/GLX) build.
|
||||
bool
|
||||
gfxPlatformHeadless::SupportsApzTouchInput() const
|
||||
{
|
||||
int value = gfxPrefs::TouchEventsEnabled();
|
||||
return value == 1 || value == 2;
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef GFX_PLATFORM_GTK_H
|
||||
#define GFX_PLATFORM_GTK_H
|
||||
|
||||
#include "gfxPlatform.h"
|
||||
#include "nsAutoRef.h"
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/gfx/gfxVars.h"
|
||||
|
||||
#if (MOZ_WIDGET_GTK == 2)
|
||||
extern "C" {
|
||||
typedef struct _GdkDrawable GdkDrawable;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_X11
|
||||
struct _XDisplay;
|
||||
typedef struct _XDisplay Display;
|
||||
#endif // MOZ_X11
|
||||
|
||||
class gfxPlatformHeadless : public gfxPlatform {
|
||||
public:
|
||||
gfxPlatformHeadless();
|
||||
virtual ~gfxPlatformHeadless();
|
||||
|
||||
static gfxPlatformHeadless *GetPlatform() {
|
||||
return (gfxPlatformHeadless*) gfxPlatform::GetPlatform();
|
||||
}
|
||||
|
||||
virtual already_AddRefed<gfxASurface>
|
||||
CreateOffscreenSurface(const IntSize& aSize,
|
||||
gfxImageFormat aFormat) override;
|
||||
|
||||
virtual nsresult GetFontList(nsIAtom *aLangGroup,
|
||||
const nsACString& aGenericFamily,
|
||||
nsTArray<nsString>& aListOfFonts) override;
|
||||
|
||||
virtual nsresult UpdateFontList() override;
|
||||
|
||||
virtual void
|
||||
GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
|
||||
Script aRunScript,
|
||||
nsTArray<const char*>& aFontList) override;
|
||||
|
||||
virtual gfxPlatformFontList* CreatePlatformFontList() override;
|
||||
|
||||
virtual nsresult GetStandardFamilyName(const nsAString& aFontName,
|
||||
nsAString& aFamilyName) override;
|
||||
|
||||
gfxFontGroup*
|
||||
CreateFontGroup(const mozilla::FontFamilyList& aFontFamilyList,
|
||||
const gfxFontStyle *aStyle,
|
||||
gfxTextPerfMetrics* aTextPerf,
|
||||
gfxUserFontSet *aUserFontSet,
|
||||
gfxFloat aDevToCssSize) override;
|
||||
|
||||
/**
|
||||
* Look up a local platform font using the full font face name (needed to
|
||||
* support @font-face src local() )
|
||||
*/
|
||||
virtual gfxFontEntry* LookupLocalFont(const nsAString& aFontName,
|
||||
uint16_t aWeight,
|
||||
int16_t aStretch,
|
||||
uint8_t aStyle) override;
|
||||
|
||||
/**
|
||||
* Activate a platform font (needed to support @font-face src url() )
|
||||
*
|
||||
*/
|
||||
virtual gfxFontEntry* MakePlatformFont(const nsAString& aFontName,
|
||||
uint16_t aWeight,
|
||||
int16_t aStretch,
|
||||
uint8_t aStyle,
|
||||
const uint8_t* aFontData,
|
||||
uint32_t aLength) override;
|
||||
|
||||
/**
|
||||
* Check whether format is supported on a platform or not (if unclear,
|
||||
* returns true).
|
||||
*/
|
||||
virtual bool IsFontFormatSupported(nsIURI *aFontURI,
|
||||
uint32_t aFormatFlags) override;
|
||||
|
||||
/**
|
||||
* Calls XFlush if xrender is enabled.
|
||||
*/
|
||||
virtual void FlushContentDrawing() override;
|
||||
|
||||
#if (MOZ_WIDGET_GTK == 2)
|
||||
static void SetGdkDrawable(cairo_surface_t *target,
|
||||
GdkDrawable *drawable);
|
||||
static GdkDrawable *GetGdkDrawable(cairo_surface_t *target);
|
||||
#endif
|
||||
|
||||
static int32_t GetDPI();
|
||||
static double GetDPIScale();
|
||||
|
||||
#ifdef MOZ_X11
|
||||
virtual void GetAzureBackendInfo(mozilla::widget::InfoObject &aObj) override {
|
||||
gfxPlatform::GetAzureBackendInfo(aObj);
|
||||
aObj.DefineProperty("CairoUseXRender", mozilla::gfx::gfxVars::UseXRender());
|
||||
}
|
||||
#endif
|
||||
|
||||
bool UseImageOffscreenSurfaces();
|
||||
|
||||
virtual gfxImageFormat GetOffscreenFormat() override;
|
||||
|
||||
bool SupportsApzWheelInput() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SupportsApzTouchInput() const override;
|
||||
|
||||
void FontsPrefsChanged(const char *aPref) override;
|
||||
|
||||
// maximum number of fonts to substitute for a generic
|
||||
uint32_t MaxGenericSubstitions();
|
||||
|
||||
bool SupportsPluginDirectBitmapDrawing() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef GL_PROVIDER_GLX
|
||||
already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() override;
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_X11
|
||||
Display* GetCompositorDisplay() {
|
||||
return mCompositorDisplay;
|
||||
}
|
||||
#endif // MOZ_X11
|
||||
|
||||
protected:
|
||||
int8_t mMaxGenericSubstitutions;
|
||||
|
||||
private:
|
||||
virtual void GetPlatformCMSOutputProfile(void *&mem,
|
||||
size_t &size) override;
|
||||
|
||||
#ifdef MOZ_X11
|
||||
Display* mCompositorDisplay;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /* GFX_PLATFORM_GTK_H */
|
||||
@@ -74,22 +74,6 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
|
||||
'gfxQuartzSurface.cpp',
|
||||
'PrintTargetCG.cpp',
|
||||
]
|
||||
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'headless':
|
||||
# Jihad headless (no X/GTK): fontconfig/freetype fonts + a memory gfxPlatform.
|
||||
EXPORTS += [
|
||||
'gfxFT2FontBase.h',
|
||||
'gfxPlatformHeadless.h',
|
||||
]
|
||||
EXPORTS.mozilla.gfx += [
|
||||
'PrintTargetPDF.h',
|
||||
]
|
||||
SOURCES += [
|
||||
'gfxFcPlatformFontList.cpp',
|
||||
'gfxFT2FontBase.cpp',
|
||||
'gfxFT2Utils.cpp',
|
||||
'gfxPlatformHeadless.cpp',
|
||||
'PrintTargetPDF.cpp',
|
||||
]
|
||||
elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
|
||||
EXPORTS += [
|
||||
'gfxFT2FontBase.h',
|
||||
|
||||
@@ -6,14 +6,6 @@
|
||||
GeckoProgram(CONFIG['MOZ_CHILD_PROCESS_NAME'], linkage='dependent')
|
||||
|
||||
SOURCES += [
|
||||
# JihadPmLogSem.c interposes sem_open/sem_close for the ONE name "PmLogLib". The device's
|
||||
# Flash pulls in libPmLogLib.so, whose constructor sem_wait()s a named semaphore created
|
||||
# by webOS's glibc 2.8; glibc 2.21 changed sem_t's value word to (tokens << 1), so our
|
||||
# bundled 2.23 reads that semaphore as "no tokens, waiters pending" and the dlopen never
|
||||
# returns. It has to be in THIS directory: a dlopened plugin resolves its PLT against the
|
||||
# global scope, which the program image heads, and ipc/app is where the program is linked
|
||||
# (ipc/contentproc only produces an archive this link does not use).
|
||||
'../contentproc/JihadPmLogSem.c',
|
||||
'MozillaRuntimeMain.cpp',
|
||||
]
|
||||
|
||||
|
||||
@@ -146,13 +146,7 @@ MessageLoop::MessageLoop(Type type, nsIThread* aThread)
|
||||
#if defined(OS_MACOSX)
|
||||
pump_ = base::MessagePumpMac::Create();
|
||||
#elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
|
||||
#if defined(MOZ_WIDGET_GTK)
|
||||
pump_ = new base::MessagePumpForUI();
|
||||
#else
|
||||
// Jihad headless: no GTK UI pump; the embedder drives events via
|
||||
// NS_ProcessNextEvent, so a default (non-UI) message pump suffices.
|
||||
pump_ = new base::MessagePumpDefault();
|
||||
#endif
|
||||
#endif // OS_LINUX
|
||||
} else if (type_ == TYPE_IO) {
|
||||
pump_ = new base::MessagePumpLibevent();
|
||||
|
||||
@@ -1,135 +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 https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Jihad Browser — the /PmLogLib named-semaphore ABI trap.
|
||||
*
|
||||
* Compiled into every one of our binaries that can dlopen a device NPAPI plugin: the
|
||||
* daemon (its plugin scan) and plugin-container (the plugin itself). Plain C, no headers
|
||||
* from UXP or from the daemon, so the same file drops into both build systems unchanged.
|
||||
*/
|
||||
|
||||
// ── WHAT THIS IS FOR ────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// The device's libflashplayer.so has libWebKitLuna.so as a DT_NEEDED, which drags in
|
||||
// libPmLogLib.so. PmLogLib's ELF constructor opens the POSIX named semaphore "PmLogLib"
|
||||
// and sem_wait()s it to guard a SysV shared-memory table of log contexts that every
|
||||
// process on the device shares.
|
||||
//
|
||||
// glibc changed sem_t's layout in 2.21. The value word went from a raw token count to
|
||||
// (tokens << 1) | has-waiters. webOS 3's userland is glibc 2.8 — the OLD layout — and
|
||||
// everything we cross-build runs on the bundled glibc 2.23, the NEW one. /dev/shm/
|
||||
// sem.PmLogLib is created once per boot by whichever process opens it first, in that
|
||||
// process's layout, and then outlives every user of it.
|
||||
//
|
||||
// When a system process wins that race the word holds a raw 1, meaning "one token". Our
|
||||
// 2.23 sem_wait reads the same word as ZERO tokens with the waiters bit set, so it goes
|
||||
// straight to futex_wait and never returns. dlopen never completes and whoever called it
|
||||
// wedges: measured on device 2026-08-10 as
|
||||
// futex(0x2dd98000, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 1, NULL, ffffffff
|
||||
// immediately after the mmap of /dev/shm/sem.PmLogLib, with every library already loaded.
|
||||
//
|
||||
// That is why this reads as an intermittent, reboot-triggered failure on binaries nobody
|
||||
// touched: it is decided by a boot race, not by our code. The same dlopen run under the
|
||||
// device's own /lib/ld-linux.so.3 completes and returns both Flash MIME types.
|
||||
//
|
||||
// ── WHY INTERPOSE RATHER THAN REPAIR THE SEMAPHORE ──────────────────────────────────────
|
||||
//
|
||||
// Rewriting /dev/shm/sem.PmLogLib into our layout, or posting it once to make it readable
|
||||
// to us, both work — and both hand every OTHER process on the device an extra token on a
|
||||
// lock guarding a shared table. That silently weakens PmLogLib's mutual exclusion
|
||||
// system-wide to fix a browser. Interposing ONE name does not: PmLogLib gets a semaphore
|
||||
// that never leaves this process, so no other glibc ever parses its layout, and the file
|
||||
// in /dev/shm is not touched at all.
|
||||
//
|
||||
// Everything else is forwarded to the real implementation untouched, which MATTERS: our
|
||||
// own shared frame buffers are named semaphores too (/dev/shm/sem.browserserver.<pid>-<n>,
|
||||
// created here and opened by the adapter inside LunaSysMgr), and those genuinely must stay
|
||||
// shared across the process boundary.
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
// PmLogLib asks for the name WITHOUT a leading slash (verified on device: the interposer
|
||||
// logged sem_open("PmLogLib")). POSIX says a portable name starts with '/', and glibc
|
||||
// accepts both, so match both rather than trusting either spelling.
|
||||
// Link anchor. plugin-container builds this TU into a static archive, and an archive member
|
||||
// is only pulled in to satisfy an UNDEFINED symbol — sem_open is already defined by
|
||||
// libpthread, so nothing was undefined and the whole file was silently dropped from the
|
||||
// link. Calling this from the program's entry point forces the member in. The daemon links
|
||||
// the object directly and does not need it; it costs one call there.
|
||||
__attribute__((visibility("default")))
|
||||
int jihad_pmlog_sem_installed(void) { return 1; }
|
||||
|
||||
static int jihad_is_pmlog_name(const char* name)
|
||||
{
|
||||
if (!name) return 0;
|
||||
if (name[0] == '/') ++name;
|
||||
return strcmp(name, "PmLogLib") == 0;
|
||||
}
|
||||
|
||||
static sem_t g_pmlogSem;
|
||||
static pthread_once_t g_pmlogSemOnce = PTHREAD_ONCE_INIT;
|
||||
static unsigned int g_pmlogSemInitial = 1;
|
||||
|
||||
static void jihad_pmlog_sem_init(void)
|
||||
{
|
||||
// pshared = 0. Process-private is the whole point: it can never be observed, and so can
|
||||
// never be misread, by a glibc with a different sem_t layout.
|
||||
sem_init(&g_pmlogSem, 0, g_pmlogSemInitial);
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
sem_t* sem_open(const char* name, int oflag, ...)
|
||||
{
|
||||
static sem_t* (*real_sem_open)(const char*, int, ...);
|
||||
if (!real_sem_open)
|
||||
real_sem_open = (sem_t* (*)(const char*, int, ...))dlsym(RTLD_NEXT, "sem_open");
|
||||
|
||||
// The mode/value pair is only present when O_CREAT is set; reading it otherwise walks off
|
||||
// the end of the argument list.
|
||||
mode_t mode = 0;
|
||||
unsigned int value = 0;
|
||||
const int creating = (oflag & O_CREAT) != 0;
|
||||
if (creating) {
|
||||
va_list ap;
|
||||
va_start(ap, oflag);
|
||||
mode = (mode_t)va_arg(ap, unsigned int);
|
||||
value = va_arg(ap, unsigned int);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
if (jihad_is_pmlog_name(name)) {
|
||||
if (creating) g_pmlogSemInitial = value;
|
||||
pthread_once(&g_pmlogSemOnce, jihad_pmlog_sem_init);
|
||||
return &g_pmlogSem;
|
||||
}
|
||||
|
||||
if (!real_sem_open) return SEM_FAILED;
|
||||
if (creating) return real_sem_open(name, oflag, mode, value);
|
||||
return real_sem_open(name, oflag);
|
||||
}
|
||||
|
||||
// PmLogLib closes the semaphore on unload. glibc's sem_close looks the pointer up in its
|
||||
// table of NAMED semaphores, would not find ours, and would return EINVAL — harmless today,
|
||||
// but only by luck. Answer for our own pointer and forward everything else.
|
||||
__attribute__((visibility("default")))
|
||||
int sem_close(sem_t* sem)
|
||||
{
|
||||
static int (*real_sem_close)(sem_t*);
|
||||
if (!real_sem_close)
|
||||
real_sem_close = (int (*)(sem_t*))dlsym(RTLD_NEXT, "sem_close");
|
||||
|
||||
// Deliberately NOT sem_destroy: the plugin can be unloaded and reloaded within one
|
||||
// process (the scan does exactly that), and a destroyed static would be reused.
|
||||
if (sem == &g_pmlogSem) return 0;
|
||||
|
||||
if (!real_sem_close) return 0;
|
||||
return real_sem_close(sem);
|
||||
}
|
||||
@@ -9,10 +9,6 @@ SOURCES += [
|
||||
'plugin-container.cpp',
|
||||
]
|
||||
|
||||
# JihadPmLogSem.c lives here next to its only caller but is compiled by ipc/app, which is
|
||||
# where the plugin-container PROGRAM is linked; this directory only produces an archive that
|
||||
# that link does not use. See ipc/app/moz.build.
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
|
||||
@@ -3,16 +3,10 @@
|
||||
* 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 <stdlib.h> // getenv / unsetenv (JIHAD: see main)
|
||||
#include "nsXPCOM.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsAutoPtr.h"
|
||||
|
||||
// JihadPmLogSem.c — C, and deliberately carries no header, so it drops unchanged into both
|
||||
// this build and the daemon's. Declared here rather than in one because that is the entire
|
||||
// interface: one call whose only job is to keep the archive member in the link.
|
||||
extern "C" int jihad_pmlog_sem_installed(void);
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
// we want a wmain entry point
|
||||
@@ -25,82 +19,6 @@ extern "C" int jihad_pmlog_sem_installed(void);
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
// JIHAD DIAGNOSTIC (R7): the child process dies with SIGSEGV between a successful
|
||||
// InitForChrome and handling its first IPC message, and the device's own minicore reporter
|
||||
// is out of report slots ("no room for new reports"), so nothing records where. This prints
|
||||
// the faulting address, a backtrace, and /proc/self/maps to stderr — which the child
|
||||
// inherits from the daemon, so it lands in the variant's daemon.log. The maps dump is the
|
||||
// part that matters on ARM: even a single frame is enough to name the faulting library.
|
||||
#if defined(OS_POSIX) || defined(XP_UNIX)
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void
|
||||
jihad_child_fault_handler(int sig, siginfo_t* info, void* ctx)
|
||||
{
|
||||
void* frames[32];
|
||||
int n = backtrace(frames, 32);
|
||||
fprintf(stderr, "[jihad-npapi-child] FAULT sig=%d addr=%p pid=%d frames=%d\n",
|
||||
sig, info ? info->si_addr : nullptr, (int)getpid(), n);
|
||||
|
||||
// backtrace() returns nothing useful from a signal frame on ARM (no unwind tables
|
||||
// through the trampoline), so take the registers straight out of the signal context.
|
||||
// pc and lr against the maps below give the faulting library and offset, and
|
||||
// addr2line on the unstripped build turns that into file:line.
|
||||
#if defined(__arm__)
|
||||
if (ctx) {
|
||||
ucontext_t* uc = (ucontext_t*)ctx;
|
||||
fprintf(stderr, "[jihad-npapi-child] FAULT pc=%08lx lr=%08lx sp=%08lx r0=%08lx\n",
|
||||
(unsigned long)uc->uc_mcontext.arm_pc,
|
||||
(unsigned long)uc->uc_mcontext.arm_lr,
|
||||
(unsigned long)uc->uc_mcontext.arm_sp,
|
||||
(unsigned long)uc->uc_mcontext.arm_r0);
|
||||
}
|
||||
#else
|
||||
(void)ctx;
|
||||
#endif
|
||||
fflush(stderr);
|
||||
backtrace_symbols_fd(frames, n, STDERR_FILENO);
|
||||
|
||||
FILE* maps = fopen("/proc/self/maps", "r");
|
||||
if (maps) {
|
||||
char line[512];
|
||||
fputs("[jihad-npapi-child] --- maps ---\n", stderr);
|
||||
while (fgets(line, sizeof(line), maps)) {
|
||||
// Only the executable mappings; the full map is hundreds of lines of noise.
|
||||
if (strstr(line, "r-xp")) {
|
||||
fputs("[jihad-npapi-child] ", stderr);
|
||||
fputs(line, stderr);
|
||||
}
|
||||
}
|
||||
fclose(maps);
|
||||
}
|
||||
fflush(stderr);
|
||||
|
||||
// Restore the default disposition and re-raise so the exit status still says "crashed".
|
||||
signal(sig, SIG_DFL);
|
||||
raise(sig);
|
||||
}
|
||||
|
||||
static void
|
||||
jihad_install_child_fault_handler()
|
||||
{
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = jihad_child_fault_handler;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sigaction(SIGSEGV, &sa, nullptr);
|
||||
sigaction(SIGBUS, &sa, nullptr);
|
||||
sigaction(SIGILL, &sa, nullptr);
|
||||
sigaction(SIGFPE, &sa, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_GMP
|
||||
#include "GMPLoader.h"
|
||||
|
||||
@@ -120,41 +38,6 @@ content_process_main(int argc, char* argv[])
|
||||
return 3;
|
||||
}
|
||||
|
||||
#if defined(OS_POSIX) || defined(XP_UNIX)
|
||||
jihad_install_child_fault_handler();
|
||||
#endif
|
||||
|
||||
// JIHAD (R7): force JihadPmLogSem.o into the link and say so. It interposes sem_open for
|
||||
// the single name "PmLogLib", without which dlopening the device's Flash deadlocks this
|
||||
// process forever — libPmLogLib's constructor waits on a named semaphore created by
|
||||
// webOS's glibc 2.8, whose sem_t value word our glibc 2.23 reads as "no tokens, waiters
|
||||
// pending". The call is also the only cheap proof the interposer survived the link: it
|
||||
// lives in a static archive, and an archive member with no undefined symbol to satisfy
|
||||
// is silently dropped. See render/goanna/JihadPmLogSem.c.
|
||||
fprintf(stderr, "[jihad-npapi-child] pmlog sem interposition=%d\n",
|
||||
jihad_pmlog_sem_installed());
|
||||
|
||||
// JIHAD DIAGNOSTIC (R7): the child's own view of its argv. Doubles as the control that
|
||||
// child stderr reaches the parent's log at all — every other child probe is worthless
|
||||
// until this line is seen.
|
||||
fprintf(stderr, "[jihad-npapi-child] main argc=%d\n", argc);
|
||||
for (int i = 0; i < argc; i++) {
|
||||
fprintf(stderr, "[jihad-npapi-child] argv[%d]=%s\n", i, argv[i]);
|
||||
}
|
||||
fflush(stderr);
|
||||
|
||||
// JIHAD (2026-10-05): DO NOT HAND OUR LIBRARY PATH TO WHAT THE PLUGIN RUNS.
|
||||
// This process is started through the bundled loader with an explicit --library-path,
|
||||
// which is what its own later dlopen()s search; the LD_LIBRARY_PATH it also inherits
|
||||
// is not needed by it. But it is inherited again by anything the plugin spawns, and
|
||||
// Flash for Linux runs `sh -c "ps x | grep netscape"` at start-up: the device's own
|
||||
// /bin/sh (built for its glibc 2.8) then loads the bundled glibc 2.23 and dies with
|
||||
// SIGSEGV, leaving a crash report in /var/log/reports for every page with Flash on it.
|
||||
// Only when the bundled loader is in use; a desktop build keeps its environment.
|
||||
if (getenv("MOZ_CHILD_PROCESS_LOADER")) {
|
||||
unsetenv("LD_LIBRARY_PATH");
|
||||
}
|
||||
|
||||
#ifdef MOZ_GMP
|
||||
XREChildData childData;
|
||||
#endif
|
||||
|
||||
@@ -160,13 +160,6 @@ GeckoChildProcessHost::GetPathToBinary(FilePath& exePath, GeckoProcessType proce
|
||||
|
||||
exePath = exePath.AppendASCII(MOZ_CHILD_PROCESS_NAME);
|
||||
|
||||
// JIHAD DIAGNOSTIC (R7): the resolved child path, and which branch produced it. In this
|
||||
// embedding the daemon is exec'd as `./ld-2.23.so … ./jihad-browserserver`, so argv[0] is the
|
||||
// LOADER — if the directory-service branch is not taken, the fallback dirname(argv[0]) is
|
||||
// relative to that, not to the daemon.
|
||||
fprintf(stderr, "[jihad-npapi] child binary path=%s (dirService=%d)\n",
|
||||
exePath.value().c_str(), (int)ShouldHaveDirectoryService());
|
||||
|
||||
return BinaryPathType::PluginContainer;
|
||||
}
|
||||
|
||||
@@ -669,27 +662,6 @@ GeckoChildProcessHost::PerformAsyncLaunchInternal(std::vector<std::string>& aExt
|
||||
|
||||
std::vector<std::string> childArgv;
|
||||
|
||||
// Optional ELF loader prefix for the child process.
|
||||
//
|
||||
// An embedder whose bundled glibc differs from the host system's cannot exec the child
|
||||
// binary directly: its PT_INTERP names the SYSTEM loader, which then resolves the child's
|
||||
// libraries against the bundled set and dies before main(). Exporting these two variables
|
||||
// makes the child be launched exactly the way such an embedder launches itself —
|
||||
// <loader> --library-path <path> <child> <args…>
|
||||
// — which runs the child in the same process image (no extra fork, no shell hop, so the
|
||||
// pid we hand to waitpid and the inherited IPC channel fd are unaffected).
|
||||
//
|
||||
// Both unset is upstream behaviour, unchanged.
|
||||
const char* childLoader = PR_GetEnv("MOZ_CHILD_PROCESS_LOADER");
|
||||
if (childLoader && *childLoader) {
|
||||
childArgv.push_back(childLoader);
|
||||
const char* loaderLibPath = PR_GetEnv("MOZ_CHILD_PROCESS_LOADER_PATH");
|
||||
if (loaderLibPath && *loaderLibPath) {
|
||||
childArgv.push_back("--library-path");
|
||||
childArgv.push_back(loaderLibPath);
|
||||
}
|
||||
}
|
||||
|
||||
childArgv.push_back(exePath.value());
|
||||
|
||||
if (pathType == BinaryPathType::Self) {
|
||||
|
||||
@@ -8185,7 +8185,7 @@ CodeGenerator::visitStoreElementV(LStoreElementV* lir)
|
||||
template <typename T> void
|
||||
CodeGenerator::emitStoreElementHoleT(T* lir)
|
||||
{
|
||||
static_assert(std::is_same<T, LStoreElementHoleT>::value || std::is_same<T, LFallibleStoreElementT>::value,
|
||||
static_assert(std::is_same_v<T, LStoreElementHoleT> || std::is_same_v<T, LFallibleStoreElementT>,
|
||||
"emitStoreElementHoleT called with unexpected argument type");
|
||||
|
||||
OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir);
|
||||
@@ -8244,7 +8244,7 @@ CodeGenerator::visitStoreElementHoleT(LStoreElementHoleT* lir)
|
||||
template <typename T> void
|
||||
CodeGenerator::emitStoreElementHoleV(T* lir)
|
||||
{
|
||||
static_assert(std::is_same<T, LStoreElementHoleV>::value || std::is_same<T, LFallibleStoreElementV>::value,
|
||||
static_assert(std::is_same_v<T, LStoreElementHoleV> || std::is_same_v<T, LFallibleStoreElementV>,
|
||||
"emitStoreElementHoleV called with unexpected parameter type");
|
||||
|
||||
OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir);
|
||||
|
||||
@@ -8,14 +8,6 @@
|
||||
|
||||
#include "jit/x86-shared/BaseAssembler-x86-shared.h"
|
||||
|
||||
// Jihad Browser: GCC 9+ -Wformat-overflow raises false positives on the debug
|
||||
// spew() format strings below (it deduces a null %s arg via value-range
|
||||
// analysis). js/src forces -Werror=format, which re-escalates this even when
|
||||
// downgraded on the command line, so silence the diagnostic at the source.
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
# pragma GCC diagnostic ignored "-Wformat-overflow"
|
||||
#endif
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
|
||||
+4
-4
@@ -2871,8 +2871,8 @@ static JSString* ReplaceAll(JSContext* cx, JSLinearString* str,
|
||||
|
||||
// Step 13.
|
||||
StringBuffer result(cx);
|
||||
if (std::is_same<StrChar, char16_t>::value ||
|
||||
std::is_same<RepChar, char16_t>::value) {
|
||||
if (std::is_same_v<StrChar, char16_t> ||
|
||||
std::is_same_v<RepChar, char16_t>) {
|
||||
if (!result.ensureTwoByteChars()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -2954,8 +2954,8 @@ static JSString* ReplaceAllInterleave(JSContext* cx, JSLinearString* str,
|
||||
|
||||
// Step 13.
|
||||
StringBuffer result(cx);
|
||||
if (std::is_same<StrChar, char16_t>::value ||
|
||||
std::is_same<RepChar, char16_t>::value) {
|
||||
if (std::is_same_v<StrChar, char16_t> ||
|
||||
std::is_same_v<RepChar, char16_t>) {
|
||||
if (!result.ensureTwoByteChars()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -309,26 +309,26 @@ InflateUTF8StringToBuffer(ContextT* cx, const UTF8Chars src, CharT* dst, size_t*
|
||||
while (v & (0x80 >> n))
|
||||
n++;
|
||||
|
||||
#define INVALID(report, arg, n2) \
|
||||
do { \
|
||||
if (Action == CountAndReportInvalids) { \
|
||||
report(cx, arg); \
|
||||
return false; \
|
||||
} else if (Action == AssertNoInvalids) { \
|
||||
MOZ_CRASH("invalid UTF-8 string: " # report); \
|
||||
} else { \
|
||||
if (Action == Copy) { \
|
||||
if (std::is_same<decltype(dst[0]), Latin1Char>::value) \
|
||||
dst[j] = CharT(REPLACE_UTF8_LATIN1); \
|
||||
else \
|
||||
dst[j] = CharT(REPLACE_UTF8); \
|
||||
} else { \
|
||||
MOZ_ASSERT(Action == CountAndIgnoreInvalids || \
|
||||
Action == FindEncoding); \
|
||||
} \
|
||||
n = n2; \
|
||||
goto invalidMultiByteCodeUnit; \
|
||||
} \
|
||||
#define INVALID(report, arg, n2) \
|
||||
do { \
|
||||
if (Action == CountAndReportInvalids) { \
|
||||
report(cx, arg); \
|
||||
return false; \
|
||||
} else if (Action == AssertNoInvalids) { \
|
||||
MOZ_CRASH("invalid UTF-8 string: " # report); \
|
||||
} else { \
|
||||
if (Action == Copy) { \
|
||||
if (std::is_same_v<decltype(dst[0]), Latin1Char>) \
|
||||
dst[j] = CharT(REPLACE_UTF8_LATIN1); \
|
||||
else \
|
||||
dst[j] = CharT(REPLACE_UTF8); \
|
||||
} else { \
|
||||
MOZ_ASSERT(Action == CountAndIgnoreInvalids || \
|
||||
Action == FindEncoding); \
|
||||
} \
|
||||
n = n2; \
|
||||
goto invalidMultiByteCodeUnit; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Check the leading byte.
|
||||
|
||||
+1
-1
@@ -1138,7 +1138,7 @@ class StaticStrings
|
||||
}
|
||||
|
||||
template <typename CharT,
|
||||
typename = typename std::enable_if<!std::is_const<CharT>::value>::type>
|
||||
typename = typename std::enable_if_t<!std::is_const_v<CharT>>>
|
||||
MOZ_ALWAYS_INLINE JSAtom* lookup(CharT* chars, size_t length) {
|
||||
// Collapse the remaining |CharT*| to |const CharT*| to avoid excess
|
||||
// instantiations.
|
||||
|
||||
@@ -677,12 +677,12 @@ class ElementSpecific
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std::is_same<T, int64_t>::value) {
|
||||
if (std::is_same_v<T, int64_t>) {
|
||||
JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigInt64(cx, v));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std::is_same<T, uint64_t>::value) {
|
||||
if (std::is_same_v<T, uint64_t>) {
|
||||
JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigUint64(cx, v));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -953,11 +953,12 @@ inDOMUtils::GetCSSValuesForProperty(const nsAString& aProperty,
|
||||
GetOtherValuesForProperty(propertyParserVariant, array);
|
||||
}
|
||||
}
|
||||
// All CSS properties take initial, inherit, unset, and revert.
|
||||
// All CSS properties take initial, inherit, unset, revert, and revert-layer.
|
||||
InsertNoDuplicates(array, NS_LITERAL_STRING("initial"));
|
||||
InsertNoDuplicates(array, NS_LITERAL_STRING("inherit"));
|
||||
InsertNoDuplicates(array, NS_LITERAL_STRING("unset"));
|
||||
InsertNoDuplicates(array, NS_LITERAL_STRING("revert"));
|
||||
InsertNoDuplicates(array, NS_LITERAL_STRING("revert-layer"));
|
||||
|
||||
*aLength = array.Length();
|
||||
char16_t** ret =
|
||||
|
||||
@@ -8,19 +8,227 @@
|
||||
#include "CSSVariableDeclarations.h"
|
||||
|
||||
#include "CSSVariableResolver.h"
|
||||
#include "CSSVariableValues.h"
|
||||
#include "nsCSSScanner.h"
|
||||
#include "nsRuleData.h"
|
||||
|
||||
// These four special string values are used to represent specified values of
|
||||
// 'initial', 'inherit', 'unset', and 'revert'. (Note that none of these are
|
||||
// valid variable values.)
|
||||
// These special string values are used to represent specified values of
|
||||
// CSS-wide keywords. (Note that none of these are valid variable values.)
|
||||
#define INITIAL_VALUE "!"
|
||||
#define INHERIT_VALUE ";"
|
||||
#define UNSET_VALUE ")"
|
||||
#define REVERT_VALUE ">"
|
||||
#define REVERT_LAYER_VALUE "]"
|
||||
|
||||
static const int32_t kRevertLayerMask = 0x000fffff;
|
||||
static const int32_t kRevertLayerImportant = 0x00100000;
|
||||
static const int32_t kRevertLayerOriginShift = 21;
|
||||
static const int32_t kLayerIndexMax = 0x000fffff;
|
||||
static const int32_t kCascadeRankStride = kLayerIndexMax + 1;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static bool
|
||||
IsRevertLayerValue(const nsString& aValue)
|
||||
{
|
||||
return StringBeginsWith(aValue, NS_LITERAL_STRING(REVERT_LAYER_VALUE));
|
||||
}
|
||||
|
||||
static bool
|
||||
ValueMightResolveToRevertLayer(const nsString& aValue)
|
||||
{
|
||||
return IsRevertLayerValue(aValue) ||
|
||||
aValue.Find(NS_LITERAL_STRING("revert-layer")) != kNotFound;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
PackRevertLayerData(SheetType aLevel, bool aIsImportant,
|
||||
int32_t aLayerIndex)
|
||||
{
|
||||
MOZ_ASSERT(aLayerIndex >= 0 && aLayerIndex <= kRevertLayerMask);
|
||||
return (uint32_t(aLevel) << kRevertLayerOriginShift) |
|
||||
(aIsImportant ? kRevertLayerImportant : 0) |
|
||||
(aLayerIndex & kRevertLayerMask);
|
||||
}
|
||||
|
||||
static void
|
||||
SetRevertLayerData(nsString& aValue, const nsRuleData* aRuleData)
|
||||
{
|
||||
aValue.AssignLiteral(REVERT_LAYER_VALUE);
|
||||
aValue.AppendInt(PackRevertLayerData(aRuleData->mLevel,
|
||||
aRuleData->mIsImportantRule,
|
||||
aRuleData->mLayerIndex));
|
||||
}
|
||||
|
||||
static bool
|
||||
UnpackRevertLayerData(const nsString& aValue, SheetType& aLevel,
|
||||
bool& aIsImportant, int32_t& aLayerIndex)
|
||||
{
|
||||
if (!IsRevertLayerValue(aValue) ||
|
||||
aValue.Length() == NS_LITERAL_STRING(REVERT_LAYER_VALUE).Length()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsAutoString metadata;
|
||||
metadata.Assign(Substring(aValue,
|
||||
NS_LITERAL_STRING(REVERT_LAYER_VALUE).Length()));
|
||||
int32_t packed = metadata.ToInteger(&rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aLevel = SheetType(uint32_t(packed) >> kRevertLayerOriginShift);
|
||||
aIsImportant = (packed & kRevertLayerImportant) != 0;
|
||||
aLayerIndex = packed & kRevertLayerMask;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
LayerIndexRank(bool aIsImportant, int32_t aLayerIndex)
|
||||
{
|
||||
MOZ_ASSERT(aLayerIndex >= 0);
|
||||
MOZ_ASSERT(aLayerIndex <= kLayerIndexMax);
|
||||
return aIsImportant ? kLayerIndexMax - aLayerIndex : aLayerIndex;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
CascadePrecedenceRank(SheetType aLevel, bool aIsImportant,
|
||||
int32_t aLayerIndex)
|
||||
{
|
||||
int32_t bucket = 0;
|
||||
bool isLayeredOrigin = false;
|
||||
|
||||
switch (aLevel) {
|
||||
case SheetType::Agent:
|
||||
bucket = aIsImportant ? 14 : 0;
|
||||
isLayeredOrigin = true;
|
||||
break;
|
||||
case SheetType::User:
|
||||
bucket = aIsImportant ? 13 : 1;
|
||||
isLayeredOrigin = true;
|
||||
break;
|
||||
case SheetType::PresHint:
|
||||
bucket = 2;
|
||||
break;
|
||||
case SheetType::SVGAttrAnimation:
|
||||
bucket = 3;
|
||||
break;
|
||||
case SheetType::Doc:
|
||||
bucket = aIsImportant ? 10 : 4;
|
||||
isLayeredOrigin = true;
|
||||
break;
|
||||
case SheetType::ScopedDoc:
|
||||
bucket = aIsImportant ? 9 : 5;
|
||||
isLayeredOrigin = true;
|
||||
break;
|
||||
case SheetType::StyleAttr:
|
||||
bucket = aIsImportant ? 11 : 6;
|
||||
break;
|
||||
case SheetType::Override:
|
||||
bucket = aIsImportant ? 12 : 7;
|
||||
break;
|
||||
case SheetType::Animation:
|
||||
bucket = 8;
|
||||
break;
|
||||
case SheetType::Transition:
|
||||
bucket = 15;
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("unexpected cascade level");
|
||||
break;
|
||||
}
|
||||
|
||||
return bucket * kCascadeRankStride +
|
||||
(isLayeredOrigin ? LayerIndexRank(aIsImportant, aLayerIndex) : 0);
|
||||
}
|
||||
|
||||
static bool
|
||||
ShouldIgnoreForRevertLayerData(SheetType aSourceLevel,
|
||||
bool aSourceImportant,
|
||||
int32_t aSourceLayerIndex,
|
||||
SheetType aCandidateLevel,
|
||||
bool aCandidateImportant,
|
||||
int32_t aCandidateLayerIndex)
|
||||
{
|
||||
int32_t candidateRank =
|
||||
CascadePrecedenceRank(aCandidateLevel, aCandidateImportant,
|
||||
aCandidateLayerIndex);
|
||||
int32_t sourceRank =
|
||||
CascadePrecedenceRank(aSourceLevel, aSourceImportant, aSourceLayerIndex);
|
||||
|
||||
if (candidateRank >= sourceRank) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!aSourceImportant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aSourceLevel == SheetType::StyleAttr) {
|
||||
return aCandidateLevel == SheetType::StyleAttr ||
|
||||
aCandidateLevel == SheetType::Animation;
|
||||
}
|
||||
|
||||
int32_t normalBoundary =
|
||||
CascadePrecedenceRank(aSourceLevel, false, aSourceLayerIndex);
|
||||
return candidateRank >= normalBoundary;
|
||||
}
|
||||
|
||||
static bool
|
||||
ShouldIgnoreForRevertLayerValue(const nsString& aTargetValue,
|
||||
SheetType aCandidateLevel,
|
||||
bool aCandidateImportant,
|
||||
int32_t aCandidateLayerIndex)
|
||||
{
|
||||
SheetType sourceLevel;
|
||||
bool sourceImportant;
|
||||
int32_t sourceLayerIndex;
|
||||
if (!UnpackRevertLayerData(aTargetValue, sourceLevel, sourceImportant,
|
||||
sourceLayerIndex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ShouldIgnoreForRevertLayerData(sourceLevel, sourceImportant,
|
||||
sourceLayerIndex, aCandidateLevel,
|
||||
aCandidateImportant,
|
||||
aCandidateLayerIndex);
|
||||
}
|
||||
|
||||
static bool
|
||||
ShouldIgnoreForRevertLayerValue(const nsString& aTargetValue,
|
||||
const nsRuleData* aRuleData)
|
||||
{
|
||||
return ShouldIgnoreForRevertLayerValue(aTargetValue, aRuleData->mLevel,
|
||||
aRuleData->mIsImportantRule,
|
||||
aRuleData->mLayerIndex);
|
||||
}
|
||||
|
||||
static bool
|
||||
ShouldIgnoreForRevertLayerSourceValue(const nsString& aTargetValue,
|
||||
const nsString& aCandidateValue)
|
||||
{
|
||||
SheetType candidateLevel;
|
||||
bool candidateImportant;
|
||||
int32_t candidateLayerIndex;
|
||||
if (!UnpackRevertLayerData(aCandidateValue, candidateLevel,
|
||||
candidateImportant, candidateLayerIndex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ShouldIgnoreForRevertLayerValue(aTargetValue, candidateLevel,
|
||||
candidateImportant,
|
||||
candidateLayerIndex);
|
||||
}
|
||||
|
||||
static bool
|
||||
IsComputedRevertLayerValue(const nsString& aValue)
|
||||
{
|
||||
nsAutoString trimmed(aValue);
|
||||
trimmed.CompressWhitespace(true, true);
|
||||
return trimmed.EqualsLiteral("revert-layer");
|
||||
}
|
||||
|
||||
CSSVariableDeclarations::CSSVariableDeclarations()
|
||||
{
|
||||
MOZ_COUNT_CTOR(CSSVariableDeclarations);
|
||||
@@ -47,6 +255,8 @@ CSSVariableDeclarations::operator=(const CSSVariableDeclarations& aOther)
|
||||
}
|
||||
|
||||
mVariables.Clear();
|
||||
mRevertLayerSources.Clear();
|
||||
mRevertLayerFallbacks.Clear();
|
||||
CopyVariablesFrom(aOther);
|
||||
return *this;
|
||||
}
|
||||
@@ -57,6 +267,15 @@ CSSVariableDeclarations::CopyVariablesFrom(const CSSVariableDeclarations& aOther
|
||||
for (auto iter = aOther.mVariables.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
mVariables.Put(iter.Key(), iter.UserData());
|
||||
}
|
||||
for (auto iter = aOther.mRevertLayerSources.ConstIter();
|
||||
!iter.Done(); iter.Next()) {
|
||||
mRevertLayerSources.Put(iter.Key(), iter.UserData());
|
||||
}
|
||||
for (auto iter = aOther.mRevertLayerFallbacks.ConstIter();
|
||||
!iter.Done(); iter.Next()) {
|
||||
RevertLayerFallbackList* list = mRevertLayerFallbacks.LookupOrAdd(iter.Key());
|
||||
list->mItems = iter.UserData()->mItems;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -87,6 +306,9 @@ CSSVariableDeclarations::Get(const nsAString& aName,
|
||||
} else if (value.EqualsLiteral(REVERT_VALUE)) {
|
||||
aType = eRevert;
|
||||
aTokenStream.Truncate();
|
||||
} else if (IsRevertLayerValue(value)) {
|
||||
aType = eRevertLayer;
|
||||
aTokenStream.Truncate();
|
||||
} else {
|
||||
aType = eTokenStream;
|
||||
aTokenStream = value;
|
||||
@@ -101,7 +323,8 @@ CSSVariableDeclarations::PutTokenStream(const nsAString& aName,
|
||||
MOZ_ASSERT(!aTokenStream.EqualsLiteral(INITIAL_VALUE) &&
|
||||
!aTokenStream.EqualsLiteral(INHERIT_VALUE) &&
|
||||
!aTokenStream.EqualsLiteral(UNSET_VALUE) &&
|
||||
!aTokenStream.EqualsLiteral(REVERT_VALUE));
|
||||
!aTokenStream.EqualsLiteral(REVERT_VALUE) &&
|
||||
!aTokenStream.EqualsLiteral(REVERT_LAYER_VALUE));
|
||||
mVariables.Put(aName, aTokenStream);
|
||||
}
|
||||
|
||||
@@ -129,6 +352,12 @@ CSSVariableDeclarations::PutRevert(const nsAString& aName)
|
||||
mVariables.Put(aName, NS_LITERAL_STRING(REVERT_VALUE));
|
||||
}
|
||||
|
||||
void
|
||||
CSSVariableDeclarations::PutRevertLayer(const nsAString& aName)
|
||||
{
|
||||
mVariables.Put(aName, NS_LITERAL_STRING(REVERT_LAYER_VALUE));
|
||||
}
|
||||
|
||||
void
|
||||
CSSVariableDeclarations::Remove(const nsAString& aName)
|
||||
{
|
||||
@@ -143,16 +372,49 @@ CSSVariableDeclarations::MapRuleInfoInto(nsRuleData* aRuleData)
|
||||
}
|
||||
|
||||
if (!aRuleData->mVariables) {
|
||||
aRuleData->mVariables = new CSSVariableDeclarations(*this);
|
||||
} else {
|
||||
for (auto iter = mVariables.Iter(); !iter.Done(); iter.Next()) {
|
||||
nsDataHashtable<nsStringHashKey, nsString>& variables =
|
||||
aRuleData->mVariables->mVariables;
|
||||
const nsAString& aName = iter.Key();
|
||||
if (!variables.Contains(aName)) {
|
||||
variables.Put(aName, iter.UserData());
|
||||
aRuleData->mVariables = new CSSVariableDeclarations;
|
||||
}
|
||||
|
||||
for (auto iter = mVariables.Iter(); !iter.Done(); iter.Next()) {
|
||||
nsDataHashtable<nsStringHashKey, nsString>& variables =
|
||||
aRuleData->mVariables->mVariables;
|
||||
const nsAString& name = iter.Key();
|
||||
nsString value = iter.UserData();
|
||||
nsString valueSource;
|
||||
SetRevertLayerData(valueSource, aRuleData);
|
||||
if (value.EqualsLiteral(REVERT_LAYER_VALUE)) {
|
||||
value = valueSource;
|
||||
aRuleData->mConditions.SetUncacheable();
|
||||
}
|
||||
|
||||
nsString existingValue;
|
||||
if (variables.Get(name, &existingValue)) {
|
||||
if (IsRevertLayerValue(existingValue)) {
|
||||
if (ShouldIgnoreForRevertLayerValue(existingValue, aRuleData)) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
nsString sourceValue;
|
||||
if (aRuleData->mVariables->mRevertLayerSources.Get(name,
|
||||
&sourceValue) &&
|
||||
!ShouldIgnoreForRevertLayerValue(sourceValue, aRuleData)) {
|
||||
RevertLayerFallbackList* fallbacks =
|
||||
aRuleData->mVariables->mRevertLayerFallbacks.LookupOrAdd(name);
|
||||
RevertLayerFallback* fallback = fallbacks->mItems.AppendElement();
|
||||
fallback->mValue = value;
|
||||
fallback->mSource = valueSource;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
variables.Put(name, value);
|
||||
if (ValueMightResolveToRevertLayer(value)) {
|
||||
aRuleData->mVariables->mRevertLayerSources.Put(name, valueSource);
|
||||
} else {
|
||||
aRuleData->mVariables->mRevertLayerSources.Remove(name);
|
||||
aRuleData->mVariables->mRevertLayerFallbacks.Remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,8 +434,9 @@ CSSVariableDeclarations::AddVariablesToResolver(
|
||||
false);
|
||||
} else if (value.EqualsLiteral(INHERIT_VALUE) ||
|
||||
value.EqualsLiteral(UNSET_VALUE) ||
|
||||
value.EqualsLiteral(REVERT_VALUE)) {
|
||||
// Values of 'inherit', 'unset', and 'revert' don't need any handling,
|
||||
value.EqualsLiteral(REVERT_VALUE) ||
|
||||
IsRevertLayerValue(value)) {
|
||||
// Values of 'inherit', 'unset', 'revert', and 'revert-layer' don't need any handling,
|
||||
// since it means we just need to keep whatever value is currently in
|
||||
// the resolver. This is because the specified variable declarations
|
||||
// already have only the winning declaration for the variable and no
|
||||
@@ -190,6 +453,83 @@ CSSVariableDeclarations::AddVariablesToResolver(
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CSSVariableDeclarations::ResolveRevertLayerFallbacks(
|
||||
const CSSVariableValues* aInherited,
|
||||
CSSVariableValues* aOutput) const
|
||||
{
|
||||
for (auto iter = mRevertLayerSources.ConstIter();
|
||||
!iter.Done(); iter.Next()) {
|
||||
const nsAString& name = iter.Key();
|
||||
nsString value;
|
||||
nsCSSTokenSerializationType firstToken, lastToken;
|
||||
if (!aOutput->Get(name, value, firstToken, lastToken)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsComputedRevertLayerValue(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const RevertLayerFallbackList* fallbacks = mRevertLayerFallbacks.Get(name);
|
||||
if (fallbacks) {
|
||||
nsString sourceValue = iter.UserData();
|
||||
bool foundFallback = false;
|
||||
for (uint32_t i = 0, n = fallbacks->mItems.Length(); i < n; i++) {
|
||||
const RevertLayerFallback& fallback = fallbacks->mItems[i];
|
||||
if (ShouldIgnoreForRevertLayerSourceValue(sourceValue,
|
||||
fallback.mSource)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsRevertLayerValue(fallback.mValue)) {
|
||||
sourceValue = fallback.mSource;
|
||||
continue;
|
||||
}
|
||||
|
||||
CSSVariableValues inheritedValues(*aOutput);
|
||||
if (aInherited->Get(name, value, firstToken, lastToken)) {
|
||||
inheritedValues.Put(name, value, firstToken, lastToken);
|
||||
} else {
|
||||
inheritedValues.Remove(name);
|
||||
}
|
||||
|
||||
CSSVariableDeclarations fallbackDeclarations;
|
||||
fallbackDeclarations.mVariables.Put(name, fallback.mValue);
|
||||
|
||||
CSSVariableValues fallbackValues;
|
||||
CSSVariableResolver resolver(&fallbackValues);
|
||||
resolver.Resolve(&inheritedValues, &fallbackDeclarations);
|
||||
|
||||
if (!fallbackValues.Get(name, value, firstToken, lastToken)) {
|
||||
aOutput->Remove(name);
|
||||
foundFallback = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (IsComputedRevertLayerValue(value)) {
|
||||
sourceValue = fallback.mSource;
|
||||
continue;
|
||||
}
|
||||
|
||||
aOutput->Put(name, value, firstToken, lastToken);
|
||||
foundFallback = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (foundFallback) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (aInherited->Get(name, value, firstToken, lastToken)) {
|
||||
aOutput->Put(name, value, firstToken, lastToken);
|
||||
} else {
|
||||
aOutput->Remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
CSSVariableDeclarations::SizeOfIncludingThis(
|
||||
mozilla::MallocSizeOf aMallocSizeOf) const
|
||||
@@ -200,6 +540,22 @@ CSSVariableDeclarations::SizeOfIncludingThis(
|
||||
n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
n += iter.Data().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
}
|
||||
n += mRevertLayerSources.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
||||
for (auto iter = mRevertLayerSources.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
n += iter.Data().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
}
|
||||
n += mRevertLayerFallbacks.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
||||
for (auto iter = mRevertLayerFallbacks.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
const RevertLayerFallbackList* list = iter.UserData();
|
||||
n += aMallocSizeOf(list);
|
||||
n += list->mItems.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
||||
for (uint32_t i = 0, len = list->mItems.Length(); i < len; i++) {
|
||||
n += list->mItems[i].mValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
n += list->mItems[i].mSource.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
#ifndef mozilla_CSSVariableDeclarations_h
|
||||
#define mozilla_CSSVariableDeclarations_h
|
||||
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
namespace mozilla {
|
||||
class CSSVariableResolver;
|
||||
class CSSVariableValues;
|
||||
} // namespace mozilla
|
||||
struct nsRuleData;
|
||||
|
||||
@@ -43,7 +46,8 @@ public:
|
||||
eInitial, // 'initial'
|
||||
eInherit, // 'inherit'
|
||||
eUnset, // 'unset'
|
||||
eRevert // 'revert'
|
||||
eRevert, // 'revert'
|
||||
eRevertLayer // 'revert-layer'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -97,6 +101,15 @@ public:
|
||||
*/
|
||||
void PutRevert(const nsAString& aName);
|
||||
|
||||
/**
|
||||
* Adds or modifies an existing entry in this set of variable declarations
|
||||
* to have the value 'revert-layer'.
|
||||
*
|
||||
* @param aName The variable name (not including any "--" prefix that would
|
||||
* be part of the custom property name) whose value is to be set.
|
||||
*/
|
||||
void PutRevertLayer(const nsAString& aName);
|
||||
|
||||
/**
|
||||
* Adds or modifies an existing entry in this set of variable declarations
|
||||
* to have a token stream value.
|
||||
@@ -132,15 +145,36 @@ public:
|
||||
*/
|
||||
void AddVariablesToResolver(CSSVariableResolver* aResolver) const;
|
||||
|
||||
/**
|
||||
* Applies stored lower-priority declarations for custom property values that
|
||||
* resolved to 'revert-layer'.
|
||||
*/
|
||||
void ResolveRevertLayerFallbacks(const CSSVariableValues* aInherited,
|
||||
CSSVariableValues* aOutput) const;
|
||||
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
private:
|
||||
struct RevertLayerFallback
|
||||
{
|
||||
nsString mValue;
|
||||
nsString mSource;
|
||||
};
|
||||
|
||||
struct RevertLayerFallbackList
|
||||
{
|
||||
nsTArray<RevertLayerFallback> mItems;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds all the variable declarations from aOther into this object.
|
||||
*/
|
||||
void CopyVariablesFrom(const CSSVariableDeclarations& aOther);
|
||||
|
||||
nsDataHashtable<nsStringHashKey, nsString> mVariables;
|
||||
nsDataHashtable<nsStringHashKey, nsString> mRevertLayerSources;
|
||||
nsClassHashtable<nsStringHashKey, RevertLayerFallbackList>
|
||||
mRevertLayerFallbacks;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
@@ -121,6 +121,21 @@ CSSVariableValues::Put(const nsAString& aName,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CSSVariableValues::Remove(const nsAString& aName)
|
||||
{
|
||||
size_t id;
|
||||
if (!mVariableIDs.Get(aName, &id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mVariables.RemoveElementAt(id);
|
||||
mVariableIDs.Clear();
|
||||
for (size_t i = 0, n = mVariables.Length(); i < n; i++) {
|
||||
mVariableIDs.Put(mVariables[i].mVariableName, i);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CSSVariableValues::CopyVariablesFrom(const CSSVariableValues& aOther)
|
||||
{
|
||||
|
||||
@@ -94,6 +94,11 @@ public:
|
||||
nsCSSTokenSerializationType aFirstToken,
|
||||
nsCSSTokenSerializationType aLastToken);
|
||||
|
||||
/**
|
||||
* Removes an entry in this set of variable values.
|
||||
*/
|
||||
void Remove(const nsAString& aName);
|
||||
|
||||
/**
|
||||
* Copies the variables from this object into aResolver, marking them as
|
||||
* computed, inherited values.
|
||||
|
||||
@@ -576,6 +576,7 @@ Declaration::GetPropertyValueInternal(
|
||||
const nsCSSValue* tokenStream = nullptr;
|
||||
uint32_t totalCount = 0, importantCount = 0,
|
||||
initialCount = 0, inheritCount = 0, unsetCount = 0, revertCount = 0,
|
||||
revertLayerCount = 0,
|
||||
matchingTokenStreamCount = 0, nonMatchingTokenStreamCount = 0;
|
||||
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty,
|
||||
CSSEnabledState::eForAllContent) {
|
||||
@@ -603,6 +604,8 @@ Declaration::GetPropertyValueInternal(
|
||||
++unsetCount;
|
||||
} else if (val->GetUnit() == eCSSUnit_Revert) {
|
||||
++revertCount;
|
||||
} else if (val->GetUnit() == eCSSUnit_RevertLayer) {
|
||||
++revertLayerCount;
|
||||
} else if (val->GetUnit() == eCSSUnit_TokenStream) {
|
||||
if (val->GetTokenStreamValue()->mShorthandPropertyID == aProperty) {
|
||||
tokenStream = val;
|
||||
@@ -640,9 +643,19 @@ Declaration::GetPropertyValueInternal(
|
||||
nsCSSValue::eNormalized);
|
||||
return;
|
||||
}
|
||||
if (revertLayerCount == totalCount) {
|
||||
// Simplify serialization below by serializing revert-layer up-front.
|
||||
nsCSSValue revertLayer;
|
||||
revertLayer.SetRevertLayerValue();
|
||||
revertLayer.AppendToString(eCSSProperty_UNKNOWN, aValue,
|
||||
nsCSSValue::eNormalized);
|
||||
return;
|
||||
}
|
||||
if (initialCount != 0 || inheritCount != 0 || unsetCount != 0 ||
|
||||
revertCount != 0 || nonMatchingTokenStreamCount != 0) {
|
||||
// Case (2): partially initial, inherit, unset, revert, or token stream.
|
||||
revertCount != 0 || revertLayerCount != 0 ||
|
||||
nonMatchingTokenStreamCount != 0) {
|
||||
// Case (2): partially initial, inherit, unset, revert, revert-layer,
|
||||
// or token stream.
|
||||
return;
|
||||
}
|
||||
if (tokenStream) {
|
||||
@@ -1535,6 +1548,7 @@ Declaration::GetPropertyValueInternal(
|
||||
case eCSSUnit_Initial:
|
||||
case eCSSUnit_Unset:
|
||||
case eCSSUnit_Revert:
|
||||
case eCSSUnit_RevertLayer:
|
||||
return true;
|
||||
case eCSSUnit_Enumerated:
|
||||
// return false if there is a fallback value or <overflow-position>
|
||||
@@ -1749,6 +1763,10 @@ Declaration::AppendVariableAndValueToString(const nsAString& aName,
|
||||
aResult.AppendLiteral("revert");
|
||||
break;
|
||||
|
||||
case CSSVariableDeclarations::eRevertLayer:
|
||||
aResult.AppendLiteral("revert-layer");
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_ASSERT(false, "unexpected variable value type");
|
||||
}
|
||||
@@ -1989,6 +2007,10 @@ Declaration::GetVariableValue(const nsAString& aName, nsAString& aValue) const
|
||||
aValue.AppendLiteral("revert");
|
||||
break;
|
||||
|
||||
case CSSVariableDeclarations::eRevertLayer:
|
||||
aValue.AppendLiteral("revert-layer");
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_ASSERT(false, "unexpected variable value type");
|
||||
}
|
||||
@@ -2059,6 +2081,11 @@ Declaration::AddVariable(const nsAString& aName,
|
||||
variables->PutRevert(aName);
|
||||
break;
|
||||
|
||||
case CSSVariableDeclarations::eRevertLayer:
|
||||
MOZ_ASSERT(aValue.IsEmpty());
|
||||
variables->PutRevertLayer(aName);
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_ASSERT(false, "unexpected aType value");
|
||||
}
|
||||
|
||||
@@ -489,7 +489,7 @@ public:
|
||||
void SetNoneValue();
|
||||
void SetIntValue(int32_t aInt, Unit aUnit);
|
||||
template<typename T,
|
||||
typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
||||
typename = typename std::enable_if_t<std::is_enum_v<T>>>
|
||||
void SetIntValue(T aInt, Unit aUnit)
|
||||
{
|
||||
static_assert(mozilla::EnumTypeFitsWithin<T, int32_t>::value,
|
||||
|
||||
@@ -48,6 +48,102 @@ ShouldIgnoreColors(nsRuleData *aRuleData)
|
||||
!aRuleData->mPresContext->UseDocumentColors();
|
||||
}
|
||||
|
||||
static const int32_t kLayerIndexMax = 0x000fffff;
|
||||
static const int32_t kCascadeRankStride = kLayerIndexMax + 1;
|
||||
|
||||
static int32_t
|
||||
LayerIndexRank(bool aIsImportant, int32_t aLayerIndex)
|
||||
{
|
||||
MOZ_ASSERT(aLayerIndex >= 0);
|
||||
MOZ_ASSERT(aLayerIndex <= kLayerIndexMax);
|
||||
return aIsImportant ? kLayerIndexMax - aLayerIndex : aLayerIndex;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
CascadePrecedenceRank(SheetType aLevel, bool aIsImportant,
|
||||
int32_t aLayerIndex)
|
||||
{
|
||||
int32_t bucket = 0;
|
||||
bool isLayeredOrigin = false;
|
||||
|
||||
switch (aLevel) {
|
||||
case SheetType::Agent:
|
||||
bucket = aIsImportant ? 14 : 0;
|
||||
isLayeredOrigin = true;
|
||||
break;
|
||||
case SheetType::User:
|
||||
bucket = aIsImportant ? 13 : 1;
|
||||
isLayeredOrigin = true;
|
||||
break;
|
||||
case SheetType::PresHint:
|
||||
bucket = 2;
|
||||
break;
|
||||
case SheetType::SVGAttrAnimation:
|
||||
bucket = 3;
|
||||
break;
|
||||
case SheetType::Doc:
|
||||
bucket = aIsImportant ? 10 : 4;
|
||||
isLayeredOrigin = true;
|
||||
break;
|
||||
case SheetType::ScopedDoc:
|
||||
bucket = aIsImportant ? 9 : 5;
|
||||
isLayeredOrigin = true;
|
||||
break;
|
||||
case SheetType::StyleAttr:
|
||||
bucket = aIsImportant ? 11 : 6;
|
||||
break;
|
||||
case SheetType::Override:
|
||||
bucket = aIsImportant ? 12 : 7;
|
||||
break;
|
||||
case SheetType::Animation:
|
||||
bucket = 8;
|
||||
break;
|
||||
case SheetType::Transition:
|
||||
bucket = 15;
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("unexpected cascade level");
|
||||
break;
|
||||
}
|
||||
|
||||
return bucket * kCascadeRankStride +
|
||||
(isLayeredOrigin ? LayerIndexRank(aIsImportant, aLayerIndex) : 0);
|
||||
}
|
||||
|
||||
static bool
|
||||
ShouldIgnoreForRevertLayer(const nsCSSValue& aTargetValue,
|
||||
const nsRuleData* aRuleData)
|
||||
{
|
||||
SheetType sourceLevel = aTargetValue.GetRevertLayerOriginValue();
|
||||
bool sourceImportant = aTargetValue.GetRevertLayerImportanceValue();
|
||||
int32_t sourceLayerIndex = aTargetValue.GetRevertLayerLayerIndexValue();
|
||||
MOZ_ASSERT(sourceLevel != SheetType::Unknown,
|
||||
"revert-layer should have cascade metadata before mapping");
|
||||
|
||||
int32_t candidateRank =
|
||||
CascadePrecedenceRank(aRuleData->mLevel, aRuleData->mIsImportantRule,
|
||||
aRuleData->mLayerIndex);
|
||||
int32_t sourceRank =
|
||||
CascadePrecedenceRank(sourceLevel, sourceImportant, sourceLayerIndex);
|
||||
|
||||
if (candidateRank >= sourceRank) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!sourceImportant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceLevel == SheetType::StyleAttr) {
|
||||
return aRuleData->mLevel == SheetType::StyleAttr ||
|
||||
aRuleData->mLevel == SheetType::Animation;
|
||||
}
|
||||
|
||||
int32_t normalBoundary =
|
||||
CascadePrecedenceRank(sourceLevel, false, sourceLayerIndex);
|
||||
return candidateRank >= normalBoundary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to call |nsCSSValue::StartImageLoad()| on an image source.
|
||||
* Image sources are specified by |url()| or |-moz-image-rect()| function.
|
||||
@@ -175,7 +271,8 @@ MapSinglePropertyInto(nsCSSPropertyID aTargetProp,
|
||||
// CSSVariableImageTable, to give them the appropriate lifetime.
|
||||
MOZ_ASSERT(aTargetValue->GetUnit() == eCSSUnit_TokenStream ||
|
||||
aTargetValue->GetUnit() == eCSSUnit_Null ||
|
||||
aTargetValue->GetUnit() == eCSSUnit_Revert,
|
||||
aTargetValue->GetUnit() == eCSSUnit_Revert ||
|
||||
aTargetValue->GetUnit() == eCSSUnit_RevertLayer,
|
||||
"aTargetValue must only be a token stream (when re-parsing "
|
||||
"properties with variable references) or null");
|
||||
|
||||
@@ -365,6 +462,10 @@ nsCSSCompressedDataBlock::MapRuleInfoInto(nsRuleData *aRuleData) const
|
||||
if (aRuleData->mLevel >= target->GetCascadeOriginValue()) {
|
||||
continue;
|
||||
}
|
||||
} else if (target->GetUnit() == eCSSUnit_RevertLayer) {
|
||||
if (ShouldIgnoreForRevertLayer(*target, aRuleData)) {
|
||||
continue;
|
||||
}
|
||||
} else if (target->GetUnit() != eCSSUnit_Null) {
|
||||
continue;
|
||||
}
|
||||
@@ -377,8 +478,18 @@ nsCSSCompressedDataBlock::MapRuleInfoInto(nsRuleData *aRuleData) const
|
||||
// the rule walking will have just updated aRuleData.
|
||||
if (val->GetUnit() == eCSSUnit_TokenStream) {
|
||||
val->GetTokenStreamValue()->mLevel = aRuleData->mLevel;
|
||||
val->GetTokenStreamValue()->mIsImportant = aRuleData->mIsImportantRule;
|
||||
val->GetTokenStreamValue()->mLayerIndex = aRuleData->mLayerIndex;
|
||||
} else if (val->GetUnit() == eCSSUnit_Revert) {
|
||||
aRuleData->mConditions.SetUncacheable();
|
||||
} else if (val->GetUnit() == eCSSUnit_RevertLayer) {
|
||||
nsCSSValue revertLayer;
|
||||
revertLayer.SetRevertLayerValue(aRuleData->mLevel,
|
||||
aRuleData->mIsImportantRule,
|
||||
aRuleData->mLayerIndex);
|
||||
aRuleData->mConditions.SetUncacheable();
|
||||
MapSinglePropertyInto(iProp, &revertLayer, target, aRuleData);
|
||||
continue;
|
||||
}
|
||||
MapSinglePropertyInto(iProp, val, target, aRuleData);
|
||||
}
|
||||
@@ -819,6 +930,17 @@ nsCSSExpandedDataBlock::MapRuleInfoInto(nsCSSPropertyID aPropID,
|
||||
MOZ_ASSERT(dest->GetUnit() == eCSSUnit_TokenStream &&
|
||||
dest->GetTokenStreamValue()->mPropertyID == aPropID);
|
||||
|
||||
if (src->GetUnit() == eCSSUnit_RevertLayer) {
|
||||
nsCSSValueTokenStream* tokenStream = dest->GetTokenStreamValue();
|
||||
nsCSSValue revertLayer;
|
||||
revertLayer.SetRevertLayerValue(tokenStream->mLevel,
|
||||
tokenStream->mIsImportant,
|
||||
tokenStream->mLayerIndex);
|
||||
aRuleData->mConditions.SetUncacheable();
|
||||
MapSinglePropertyInto(physicalProp, &revertLayer, dest, aRuleData);
|
||||
return;
|
||||
}
|
||||
|
||||
CSSVariableImageTable::ReplaceAll(aRuleData->mStyleContext, aPropID, [=] {
|
||||
MapSinglePropertyInto(physicalProp, src, dest, aRuleData);
|
||||
});
|
||||
|
||||
@@ -481,6 +481,7 @@ CSS_KEY(repeat-x, repeat_x)
|
||||
CSS_KEY(repeat-y, repeat_y)
|
||||
CSS_KEY(reverse, reverse)
|
||||
CSS_KEY(revert, revert)
|
||||
CSS_KEY(revert-layer, revert_layer)
|
||||
CSS_KEY(ridge, ridge)
|
||||
CSS_KEY(right, right)
|
||||
CSS_KEY(rl, rl)
|
||||
@@ -814,4 +815,3 @@ CSS_KEY(text-before-edge, text_before_edge)
|
||||
CSS_KEY(use-script, use_script)
|
||||
CSS_KEY(-moz-crisp-edges, _moz_crisp_edges)
|
||||
CSS_KEY(space, space)
|
||||
|
||||
@@ -9126,6 +9126,10 @@ CSSParserImpl::ParseVariant(nsCSSValue& aValue,
|
||||
aValue.SetRevertValue(mLevel);
|
||||
return CSSParseResult::Ok;
|
||||
}
|
||||
else if (eCSSKeyword_revert_layer == keyword) {
|
||||
aValue.SetRevertLayerValue();
|
||||
return CSSParseResult::Ok;
|
||||
}
|
||||
}
|
||||
if ((aVariantMask & VARIANT_NONE) != 0) {
|
||||
if (eCSSKeyword_none == keyword) {
|
||||
@@ -9330,7 +9334,8 @@ CSSParserImpl::ParseVariant(nsCSSValue& aValue,
|
||||
tk->mIdent.LowerCaseEqualsLiteral("initial") ||
|
||||
(tk->mIdent.LowerCaseEqualsLiteral("unset") &&
|
||||
nsLayoutUtils::UnsetValueEnabled()) ||
|
||||
tk->mIdent.LowerCaseEqualsLiteral("revert")))) {
|
||||
tk->mIdent.LowerCaseEqualsLiteral("revert") ||
|
||||
tk->mIdent.LowerCaseEqualsLiteral("revert-layer")))) {
|
||||
aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident);
|
||||
return CSSParseResult::Ok;
|
||||
}
|
||||
@@ -9406,6 +9411,7 @@ CSSParserImpl::ParseCustomIdent(nsCSSValue& aValue,
|
||||
keyword == eCSSKeyword_initial ||
|
||||
keyword == eCSSKeyword_unset ||
|
||||
keyword == eCSSKeyword_revert ||
|
||||
keyword == eCSSKeyword_revert_layer ||
|
||||
keyword == eCSSKeyword_default ||
|
||||
(aPropertyKTable &&
|
||||
nsCSSProps::FindIndexOfKeyword(keyword, aPropertyKTable) >= 0)) {
|
||||
@@ -12595,6 +12601,12 @@ CSSParserImpl::ParseChoice(nsCSSValue aValues[],
|
||||
}
|
||||
found = ((1 << aNumIDs) - 1);
|
||||
}
|
||||
else if (eCSSUnit_RevertLayer == aValues[0].GetUnit()) { // one revert-layer, all revert-layer
|
||||
for (loop = 1; loop < aNumIDs; loop++) {
|
||||
aValues[loop].SetRevertLayerValue();
|
||||
}
|
||||
found = ((1 << aNumIDs) - 1);
|
||||
}
|
||||
}
|
||||
else { // more than one value, verify no inherits, initials, unsets, or reverts
|
||||
for (loop = 0; loop < aNumIDs; loop++) {
|
||||
@@ -12610,7 +12622,8 @@ CSSParserImpl::ParseChoice(nsCSSValue aValues[],
|
||||
found = -1;
|
||||
break;
|
||||
}
|
||||
else if (eCSSUnit_Revert == aValues[loop].GetUnit()) {
|
||||
else if (eCSSUnit_Revert == aValues[loop].GetUnit() ||
|
||||
eCSSUnit_RevertLayer == aValues[loop].GetUnit()) {
|
||||
found = -1;
|
||||
break;
|
||||
}
|
||||
@@ -12658,7 +12671,8 @@ CSSParserImpl::ParseBoxProperties(const nsCSSPropertyID aPropIDs[])
|
||||
if (eCSSUnit_Inherit == unit ||
|
||||
eCSSUnit_Initial == unit ||
|
||||
eCSSUnit_Unset == unit ||
|
||||
eCSSUnit_Revert == unit) {
|
||||
eCSSUnit_Revert == unit ||
|
||||
eCSSUnit_RevertLayer == unit) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -12766,7 +12780,8 @@ CSSParserImpl::ParseBoxCornerRadius(nsCSSPropertyID aPropID)
|
||||
if (dimenX.GetUnit() != eCSSUnit_Inherit &&
|
||||
dimenX.GetUnit() != eCSSUnit_Initial &&
|
||||
dimenX.GetUnit() != eCSSUnit_Unset &&
|
||||
dimenX.GetUnit() != eCSSUnit_Revert) {
|
||||
dimenX.GetUnit() != eCSSUnit_Revert &&
|
||||
dimenX.GetUnit() != eCSSUnit_RevertLayer) {
|
||||
if (ParseNonNegativeVariant(dimenY, VARIANT_LP | VARIANT_CALC, nullptr) ==
|
||||
CSSParseResult::Error) {
|
||||
return false;
|
||||
@@ -12831,7 +12846,8 @@ CSSParserImpl::ParseBoxCornerRadiiInternals(nsCSSValue array[])
|
||||
if (eCSSUnit_Inherit == unit ||
|
||||
eCSSUnit_Initial == unit ||
|
||||
eCSSUnit_Unset == unit ||
|
||||
eCSSUnit_Revert == unit)
|
||||
eCSSUnit_Revert == unit ||
|
||||
eCSSUnit_RevertLayer == unit)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -13642,6 +13658,7 @@ CSSParserImpl::ParseFontDescriptorValue(nsCSSFontDesc aDescID,
|
||||
aValue.GetUnit() != eCSSUnit_Initial &&
|
||||
aValue.GetUnit() != eCSSUnit_Unset &&
|
||||
aValue.GetUnit() != eCSSUnit_Revert &&
|
||||
aValue.GetUnit() != eCSSUnit_RevertLayer &&
|
||||
(aValue.GetUnit() != eCSSUnit_Enumerated ||
|
||||
(aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_BOLDER &&
|
||||
aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_LIGHTER)));
|
||||
@@ -13905,7 +13922,8 @@ CSSParserImpl::ParseImageLayersItem(
|
||||
if (keyword == eCSSKeyword_inherit ||
|
||||
keyword == eCSSKeyword_initial ||
|
||||
keyword == eCSSKeyword_unset ||
|
||||
keyword == eCSSKeyword_revert) {
|
||||
keyword == eCSSKeyword_revert ||
|
||||
keyword == eCSSKeyword_revert_layer) {
|
||||
return false;
|
||||
} else if (keyword == eCSSKeyword_none) {
|
||||
if (haveImage)
|
||||
@@ -14262,7 +14280,8 @@ bool CSSParserImpl::ParseBoxPositionValues(nsCSSValuePair &aOut,
|
||||
if (eCSSUnit_Inherit == xValue.GetUnit() ||
|
||||
eCSSUnit_Initial == xValue.GetUnit() ||
|
||||
eCSSUnit_Unset == xValue.GetUnit() ||
|
||||
eCSSUnit_Revert == xValue.GetUnit()) { // both are inherit, initial, unset, or revert
|
||||
eCSSUnit_Revert == xValue.GetUnit() ||
|
||||
eCSSUnit_RevertLayer == xValue.GetUnit()) {
|
||||
yValue = xValue;
|
||||
return true;
|
||||
}
|
||||
@@ -15094,6 +15113,7 @@ CSSParserImpl::ParseBorderSide(const nsCSSPropertyID aPropIDs[],
|
||||
case eCSSUnit_Initial:
|
||||
case eCSSUnit_Unset:
|
||||
case eCSSUnit_Revert:
|
||||
case eCSSUnit_RevertLayer:
|
||||
extraValue = values[0];
|
||||
// Set value of border-image properties to initial/inherit/unset/revert
|
||||
AppendValue(eCSSProperty_border_image_source, extraValue);
|
||||
@@ -15860,7 +15880,8 @@ CSSParserImpl::ParseFont()
|
||||
if (eCSSUnit_Inherit == family.GetUnit() ||
|
||||
eCSSUnit_Initial == family.GetUnit() ||
|
||||
eCSSUnit_Unset == family.GetUnit() ||
|
||||
eCSSUnit_Revert == family.GetUnit()) {
|
||||
eCSSUnit_Revert == family.GetUnit() ||
|
||||
eCSSUnit_RevertLayer == family.GetUnit()) {
|
||||
AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
|
||||
AppendValue(eCSSProperty_font_family, family);
|
||||
AppendValue(eCSSProperty_font_style, family);
|
||||
@@ -15929,7 +15950,8 @@ CSSParserImpl::ParseFont()
|
||||
eCSSUnit_Inherit == values[kFontStyleIndex].GetUnit() ||
|
||||
eCSSUnit_Initial == values[kFontStyleIndex].GetUnit() ||
|
||||
eCSSUnit_Unset == values[kFontStyleIndex].GetUnit() ||
|
||||
eCSSUnit_Revert == values[kFontStyleIndex].GetUnit() ) { // illegal data
|
||||
eCSSUnit_Revert == values[kFontStyleIndex].GetUnit() ||
|
||||
eCSSUnit_RevertLayer == values[kFontStyleIndex].GetUnit() ) { // illegal data
|
||||
return false;
|
||||
}
|
||||
if ((found & (1 << kFontStyleIndex)) == 0) {
|
||||
@@ -15988,7 +16010,8 @@ CSSParserImpl::ParseFont()
|
||||
if (eCSSUnit_Inherit != family.GetUnit() &&
|
||||
eCSSUnit_Initial != family.GetUnit() &&
|
||||
eCSSUnit_Unset != family.GetUnit() &&
|
||||
eCSSUnit_Revert != family.GetUnit()) {
|
||||
eCSSUnit_Revert != family.GetUnit() &&
|
||||
eCSSUnit_RevertLayer != family.GetUnit()) {
|
||||
AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
|
||||
AppendValue(eCSSProperty_font_family, family);
|
||||
AppendValue(eCSSProperty_font_style, values[kFontStyleIndex]);
|
||||
@@ -16034,7 +16057,8 @@ CSSParserImpl::ParseFontSynthesis(nsCSSValue& aValue)
|
||||
eCSSUnit_Initial == aValue.GetUnit() ||
|
||||
eCSSUnit_Inherit == aValue.GetUnit() ||
|
||||
eCSSUnit_Unset == aValue.GetUnit() ||
|
||||
eCSSUnit_Revert == aValue.GetUnit() )
|
||||
eCSSUnit_Revert == aValue.GetUnit() ||
|
||||
eCSSUnit_RevertLayer == aValue.GetUnit() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -16657,6 +16681,9 @@ CSSParserImpl::ParseFamily(nsCSSValue& aValue)
|
||||
case eCSSKeyword_revert:
|
||||
aValue.SetRevertValue(mLevel);
|
||||
return true;
|
||||
case eCSSKeyword_revert_layer:
|
||||
aValue.SetRevertLayerValue();
|
||||
return true;
|
||||
case eCSSKeyword__moz_use_system_font:
|
||||
if (!IsParsingCompoundProperty()) {
|
||||
aValue.SetSystemFontValue();
|
||||
@@ -16691,6 +16718,7 @@ CSSParserImpl::ParseFamily(nsCSSValue& aValue)
|
||||
case eCSSKeyword_initial:
|
||||
case eCSSKeyword_default:
|
||||
case eCSSKeyword_revert:
|
||||
case eCSSKeyword_revert_layer:
|
||||
case eCSSKeyword__moz_use_system_font:
|
||||
return false;
|
||||
case eCSSKeyword_unset:
|
||||
@@ -18340,7 +18368,8 @@ bool CSSParserImpl::ParseTransformOrigin(bool aPerspective)
|
||||
if (position.mXValue.GetUnit() == eCSSUnit_Inherit ||
|
||||
position.mXValue.GetUnit() == eCSSUnit_Initial ||
|
||||
position.mXValue.GetUnit() == eCSSUnit_Unset ||
|
||||
position.mXValue.GetUnit() == eCSSUnit_Revert) {
|
||||
position.mXValue.GetUnit() == eCSSUnit_Revert ||
|
||||
position.mXValue.GetUnit() == eCSSUnit_RevertLayer) {
|
||||
MOZ_ASSERT(position.mXValue == position.mYValue,
|
||||
"inherit/initial/unset/revert only half?");
|
||||
AppendValue(prop, position.mXValue);
|
||||
@@ -18564,14 +18593,15 @@ CSSParserImpl::ParseTransitionProperty()
|
||||
}
|
||||
if (cur->mValue.GetUnit() == eCSSUnit_Ident) {
|
||||
nsDependentString str(cur->mValue.GetStringBufferValue());
|
||||
// Exclude 'none', 'inherit', 'initial', 'unset', and 'revert'
|
||||
// according to the same rules as for 'counter-reset' in CSS 2.1.
|
||||
// Exclude CSS-wide keywords and 'none' according to the same rules
|
||||
// as for 'counter-reset' in CSS 2.1.
|
||||
if (str.LowerCaseEqualsLiteral("none") ||
|
||||
str.LowerCaseEqualsLiteral("inherit") ||
|
||||
str.LowerCaseEqualsLiteral("initial") ||
|
||||
(str.LowerCaseEqualsLiteral("unset") &&
|
||||
nsLayoutUtils::UnsetValueEnabled()) ||
|
||||
str.LowerCaseEqualsLiteral("revert")) {
|
||||
str.LowerCaseEqualsLiteral("revert") ||
|
||||
str.LowerCaseEqualsLiteral("revert-layer")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -19484,8 +19514,7 @@ CSSParserImpl::ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
|
||||
}
|
||||
}
|
||||
|
||||
// Look for 'initial', 'inherit', 'unset', or 'revert' as the first
|
||||
// non-white space token.
|
||||
// Look for CSS-wide keywords as the first non-white space token.
|
||||
CSSVariableDeclarations::Type type = CSSVariableDeclarations::eTokenStream;
|
||||
if (mToken.mType == eCSSToken_Ident) {
|
||||
if (mToken.mIdent.LowerCaseEqualsLiteral("initial")) {
|
||||
@@ -19496,6 +19525,8 @@ CSSParserImpl::ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
|
||||
type = CSSVariableDeclarations::eUnset;
|
||||
} else if (mToken.mIdent.LowerCaseEqualsLiteral("revert")) {
|
||||
type = CSSVariableDeclarations::eRevert;
|
||||
} else if (mToken.mIdent.LowerCaseEqualsLiteral("revert-layer")) {
|
||||
type = CSSVariableDeclarations::eRevertLayer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
// 'inherit' and 'initial'
|
||||
#define VARIANT_OPACITY 0x008000 // Take floats and percents as input, output float.
|
||||
#define VARIANT_AUTO 0x010000 // A
|
||||
#define VARIANT_INHERIT 0x020000 // H eCSSUnit_Initial, eCSSUnit_Inherit, eCSSUnit_Unset, eCSSUnit_Revert
|
||||
#define VARIANT_INHERIT 0x020000 // H eCSSUnit_Initial, eCSSUnit_Inherit, eCSSUnit_Unset, eCSSUnit_Revert, eCSSUnit_RevertLayer
|
||||
#define VARIANT_NONE 0x040000 // O
|
||||
#define VARIANT_NORMAL 0x080000 // M
|
||||
#define VARIANT_SYSFONT 0x100000 // eCSSUnit_System_Font
|
||||
@@ -364,7 +364,7 @@ public:
|
||||
}
|
||||
|
||||
template<typename T,
|
||||
typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
||||
typename = typename std::enable_if_t<std::is_enum_v<T>>>
|
||||
KTableEntry(nsCSSKeyword aKeyword, T aValue)
|
||||
: mKeyword(aKeyword)
|
||||
, mValue(static_cast<int16_t>(aValue))
|
||||
@@ -451,7 +451,7 @@ public:
|
||||
static nsCSSKeyword ValueToKeywordEnum(int32_t aValue,
|
||||
const KTableEntry aTable[]);
|
||||
template<typename T,
|
||||
typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
||||
typename = typename std::enable_if_t<std::is_enum_v<T>>>
|
||||
static nsCSSKeyword ValueToKeywordEnum(T aValue,
|
||||
const KTableEntry aTable[])
|
||||
{
|
||||
@@ -463,7 +463,7 @@ public:
|
||||
static const nsAFlatCString& ValueToKeyword(int32_t aValue,
|
||||
const KTableEntry aTable[]);
|
||||
template<typename T,
|
||||
typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
||||
typename = typename std::enable_if_t<std::is_enum_v<T>>>
|
||||
static const nsAFlatCString& ValueToKeyword(T aValue,
|
||||
const KTableEntry aTable[])
|
||||
{
|
||||
|
||||
@@ -192,6 +192,9 @@ nsCSSValue::nsCSSValue(const nsCSSValue& aCopy)
|
||||
else if (eCSSUnit_Revert == mUnit) {
|
||||
mValue.mCascadeOrigin = aCopy.mValue.mCascadeOrigin;
|
||||
}
|
||||
else if (eCSSUnit_RevertLayer == mUnit) {
|
||||
mValue.mInt = aCopy.mValue.mInt;
|
||||
}
|
||||
else if (UnitHasArrayValue()) {
|
||||
mValue.mArray = aCopy.mValue.mArray;
|
||||
mValue.mArray->AddRef();
|
||||
@@ -316,6 +319,9 @@ bool nsCSSValue::operator==(const nsCSSValue& aOther) const
|
||||
else if (eCSSUnit_Revert == mUnit) {
|
||||
return mValue.mCascadeOrigin == aOther.mValue.mCascadeOrigin;
|
||||
}
|
||||
else if (eCSSUnit_RevertLayer == mUnit) {
|
||||
return mValue.mInt == aOther.mValue.mInt;
|
||||
}
|
||||
else if (UnitHasArrayValue()) {
|
||||
return *mValue.mArray == *aOther.mValue.mArray;
|
||||
}
|
||||
@@ -593,6 +599,31 @@ nsCSSValue::SetCascadeOriginValue(mozilla::SheetType aValue, nsCSSUnit aUnit)
|
||||
mValue.mCascadeOrigin = aValue;
|
||||
}
|
||||
|
||||
static const int32_t kRevertLayerMask = 0x000fffff;
|
||||
static const int32_t kRevertLayerImportant = 0x00100000;
|
||||
static const int32_t kRevertLayerOriginShift = 21;
|
||||
|
||||
mozilla::SheetType
|
||||
nsCSSValue::GetRevertLayerOriginValue() const
|
||||
{
|
||||
MOZ_ASSERT(mUnit == eCSSUnit_RevertLayer, "not a revert-layer value");
|
||||
return mozilla::SheetType(uint32_t(mValue.mInt) >> kRevertLayerOriginShift);
|
||||
}
|
||||
|
||||
bool
|
||||
nsCSSValue::GetRevertLayerImportanceValue() const
|
||||
{
|
||||
MOZ_ASSERT(mUnit == eCSSUnit_RevertLayer, "not a revert-layer value");
|
||||
return (mValue.mInt & kRevertLayerImportant) != 0;
|
||||
}
|
||||
|
||||
int32_t
|
||||
nsCSSValue::GetRevertLayerLayerIndexValue() const
|
||||
{
|
||||
MOZ_ASSERT(mUnit == eCSSUnit_RevertLayer, "not a revert-layer value");
|
||||
return mValue.mInt & kRevertLayerMask;
|
||||
}
|
||||
|
||||
void nsCSSValue::SetArrayValue(nsCSSValue::Array* aValue, nsCSSUnit aUnit)
|
||||
{
|
||||
Reset();
|
||||
@@ -663,7 +694,9 @@ void nsCSSValue::SetPairValue(const nsCSSValuePair* aValue)
|
||||
aValue->mXValue.GetUnit() != eCSSUnit_Unset &&
|
||||
aValue->mYValue.GetUnit() != eCSSUnit_Unset &&
|
||||
aValue->mXValue.GetUnit() != eCSSUnit_Revert &&
|
||||
aValue->mYValue.GetUnit() != eCSSUnit_Revert,
|
||||
aValue->mYValue.GetUnit() != eCSSUnit_Revert &&
|
||||
aValue->mXValue.GetUnit() != eCSSUnit_RevertLayer &&
|
||||
aValue->mYValue.GetUnit() != eCSSUnit_RevertLayer,
|
||||
"missing or inappropriate pair value");
|
||||
Reset();
|
||||
mUnit = eCSSUnit_Pair;
|
||||
@@ -683,7 +716,9 @@ void nsCSSValue::SetPairValue(const nsCSSValue& xValue,
|
||||
xValue.GetUnit() != eCSSUnit_Unset &&
|
||||
yValue.GetUnit() != eCSSUnit_Unset &&
|
||||
xValue.GetUnit() != eCSSUnit_Revert &&
|
||||
yValue.GetUnit() != eCSSUnit_Revert,
|
||||
yValue.GetUnit() != eCSSUnit_Revert &&
|
||||
xValue.GetUnit() != eCSSUnit_RevertLayer &&
|
||||
yValue.GetUnit() != eCSSUnit_RevertLayer,
|
||||
"inappropriate pair value");
|
||||
Reset();
|
||||
mUnit = eCSSUnit_Pair;
|
||||
@@ -709,7 +744,10 @@ void nsCSSValue::SetTripletValue(const nsCSSValueTriplet* aValue)
|
||||
aValue->mZValue.GetUnit() != eCSSUnit_Unset &&
|
||||
aValue->mXValue.GetUnit() != eCSSUnit_Revert &&
|
||||
aValue->mYValue.GetUnit() != eCSSUnit_Revert &&
|
||||
aValue->mZValue.GetUnit() != eCSSUnit_Revert,
|
||||
aValue->mZValue.GetUnit() != eCSSUnit_Revert &&
|
||||
aValue->mXValue.GetUnit() != eCSSUnit_RevertLayer &&
|
||||
aValue->mYValue.GetUnit() != eCSSUnit_RevertLayer &&
|
||||
aValue->mZValue.GetUnit() != eCSSUnit_RevertLayer,
|
||||
"missing or inappropriate triplet value");
|
||||
Reset();
|
||||
mUnit = eCSSUnit_Triplet;
|
||||
@@ -735,7 +773,10 @@ void nsCSSValue::SetTripletValue(const nsCSSValue& xValue,
|
||||
zValue.GetUnit() != eCSSUnit_Unset &&
|
||||
xValue.GetUnit() != eCSSUnit_Revert &&
|
||||
yValue.GetUnit() != eCSSUnit_Revert &&
|
||||
zValue.GetUnit() != eCSSUnit_Revert,
|
||||
zValue.GetUnit() != eCSSUnit_Revert &&
|
||||
xValue.GetUnit() != eCSSUnit_RevertLayer &&
|
||||
yValue.GetUnit() != eCSSUnit_RevertLayer &&
|
||||
zValue.GetUnit() != eCSSUnit_RevertLayer,
|
||||
"inappropriate triplet value");
|
||||
Reset();
|
||||
mUnit = eCSSUnit_Triplet;
|
||||
@@ -851,6 +892,26 @@ nsCSSValue::SetRevertValue(mozilla::SheetType aValue)
|
||||
SetCascadeOriginValue(aValue, eCSSUnit_Revert);
|
||||
}
|
||||
|
||||
void
|
||||
nsCSSValue::SetRevertLayerValue()
|
||||
{
|
||||
SetRevertLayerValue(mozilla::SheetType::Unknown, false, 0);
|
||||
}
|
||||
|
||||
void
|
||||
nsCSSValue::SetRevertLayerValue(mozilla::SheetType aLevel,
|
||||
bool aIsImportant,
|
||||
int32_t aLayerIndex)
|
||||
{
|
||||
MOZ_ASSERT(aLayerIndex >= 0 && aLayerIndex <= kRevertLayerMask,
|
||||
"cascade layer index is out of range");
|
||||
Reset();
|
||||
mUnit = eCSSUnit_RevertLayer;
|
||||
mValue.mInt = (uint32_t(aLevel) << kRevertLayerOriginShift) |
|
||||
(aIsImportant ? kRevertLayerImportant : 0) |
|
||||
(aLayerIndex & kRevertLayerMask);
|
||||
}
|
||||
|
||||
void nsCSSValue::SetNoneValue()
|
||||
{
|
||||
Reset();
|
||||
@@ -2234,6 +2295,7 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
|
||||
case eCSSUnit_Initial: aResult.AppendLiteral("initial"); break;
|
||||
case eCSSUnit_Unset: aResult.AppendLiteral("unset"); break;
|
||||
case eCSSUnit_Revert: aResult.AppendLiteral("revert"); break;
|
||||
case eCSSUnit_RevertLayer: aResult.AppendLiteral("revert-layer"); break;
|
||||
case eCSSUnit_None: aResult.AppendLiteral("none"); break;
|
||||
case eCSSUnit_Normal: aResult.AppendLiteral("normal"); break;
|
||||
case eCSSUnit_System_Font: aResult.AppendLiteral("-moz-use-system-font"); break;
|
||||
@@ -2511,6 +2573,7 @@ nsCSSValue::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
|
||||
|
||||
// Cascade Origin: nothing extra to measure.
|
||||
case eCSSUnit_Revert:
|
||||
case eCSSUnit_RevertLayer:
|
||||
break;
|
||||
|
||||
// Float: nothing extra to measure.
|
||||
@@ -2836,7 +2899,8 @@ nsCSSRect::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
|
||||
mTop.GetUnit() != eCSSUnit_Inherit &&
|
||||
mTop.GetUnit() != eCSSUnit_Initial &&
|
||||
mTop.GetUnit() != eCSSUnit_Unset &&
|
||||
mTop.GetUnit() != eCSSUnit_Revert,
|
||||
mTop.GetUnit() != eCSSUnit_Revert &&
|
||||
mTop.GetUnit() != eCSSUnit_RevertLayer,
|
||||
"parser should have used a bare value");
|
||||
|
||||
if (eCSSProperty_border_image_slice == aProperty ||
|
||||
@@ -3007,6 +3071,7 @@ nsCSSValuePairList::AppendToString(nsCSSPropertyID aProperty,
|
||||
item->mXValue.GetUnit() != eCSSUnit_Initial &&
|
||||
item->mXValue.GetUnit() != eCSSUnit_Unset &&
|
||||
item->mXValue.GetUnit() != eCSSUnit_Revert &&
|
||||
item->mXValue.GetUnit() != eCSSUnit_RevertLayer &&
|
||||
item->mYValue.GetUnit() != eCSSUnit_Null) {
|
||||
aResult.Append(char16_t(' '));
|
||||
item->mYValue.AppendToString(aProperty, aResult, aSerialization);
|
||||
@@ -3443,6 +3508,8 @@ nsCSSValueTokenStream::nsCSSValueTokenStream()
|
||||
: mPropertyID(eCSSProperty_UNKNOWN)
|
||||
, mShorthandPropertyID(eCSSProperty_UNKNOWN)
|
||||
, mLevel(SheetType::Count)
|
||||
, mIsImportant(false)
|
||||
, mLayerIndex(0)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsCSSValueTokenStream);
|
||||
}
|
||||
|
||||
@@ -490,6 +490,7 @@ enum nsCSSUnit {
|
||||
eCSSUnit_GridTemplateAreas = 44, // (GridTemplateAreasValue*)
|
||||
// for grid-template-areas
|
||||
eCSSUnit_Revert = 45, // (SheetType) value is the current cascade origin
|
||||
eCSSUnit_RevertLayer = 46, // (int32_t) packed cascade layer rollback data
|
||||
|
||||
eCSSUnit_Pair = 50, // (nsCSSValuePair*) pair of values
|
||||
eCSSUnit_Triplet = 51, // (nsCSSValueTriplet*) triplet of values
|
||||
@@ -939,6 +940,9 @@ public:
|
||||
MOZ_ASSERT(mUnit == eCSSUnit_Revert, "not a cascade origin value");
|
||||
return mValue.mCascadeOrigin;
|
||||
}
|
||||
mozilla::SheetType GetRevertLayerOriginValue() const;
|
||||
bool GetRevertLayerImportanceValue() const;
|
||||
int32_t GetRevertLayerLayerIndexValue() const;
|
||||
|
||||
void Reset() // sets to null
|
||||
{
|
||||
@@ -951,7 +955,7 @@ private:
|
||||
public:
|
||||
void SetIntValue(int32_t aValue, nsCSSUnit aUnit);
|
||||
template<typename T,
|
||||
typename = typename std::enable_if<std::is_enum<T>::value>::type>
|
||||
typename = typename std::enable_if_t<std::is_enum_v<T>>>
|
||||
void SetIntValue(T aValue, nsCSSUnit aUnit)
|
||||
{
|
||||
static_assert(mozilla::EnumTypeFitsWithin<T, int32_t>::value,
|
||||
@@ -994,6 +998,10 @@ public:
|
||||
void SetInitialValue();
|
||||
void SetUnsetValue();
|
||||
void SetRevertValue(mozilla::SheetType aValue);
|
||||
void SetRevertLayerValue();
|
||||
void SetRevertLayerValue(mozilla::SheetType aLevel,
|
||||
bool aIsImportant,
|
||||
int32_t aLayerIndex);
|
||||
void SetNoneValue();
|
||||
void SetAllValue();
|
||||
void SetNormalValue();
|
||||
@@ -1837,6 +1845,8 @@ public:
|
||||
mShorthandPropertyID == aOther.mShorthandPropertyID &&
|
||||
mTokenStream.Equals(aOther.mTokenStream) &&
|
||||
mLevel == aOther.mLevel &&
|
||||
mIsImportant == aOther.mIsImportant &&
|
||||
mLayerIndex == aOther.mLayerIndex &&
|
||||
(mBaseURI == aOther.mBaseURI ||
|
||||
(mBaseURI && aOther.mBaseURI &&
|
||||
NS_SUCCEEDED(mBaseURI->Equals(aOther.mBaseURI, &eq)) &&
|
||||
@@ -1886,6 +1896,8 @@ public:
|
||||
uint32_t mLineNumber;
|
||||
uint32_t mLineOffset;
|
||||
mozilla::SheetType mLevel;
|
||||
bool mIsImportant;
|
||||
int32_t mLayerIndex;
|
||||
|
||||
private:
|
||||
nsCSSValueTokenStream(const nsCSSValueTokenStream& aOther) = delete;
|
||||
|
||||
@@ -28,6 +28,9 @@ nsRuleData::GetPoisonOffset()
|
||||
nsRuleData::nsRuleData(uint32_t aSIDs, nsCSSValue* aValueStorage,
|
||||
nsPresContext* aContext, nsStyleContext* aStyleContext)
|
||||
: mSIDs(aSIDs),
|
||||
mIsImportantRule(false),
|
||||
mLevel(mozilla::SheetType::Unknown),
|
||||
mLayerIndex(0),
|
||||
mPresContext(aContext),
|
||||
mStyleContext(aStyleContext),
|
||||
mValueStorage(aValueStorage)
|
||||
|
||||
@@ -31,6 +31,7 @@ struct nsRuleData
|
||||
mozilla::RuleNodeCacheConditions mConditions;
|
||||
bool mIsImportantRule;
|
||||
mozilla::SheetType mLevel;
|
||||
int32_t mLayerIndex;
|
||||
nsPresContext* const mPresContext;
|
||||
nsStyleContext* const mStyleContext;
|
||||
|
||||
|
||||
+147
-12
@@ -188,9 +188,10 @@ nsRuleNode::ChildrenHashHashKey(const void *aKey)
|
||||
{
|
||||
const nsRuleNode::Key *key =
|
||||
static_cast<const nsRuleNode::Key*>(aKey);
|
||||
// Disagreement on importance and level for the same rule is extremely
|
||||
// rare, so hash just on the rule.
|
||||
return PLDHashTable::HashVoidPtrKeyStub(key->mRule);
|
||||
return PLDHashTable::HashVoidPtrKeyStub(key->mRule) ^
|
||||
(uint32_t(key->mLayerIndex) << 4) ^
|
||||
(uint32_t(key->mLevel) << 1) ^
|
||||
(key->mIsImportantRule ? 1 : 0);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
@@ -2270,18 +2271,19 @@ already_AddRefed<nsRuleNode>
|
||||
nsRuleNode::CreateRootNode(nsPresContext* aPresContext)
|
||||
{
|
||||
return do_AddRef(new (aPresContext)
|
||||
nsRuleNode(aPresContext, nullptr, nullptr, SheetType::Unknown, false));
|
||||
nsRuleNode(aPresContext, nullptr, nullptr, SheetType::Unknown, false, 0));
|
||||
}
|
||||
|
||||
nsRuleNode::nsRuleNode(nsPresContext* aContext, nsRuleNode* aParent,
|
||||
nsIStyleRule* aRule, SheetType aLevel,
|
||||
bool aIsImportant)
|
||||
bool aIsImportant, int32_t aLayerIndex)
|
||||
: mPresContext(aContext),
|
||||
mParent(aParent),
|
||||
mRule(aRule),
|
||||
mNextSibling(nullptr),
|
||||
mDependentBits((uint32_t(aLevel) << NS_RULE_NODE_LEVEL_SHIFT) |
|
||||
(aIsImportant ? NS_RULE_NODE_IS_IMPORTANT : 0)),
|
||||
mLayerIndex(aLayerIndex),
|
||||
mNoneBits(aParent ? aParent->mNoneBits & NS_RULE_NODE_HAS_ANIMATION_DATA :
|
||||
0),
|
||||
mRefCnt(0)
|
||||
@@ -2295,6 +2297,8 @@ nsRuleNode::nsRuleNode(nsPresContext* aContext, nsRuleNode* aParent,
|
||||
|
||||
NS_ASSERTION(IsRoot() || GetLevel() == aLevel, "not enough bits");
|
||||
NS_ASSERTION(IsRoot() || IsImportantRule() == aIsImportant, "yikes");
|
||||
NS_ASSERTION(IsRoot() || GetLayerIndex() == aLayerIndex,
|
||||
"bad layer index");
|
||||
aContext->StyleSet()->RuleNodeUnused(this, /* aMayGC = */ false);
|
||||
|
||||
// nsStyleSet::GetContext depends on there being only one animation
|
||||
@@ -2319,7 +2323,7 @@ nsRuleNode::~nsRuleNode()
|
||||
|
||||
nsRuleNode*
|
||||
nsRuleNode::Transition(nsIStyleRule* aRule, SheetType aLevel,
|
||||
bool aIsImportantRule)
|
||||
bool aIsImportantRule, int32_t aLayerIndex)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
{
|
||||
@@ -2330,7 +2334,7 @@ nsRuleNode::Transition(nsIStyleRule* aRule, SheetType aLevel,
|
||||
#endif
|
||||
|
||||
nsRuleNode* next = nullptr;
|
||||
nsRuleNode::Key key(aRule, aLevel, aIsImportantRule);
|
||||
nsRuleNode::Key key(aRule, aLevel, aIsImportantRule, aLayerIndex);
|
||||
|
||||
if (HaveChildren() && !ChildrenAreHashed()) {
|
||||
int32_t numKids = 0;
|
||||
@@ -2357,12 +2361,14 @@ nsRuleNode::Transition(nsIStyleRule* aRule, SheetType aLevel,
|
||||
next = entry->mRuleNode;
|
||||
} else {
|
||||
next = entry->mRuleNode = new (mPresContext)
|
||||
nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule);
|
||||
nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule,
|
||||
aLayerIndex);
|
||||
}
|
||||
} else if (!next) {
|
||||
// Create the new entry in our list.
|
||||
next = new (mPresContext)
|
||||
nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule);
|
||||
nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule,
|
||||
aLayerIndex);
|
||||
next->mNextSibling = ChildrenList();
|
||||
SetChildrenList(next);
|
||||
}
|
||||
@@ -2545,7 +2551,8 @@ ExamineCSSValue(const nsCSSValue& aValue,
|
||||
++aInheritedCount;
|
||||
} else if (aValue.GetUnit() == eCSSUnit_Unset) {
|
||||
++aUnsetCount;
|
||||
} else if (aValue.GetUnit() == eCSSUnit_Revert) {
|
||||
} else if (aValue.GetUnit() == eCSSUnit_Revert ||
|
||||
aValue.GetUnit() == eCSSUnit_RevertLayer) {
|
||||
++aRevertCount;
|
||||
}
|
||||
}
|
||||
@@ -3021,7 +3028,11 @@ nsRuleNode::ResolveVariableReferences(const nsStyleStructID aSID,
|
||||
"Token stream should have a defined level");
|
||||
|
||||
AutoRestore<SheetType> saveLevel(aRuleData->mLevel);
|
||||
AutoRestore<bool> saveImportant(aRuleData->mIsImportantRule);
|
||||
AutoRestore<int32_t> saveLayerIndex(aRuleData->mLayerIndex);
|
||||
aRuleData->mLevel = tokenStream->mLevel;
|
||||
aRuleData->mIsImportantRule = tokenStream->mIsImportant;
|
||||
aRuleData->mLayerIndex = tokenStream->mLayerIndex;
|
||||
|
||||
// Note that ParsePropertyWithVariableReferences relies on the fact
|
||||
// that the nsCSSValue in aRuleData for the property we are re-parsing
|
||||
@@ -3045,6 +3056,114 @@ nsRuleNode::ResolveVariableReferences(const nsStyleStructID aSID,
|
||||
return anyTokenStreams;
|
||||
}
|
||||
|
||||
static nsCSSPropertyID
|
||||
PropertyAtIndexInStruct(nsStyleStructID aSID, size_t aIndex)
|
||||
{
|
||||
for (int32_t prop = 0; prop < eCSSProperty_COUNT_no_shorthands; prop++) {
|
||||
nsCSSPropertyID property = nsCSSPropertyID(prop);
|
||||
if (nsCSSProps::kSIDTable[property] == aSID &&
|
||||
nsCSSProps::PropertyIndexInStruct(property) == aIndex) {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT_UNREACHABLE("no property for style struct index");
|
||||
return eCSSProperty_UNKNOWN;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
nsRuleNode::ResolveRevertLayerValues(const nsStyleStructID aSID,
|
||||
nsRuleData* aRuleData,
|
||||
nsStyleContext* aContext,
|
||||
nsRuleNode* aStartRuleNode)
|
||||
{
|
||||
MOZ_ASSERT(aSID != eStyleStruct_Variables);
|
||||
|
||||
size_t nprops = nsCSSProps::PropertyCountInStruct(aSID);
|
||||
bool resolvedAny = false;
|
||||
|
||||
for (size_t pass = 0; pass <= nprops; pass++) {
|
||||
bool resolvedThisPass = false;
|
||||
bool needsVariableResolution = false;
|
||||
|
||||
for (size_t index = 0; index < nprops; index++) {
|
||||
nsCSSValue* target = aRuleData->mValueStorage + index;
|
||||
if (target->GetUnit() != eCSSUnit_RevertLayer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nsCSSPropertyID property = PropertyAtIndexInStruct(aSID, index);
|
||||
if (property == eCSSProperty_UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AutoTArray<nsCSSValue, 64> tempValues;
|
||||
tempValues.SetLength(nprops);
|
||||
for (size_t i = 0; i < nprops; i++) {
|
||||
tempValues[i].SetDummyValue();
|
||||
}
|
||||
tempValues[index] = *target;
|
||||
|
||||
nsRuleData tempRuleData(nsCachedStyleData::GetBitForSID(aSID),
|
||||
tempValues.Elements(),
|
||||
aStartRuleNode->mPresContext, aContext);
|
||||
tempRuleData.mValueOffsets[aSID] = 0;
|
||||
|
||||
uint32_t bit = nsCachedStyleData::GetBitForSID(aSID);
|
||||
for (nsRuleNode* ruleNode = aStartRuleNode; ruleNode;
|
||||
ruleNode = ruleNode->GetParent()) {
|
||||
if (ruleNode->mNoneBits & bit) {
|
||||
break;
|
||||
}
|
||||
|
||||
nsIStyleRule* rule = ruleNode->mRule;
|
||||
if (rule) {
|
||||
tempRuleData.mLevel = ruleNode->GetLevel();
|
||||
tempRuleData.mIsImportantRule = ruleNode->IsImportantRule();
|
||||
tempRuleData.mLayerIndex = ruleNode->GetLayerIndex();
|
||||
rule->MapRuleInfoInto(&tempRuleData);
|
||||
}
|
||||
|
||||
if (tempValues[index].GetUnit() != eCSSUnit_RevertLayer) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tempValues[index].GetUnit() == eCSSUnit_RevertLayer) {
|
||||
tempValues[index].SetRevertValue(
|
||||
target->GetRevertLayerOriginValue());
|
||||
} else if (tempValues[index].GetUnit() == eCSSUnit_TokenStream) {
|
||||
needsVariableResolution = true;
|
||||
}
|
||||
|
||||
*target = tempValues[index];
|
||||
aRuleData->mConditions.SetUncacheable();
|
||||
resolvedThisPass = true;
|
||||
resolvedAny = true;
|
||||
}
|
||||
|
||||
if (!resolvedThisPass) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (needsVariableResolution &&
|
||||
ResolveVariableReferences(aSID, aRuleData, aContext)) {
|
||||
resolvedAny = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t index = 0; index < nprops; index++) {
|
||||
nsCSSValue* target = aRuleData->mValueStorage + index;
|
||||
if (target->GetUnit() == eCSSUnit_RevertLayer) {
|
||||
target->SetRevertValue(target->GetRevertLayerOriginValue());
|
||||
aRuleData->mConditions.SetUncacheable();
|
||||
resolvedAny = true;
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedAny;
|
||||
}
|
||||
|
||||
const void*
|
||||
nsRuleNode::WalkRuleTree(const nsStyleStructID aSID,
|
||||
nsStyleContext* aContext)
|
||||
@@ -3118,6 +3237,7 @@ nsRuleNode::WalkRuleTree(const nsStyleStructID aSID,
|
||||
if (rule) {
|
||||
ruleData.mLevel = ruleNode->GetLevel();
|
||||
ruleData.mIsImportantRule = ruleNode->IsImportantRule();
|
||||
ruleData.mLayerIndex = ruleNode->GetLayerIndex();
|
||||
rule->MapRuleInfoInto(&ruleData);
|
||||
}
|
||||
|
||||
@@ -3154,6 +3274,11 @@ nsRuleNode::WalkRuleTree(const nsStyleStructID aSID,
|
||||
recomputeDetail = ResolveVariableReferences(aSID, &ruleData, aContext);
|
||||
}
|
||||
|
||||
if (aSID != eStyleStruct_Variables &&
|
||||
ResolveRevertLayerValues(aSID, &ruleData, aContext, this)) {
|
||||
recomputeDetail = true;
|
||||
}
|
||||
|
||||
// If needed, unset the properties that don't have a flag that allows
|
||||
// them to be set for this style context. (For example, only some
|
||||
// properties apply to :first-line and :first-letter.)
|
||||
@@ -4889,6 +5014,7 @@ nsRuleNode::SetGenericFont(nsPresContext* aPresContext,
|
||||
if (rule) {
|
||||
ruleData.mLevel = ruleNode->GetLevel();
|
||||
ruleData.mIsImportantRule = ruleNode->IsImportantRule();
|
||||
ruleData.mLayerIndex = ruleNode->GetLayerIndex();
|
||||
rule->MapRuleInfoInto(&ruleData);
|
||||
}
|
||||
}
|
||||
@@ -4902,6 +5028,8 @@ nsRuleNode::SetGenericFont(nsPresContext* aPresContext,
|
||||
}
|
||||
|
||||
ResolveVariableReferences(eStyleStruct_Font, &ruleData, aContext);
|
||||
ResolveRevertLayerValues(eStyleStruct_Font, &ruleData, aContext,
|
||||
context->RuleNode());
|
||||
|
||||
RuleNodeCacheConditions dummy;
|
||||
nsRuleNode::SetFont(aPresContext, context,
|
||||
@@ -11081,6 +11209,8 @@ nsRuleNode::ComputeVariablesData(void* aStartStruct,
|
||||
CSSVariableResolver resolver(&variables->mVariables);
|
||||
resolver.Resolve(&parentVariables->mVariables,
|
||||
aRuleData->mVariables);
|
||||
aRuleData->mVariables->ResolveRevertLayerFallbacks(&parentVariables->mVariables,
|
||||
&variables->mVariables);
|
||||
conditions.SetUncacheable();
|
||||
|
||||
COMPUTE_END_INHERITED(Variables, variables)
|
||||
@@ -11473,6 +11603,7 @@ nsRuleNode::HasAuthorSpecifiedRules(nsStyleContext* aStyleContext,
|
||||
if (rule) {
|
||||
ruleData.mLevel = ruleNode->GetLevel();
|
||||
ruleData.mIsImportantRule = ruleNode->IsImportantRule();
|
||||
ruleData.mLayerIndex = ruleNode->GetLayerIndex();
|
||||
|
||||
rule->MapRuleInfoInto(&ruleData);
|
||||
|
||||
@@ -11485,11 +11616,13 @@ nsRuleNode::HasAuthorSpecifiedRules(nsStyleContext* aStyleContext,
|
||||
nsCSSUnit unit = values[i]->GetUnit();
|
||||
if (unit != eCSSUnit_Null &&
|
||||
unit != eCSSUnit_Revert &&
|
||||
unit != eCSSUnit_RevertLayer &&
|
||||
unit != eCSSUnit_Dummy &&
|
||||
unit != eCSSUnit_DummyInherit) {
|
||||
if (unit == eCSSUnit_Inherit ||
|
||||
(i >= inheritedOffset && unit == eCSSUnit_Unset) ||
|
||||
(i >= inheritedOffset && unit == eCSSUnit_Revert)) {
|
||||
(i >= inheritedOffset && unit == eCSSUnit_Revert) ||
|
||||
(i >= inheritedOffset && unit == eCSSUnit_RevertLayer)) {
|
||||
haveExplicitUAInherit = true;
|
||||
values[i]->SetDummyInheritValue();
|
||||
} else {
|
||||
@@ -11504,7 +11637,8 @@ nsRuleNode::HasAuthorSpecifiedRules(nsStyleContext* aStyleContext,
|
||||
if (values[i]->GetUnit() != eCSSUnit_Null &&
|
||||
values[i]->GetUnit() != eCSSUnit_Dummy && // see above
|
||||
values[i]->GetUnit() != eCSSUnit_DummyInherit &&
|
||||
values[i]->GetUnit() != eCSSUnit_Revert) {
|
||||
values[i]->GetUnit() != eCSSUnit_Revert &&
|
||||
values[i]->GetUnit() != eCSSUnit_RevertLayer) {
|
||||
// If author colors are not allowed, only claim to have
|
||||
// author-specified rules if we're looking at a non-color
|
||||
// property or if we're looking at the background color and it's
|
||||
@@ -11596,6 +11730,7 @@ nsRuleNode::ComputePropertiesOverridingAnimation(
|
||||
if (rule) {
|
||||
ruleData.mLevel = ruleNode->GetLevel();
|
||||
ruleData.mIsImportantRule = ruleNode->IsImportantRule();
|
||||
ruleData.mLayerIndex = ruleNode->GetLayerIndex();
|
||||
|
||||
// Transitions are the only non-!important level overriding
|
||||
// animations in the cascade ordering. They also don't actually
|
||||
|
||||
@@ -441,16 +441,20 @@ private:
|
||||
struct Key {
|
||||
nsIStyleRule* mRule;
|
||||
mozilla::SheetType mLevel;
|
||||
int32_t mLayerIndex;
|
||||
bool mIsImportantRule;
|
||||
|
||||
Key(nsIStyleRule* aRule, mozilla::SheetType aLevel, bool aIsImportantRule)
|
||||
: mRule(aRule), mLevel(aLevel), mIsImportantRule(aIsImportantRule)
|
||||
Key(nsIStyleRule* aRule, mozilla::SheetType aLevel,
|
||||
bool aIsImportantRule, int32_t aLayerIndex)
|
||||
: mRule(aRule), mLevel(aLevel), mLayerIndex(aLayerIndex),
|
||||
mIsImportantRule(aIsImportantRule)
|
||||
{}
|
||||
|
||||
bool operator==(const Key& aOther) const
|
||||
{
|
||||
return mRule == aOther.mRule &&
|
||||
mLevel == aOther.mLevel &&
|
||||
mLayerIndex == aOther.mLayerIndex &&
|
||||
mIsImportantRule == aOther.mIsImportantRule;
|
||||
}
|
||||
|
||||
@@ -469,7 +473,7 @@ private:
|
||||
static const PLDHashTableOps ChildrenHashOps;
|
||||
|
||||
Key GetKey() const {
|
||||
return Key(mRule, GetLevel(), IsImportantRule());
|
||||
return Key(mRule, GetLevel(), IsImportantRule(), GetLayerIndex());
|
||||
}
|
||||
|
||||
// The children of this node are stored in either a hashtable or list
|
||||
@@ -528,6 +532,8 @@ private:
|
||||
uint32_t mDependentBits; // Used to cache the fact that we can look up
|
||||
// cached data under a parent rule.
|
||||
|
||||
int32_t mLayerIndex;
|
||||
|
||||
uint32_t mNoneBits; // Used to cache the fact that the branch to this
|
||||
// node specifies no non-inherited data for a
|
||||
// given struct type. (This usually implies that
|
||||
@@ -585,6 +591,10 @@ protected:
|
||||
static bool ResolveVariableReferences(const nsStyleStructID aSID,
|
||||
nsRuleData* aRuleData,
|
||||
nsStyleContext* aContext);
|
||||
static bool ResolveRevertLayerValues(const nsStyleStructID aSID,
|
||||
nsRuleData* aRuleData,
|
||||
nsStyleContext* aContext,
|
||||
nsRuleNode* aStartRuleNode);
|
||||
|
||||
const void*
|
||||
WalkRuleTree(const nsStyleStructID aSID, nsStyleContext* aContext);
|
||||
@@ -792,7 +802,8 @@ protected:
|
||||
|
||||
private:
|
||||
nsRuleNode(nsPresContext* aPresContext, nsRuleNode* aParent,
|
||||
nsIStyleRule* aRule, mozilla::SheetType aLevel, bool aIsImportant);
|
||||
nsIStyleRule* aRule, mozilla::SheetType aLevel,
|
||||
bool aIsImportant, int32_t aLayerIndex);
|
||||
~nsRuleNode();
|
||||
|
||||
public:
|
||||
@@ -805,7 +816,7 @@ public:
|
||||
|
||||
// Transition never returns null; on out of memory it'll just return |this|.
|
||||
nsRuleNode* Transition(nsIStyleRule* aRule, mozilla::SheetType aLevel,
|
||||
bool aIsImportantRule);
|
||||
bool aIsImportantRule, int32_t aLayerIndex);
|
||||
nsRuleNode* GetParent() const { return mParent; }
|
||||
bool IsRoot() const { return mParent == nullptr; }
|
||||
|
||||
@@ -825,6 +836,10 @@ public:
|
||||
NS_ASSERTION(!IsRoot(), "can't call on root");
|
||||
return (mDependentBits & NS_RULE_NODE_IS_IMPORTANT) != 0;
|
||||
}
|
||||
int32_t GetLayerIndex() const {
|
||||
NS_ASSERTION(!IsRoot(), "can't call on root");
|
||||
return mLayerIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has this rule node at some time in its lifetime been the mRuleNode
|
||||
|
||||
@@ -28,7 +28,8 @@ public:
|
||||
|
||||
protected:
|
||||
void DoForward(nsIStyleRule* aRule) {
|
||||
mCurrent = mCurrent->Transition(aRule, mLevel, mImportance);
|
||||
mCurrent = mCurrent->Transition(aRule, mLevel, mImportance,
|
||||
mLayerIndex);
|
||||
NS_POSTCONDITION(mCurrent, "Transition messed up");
|
||||
}
|
||||
|
||||
@@ -61,10 +62,16 @@ public:
|
||||
"important rules");
|
||||
mLevel = aLevel;
|
||||
mImportance = aImportance;
|
||||
mLayerIndex = 0;
|
||||
mCheckForImportantRules = aCheckForImportantRules;
|
||||
}
|
||||
mozilla::SheetType GetLevel() const { return mLevel; }
|
||||
bool GetImportance() const { return mImportance; }
|
||||
void SetLayerIndex(int32_t aLayerIndex) {
|
||||
MOZ_ASSERT(aLayerIndex >= 0, "negative cascade layer index");
|
||||
mLayerIndex = aLayerIndex;
|
||||
}
|
||||
int32_t GetLayerIndex() const { return mLayerIndex; }
|
||||
bool GetCheckForImportantRules() const { return mCheckForImportantRules; }
|
||||
|
||||
bool AuthorStyleDisabled() const { return mAuthorStyleDisabled; }
|
||||
@@ -87,6 +94,7 @@ private:
|
||||
nsRuleNode* mCurrent; // Our current position. Never null.
|
||||
nsRuleNode* mRoot; // The root of the tree we're walking.
|
||||
mozilla::SheetType mLevel;
|
||||
int32_t mLayerIndex;
|
||||
bool mImportance;
|
||||
bool mCheckForImportantRules; // If true, check for important rules as
|
||||
// we walk and set to false if we find
|
||||
@@ -97,6 +105,10 @@ public:
|
||||
nsRuleWalker(nsRuleNode* aRoot, bool aAuthorStyleDisabled)
|
||||
: mCurrent(aRoot)
|
||||
, mRoot(aRoot)
|
||||
, mLevel(mozilla::SheetType::Unknown)
|
||||
, mLayerIndex(0)
|
||||
, mImportance(false)
|
||||
, mCheckForImportantRules(false)
|
||||
, mAuthorStyleDisabled(aAuthorStyleDisabled)
|
||||
{
|
||||
NS_ASSERTION(mCurrent, "Caller screwed up and gave us null node");
|
||||
|
||||
@@ -847,14 +847,14 @@ ReplaceAnimationRule(nsRuleNode *aOldRuleNode,
|
||||
"wrong level");
|
||||
|
||||
if (aNewAnimRule) {
|
||||
n = n->Transition(aNewAnimRule, SheetType::Animation, false);
|
||||
n = n->Transition(aNewAnimRule, SheetType::Animation, false, 0);
|
||||
n->SetIsAnimationRule();
|
||||
}
|
||||
|
||||
for (uint32_t i = moreSpecificNodes.Length(); i-- != 0; ) {
|
||||
nsRuleNode *oldNode = moreSpecificNodes[i];
|
||||
n = n->Transition(oldNode->GetRule(), oldNode->GetLevel(),
|
||||
oldNode->IsImportantRule());
|
||||
oldNode->IsImportantRule(), oldNode->GetLayerIndex());
|
||||
}
|
||||
|
||||
return n;
|
||||
@@ -1124,6 +1124,7 @@ FileRulesFromAllChildProcessors(
|
||||
if (i > 0) {
|
||||
aRuleWalker->SetLevel(aLevel, false, true);
|
||||
}
|
||||
aRuleWalker->SetLayerIndex(i);
|
||||
nsCOMPtr<nsIStyleRuleProcessor> processor = (*processors)[i];
|
||||
(*aCollectorFunc)(processor, aData);
|
||||
TrackRuleNodeForCurrentOrigin(aRuleWalker,
|
||||
@@ -1307,6 +1308,7 @@ nsStyleSet::FileRules(nsIStyleRuleProcessor::EnumFunc aCollectorFunc,
|
||||
if (haveAnyImportantDocRules) {
|
||||
for (uint32_t i = lastDocRNs.Length(); i-- != 0; ) {
|
||||
aRuleWalker->SetLevel(SheetType::Doc, true, false);
|
||||
aRuleWalker->SetLayerIndex(i);
|
||||
nsRuleNode* startRN = lastDocRNs[i];
|
||||
nsRuleNode* endRN = i == 0 ? lastSVGAttrAnimationRN : lastDocRNs[i - 1];
|
||||
if (haveImportantDocRules[i]) {
|
||||
@@ -1352,6 +1354,7 @@ nsStyleSet::FileRules(nsIStyleRuleProcessor::EnumFunc aCollectorFunc,
|
||||
if (haveAnyImportantUserRules) {
|
||||
for (uint32_t i = lastUserRNs.Length(); i-- != 0;) {
|
||||
aRuleWalker->SetLevel(SheetType::User, true, false);
|
||||
aRuleWalker->SetLayerIndex(i);
|
||||
nsRuleNode* startRN = lastUserRNs[i];
|
||||
nsRuleNode* endRN = i == 0 ? lastAgentRN : lastUserRNs[i - 1];
|
||||
if (haveImportantUserRules[i]) {
|
||||
@@ -1373,6 +1376,7 @@ nsStyleSet::FileRules(nsIStyleRuleProcessor::EnumFunc aCollectorFunc,
|
||||
if (haveAnyImportantAgentRules) {
|
||||
for (uint32_t i = lastAgentRNs.Length(); i-- != 0;) {
|
||||
aRuleWalker->SetLevel(SheetType::Agent, true, false);
|
||||
aRuleWalker->SetLayerIndex(i);
|
||||
nsRuleNode* startRN = lastAgentRNs[i];
|
||||
nsRuleNode* endRN = i == 0 ? lastRestrictionRN : lastAgentRNs[i - 1];
|
||||
if (haveImportantAgentRules[i]) {
|
||||
@@ -1585,6 +1589,7 @@ nsStyleSet::ResolveStyleByAddingRules(nsStyleContext* aBaseContext,
|
||||
struct RuleNodeInfo {
|
||||
nsIStyleRule* mRule;
|
||||
SheetType mLevel;
|
||||
int32_t mLayerIndex;
|
||||
bool mIsImportant;
|
||||
bool mIsAnimationRule;
|
||||
};
|
||||
@@ -1657,6 +1662,7 @@ nsStyleSet::RuleNodeWithReplacement(Element* aElement,
|
||||
RuleNodeInfo* curRule = rules.AppendElement();
|
||||
curRule->mRule = ruleNode->GetRule();
|
||||
curRule->mLevel = ruleNode->GetLevel();
|
||||
curRule->mLayerIndex = ruleNode->GetLayerIndex();
|
||||
curRule->mIsImportant = ruleNode->IsImportantRule();
|
||||
curRule->mIsAnimationRule = ruleNode->IsAnimationRule();
|
||||
}
|
||||
@@ -1771,6 +1777,7 @@ nsStyleSet::RuleNodeWithReplacement(Element* aElement,
|
||||
}
|
||||
|
||||
if (!doReplace) {
|
||||
ruleWalker.SetLayerIndex(ruleInfo.mLayerIndex);
|
||||
ruleWalker.ForwardOnPossiblyCSSRule(ruleInfo.mRule);
|
||||
if (ruleInfo.mIsAnimationRule) {
|
||||
ruleWalker.CurrentNode()->SetIsAnimationRule();
|
||||
|
||||
@@ -245,6 +245,7 @@ support-files =
|
||||
support-files = redundant_font_download.sjs
|
||||
[test_rem_unit.html]
|
||||
[test_restyles_in_smil_animation.html]
|
||||
[test_revert_layer.html]
|
||||
[test_root_node_display.html]
|
||||
[test_rule_insertion.html]
|
||||
[test_rule_serialization.html]
|
||||
|
||||
@@ -37,6 +37,7 @@ function runTest()
|
||||
"(unknown:) or (color: green)",
|
||||
"(unknown) or (color: green)",
|
||||
"(font: 16px serif)",
|
||||
"(all: revert-layer)",
|
||||
"(color:) or (color: green)",
|
||||
"not (@page)",
|
||||
"not ({ something @with [ balanced ] brackets })",
|
||||
@@ -69,6 +70,7 @@ function runTest()
|
||||
"not ({ something @with (unbalanced brackets })",
|
||||
"(color: green) or an-extension(that is [unbalanced)",
|
||||
"not(unknown: unknown)",
|
||||
"not (all: revert-layer)",
|
||||
"(color: green) or(color: blue)",
|
||||
"(color: green;)",
|
||||
"(font-family: 'Helvetica\n",
|
||||
@@ -87,7 +89,9 @@ function runTest()
|
||||
["font", "16px serif"],
|
||||
["font", "16px /* comment */ serif"],
|
||||
["font", "16px\nserif"],
|
||||
["color", "\\0067reen"]
|
||||
["color", "\\0067reen"],
|
||||
["all", "revert-layer"],
|
||||
["color", "revert-layer"]
|
||||
];
|
||||
|
||||
var failingDeclarations = [
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test CSS revert-layer cascade behavior</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="display"></div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let fixtureCount = 0;
|
||||
|
||||
function runFixture(css, callback) {
|
||||
const id = "revert-layer-fixture-" + (++fixtureCount);
|
||||
const style = document.createElement("style");
|
||||
style.textContent = css.replace(/__ID__/g, id);
|
||||
document.head.appendChild(style);
|
||||
|
||||
const element = document.createElement("div");
|
||||
element.id = id;
|
||||
document.getElementById("display").appendChild(element);
|
||||
|
||||
try {
|
||||
callback(element, getComputedStyle(element));
|
||||
} finally {
|
||||
element.remove();
|
||||
style.remove();
|
||||
}
|
||||
}
|
||||
|
||||
ok(CSS.supports("color", "revert-layer"), "revert-layer parses as a CSS-wide keyword");
|
||||
ok(CSS.supports("all", "revert-layer"), "all accepts revert-layer");
|
||||
ok(!CSS.supports("not (all: revert-layer)"), "supports negation sees revert-layer support");
|
||||
|
||||
runFixture(`
|
||||
@layer { #__ID__ { color: rgb(0, 128, 0); } }
|
||||
@layer { #__ID__ { color: red; color: revert-layer; } }
|
||||
`, function(element, cs) {
|
||||
is(cs.color, "rgb(0, 128, 0)", "revert-layer rolls back to a previous explicit layer");
|
||||
});
|
||||
|
||||
runFixture(`
|
||||
@layer { #__ID__ { color: rgb(0, 128, 0); } }
|
||||
#__ID__ { color: red; color: revert-layer; }
|
||||
`, function(element, cs) {
|
||||
is(cs.color, "rgb(0, 128, 0)", "revert-layer rolls back from the implicit outer layer");
|
||||
});
|
||||
|
||||
runFixture(`
|
||||
@layer {
|
||||
#__ID__ { color: rgb(0, 128, 0); width: 5px; height: 6px; }
|
||||
}
|
||||
@layer {
|
||||
#__ID__ { color: red; width: 50px; height: 60px; }
|
||||
#__ID__ { all: revert-layer; }
|
||||
}
|
||||
`, function(element, cs) {
|
||||
is(cs.color, "rgb(0, 128, 0)", "all: revert-layer rolls color back");
|
||||
is(cs.width, "5px", "all: revert-layer rolls width back");
|
||||
is(cs.height, "6px", "all: revert-layer rolls height back");
|
||||
});
|
||||
|
||||
runFixture(`
|
||||
@layer { #__ID__ { height: 7px; } }
|
||||
@layer { #__ID__ { block-size: revert-layer; } }
|
||||
`, function(element, cs) {
|
||||
is(cs.height, "7px", "revert-layer on a logical alias rolls back the physical property");
|
||||
});
|
||||
|
||||
runFixture(`
|
||||
@layer { #__ID__ { color: rgb(0, 128, 0) !important; } }
|
||||
@layer { #__ID__ { color: red !important; color: revert-layer !important; } }
|
||||
`, function(element, cs) {
|
||||
is(cs.color, "rgb(0, 128, 0)", "important revert-layer uses reversed layer order");
|
||||
});
|
||||
|
||||
runFixture(`
|
||||
@layer { #__ID__ { color: rgb(0, 128, 0); } }
|
||||
`, function(element, cs) {
|
||||
element.style.color = "revert-layer";
|
||||
is(cs.color, "rgb(0, 128, 0)", "normal style attribute revert-layer rolls back to author rules");
|
||||
});
|
||||
|
||||
runFixture(`
|
||||
@layer { #__ID__ { color: rgb(0, 128, 0) !important; } }
|
||||
`, function(element, cs) {
|
||||
element.style.setProperty("color", "revert-layer", "important");
|
||||
is(cs.color, "rgb(0, 128, 0)",
|
||||
"important style attribute revert-layer does not skip author important rules");
|
||||
});
|
||||
|
||||
runFixture(`
|
||||
@layer {
|
||||
#__ID__ { --x:PASS; margin: 1px; padding-left: 1px; }
|
||||
}
|
||||
@layer {
|
||||
#__ID__ {
|
||||
--x: var(--unknown, revert-layer);
|
||||
margin: var(--unknown, revert-layer);
|
||||
padding-left: var(--unknown, revert-layer);
|
||||
}
|
||||
}
|
||||
`, function(element, cs) {
|
||||
is(cs.getPropertyValue("--x"), "PASS", "custom property var() fallback can resolve to revert-layer");
|
||||
is(cs.marginLeft, "1px", "shorthand var() fallback can resolve to revert-layer");
|
||||
is(cs.paddingLeft, "1px", "longhand var() fallback can resolve to revert-layer");
|
||||
});
|
||||
|
||||
runFixture(`
|
||||
@layer { #__ID__ { --x:PASS; } }
|
||||
@layer { #__ID__ { --x: var(--unknown-a, revert-layer); } }
|
||||
@layer { #__ID__ { --x: var(--unknown-b, revert-layer); } }
|
||||
`, function(element, cs) {
|
||||
is(cs.getPropertyValue("--x"), "PASS",
|
||||
"custom property fallback continues past lower declarations that also resolve to revert-layer");
|
||||
});
|
||||
|
||||
SimpleTest.finish();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -673,13 +673,6 @@ nsXULPopupManager::ShowMenu(nsIContent *aMenu,
|
||||
bool aSelectFirstItem,
|
||||
bool aAsynchronous)
|
||||
{
|
||||
// JIHAD popup instrumentation: the toolbarbutton type="menu" path (about:addons
|
||||
// utils menu) enters here; if this line never fires, the DOM activation upstream
|
||||
// (XUL button command/mousedown handling) is where the tap died.
|
||||
if (getenv("JIHAD_OFFSCREEN")) {
|
||||
fprintf(stderr, "[jihad-popup] ShowMenu menu=%p async=%d\n",
|
||||
(void*)aMenu, (int)aAsynchronous);
|
||||
}
|
||||
// generate any template content first. Otherwise, the menupopup may not
|
||||
// have been created yet.
|
||||
if (aMenu) {
|
||||
@@ -904,18 +897,6 @@ nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
|
||||
nsPopupType popupType = aPopupFrame->PopupType();
|
||||
bool ismenu = (popupType == ePopupTypeMenu);
|
||||
|
||||
// JIHAD popup instrumentation (2026-08-02): EVERY popup that actually opens funnels
|
||||
// through here. Silent until now — "menu never opened" vs "opened but painted
|
||||
// nowhere" were indistinguishable in the headless daemon.
|
||||
if (getenv("JIHAD_OFFSCREEN")) {
|
||||
nsRect r = aPopupFrame->GetRect();
|
||||
fprintf(stderr, "[jihad-popup] ShowPopupCallback type=%d ctx=%d frame=%p rect=%d,%d %dx%d hasView=%d hasWidget=%d\n",
|
||||
(int)popupType, (int)aIsContextMenu, (void*)aPopupFrame,
|
||||
r.x, r.y, r.width, r.height,
|
||||
(int)aPopupFrame->HasView(),
|
||||
(int)(aPopupFrame->GetView() && aPopupFrame->GetView()->HasWidget()));
|
||||
}
|
||||
|
||||
nsMenuChainItem* item =
|
||||
new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType);
|
||||
if (!item)
|
||||
|
||||
@@ -40,9 +40,6 @@
|
||||
X(pa_operation_get_state) \
|
||||
X(pa_operation_unref) \
|
||||
X(pa_proplist_gets) \
|
||||
X(pa_proplist_new) \
|
||||
X(pa_proplist_sets) \
|
||||
X(pa_proplist_free) \
|
||||
X(pa_rtclock_now) \
|
||||
X(pa_stream_begin_write) \
|
||||
X(pa_stream_cancel_write) \
|
||||
@@ -56,7 +53,6 @@
|
||||
X(pa_stream_get_state) \
|
||||
X(pa_stream_get_time) \
|
||||
X(pa_stream_new) \
|
||||
X(pa_stream_new_with_proplist) \
|
||||
X(pa_stream_set_state_callback) \
|
||||
X(pa_stream_set_write_callback) \
|
||||
X(pa_stream_unref) \
|
||||
@@ -118,20 +114,8 @@ struct cubeb_stream {
|
||||
int shutdown;
|
||||
float volume;
|
||||
cubeb_state state;
|
||||
int jihad_active; /* Jihad: counted in jihad_pulse_active_count while this stream is playing */
|
||||
};
|
||||
|
||||
/* Jihad/webOS: number of currently-PLAYING (uncorked) output streams. The daemon
|
||||
(render/goanna/BrowserPageGoanna) polls jihad_pulse_active_streams() and, on 0<->N transitions,
|
||||
runs $JIHAD_STATE_DIR/audio-{on,off}.sh to hold the audiod media scenario + force the speaker
|
||||
mixer, so engine media audio respects the system volume and can reach the built-in speaker (the
|
||||
media.role set below gets the stream onto the media policy path in the first place). Weak-linked
|
||||
from the daemon; on desktop nothing reads it and it is inert. */
|
||||
static int jihad_pulse_active_count = 0;
|
||||
/* visibility("default"): libxul is built -fvisibility=hidden, so export it explicitly (same as the
|
||||
MOZ_EXPORT on jihad_plugin_frame_seq) or the daemon's weak extern resolves to null. */
|
||||
__attribute__((visibility("default"))) int jihad_pulse_active_streams(void) { return jihad_pulse_active_count; }
|
||||
|
||||
static const float PULSE_NO_GAIN = -1.0;
|
||||
|
||||
enum cork_state {
|
||||
@@ -696,41 +680,7 @@ create_pa_stream(cubeb_stream * stm,
|
||||
ss.rate = stream_params->rate;
|
||||
ss.channels = stream_params->channels;
|
||||
|
||||
/* Jihad/webOS: module-palm-policy powers the hardware codec route (and the built-in speaker)
|
||||
only for a stream it CLASSIFIES as media, via a pa_proplist property our stream otherwise
|
||||
lacks (see impl-audio-backend.md). The exact property/value is not yet pinned, so read
|
||||
"key=value" lines from a WRITABLE file, $JIHAD_STATE_DIR/pulse.props, and set each on the
|
||||
stream proplist — this lets the routing property be tuned on-device without a libxul rebuild.
|
||||
With no file present, default to media.role=music. On desktop JIHAD_STATE_DIR is unset, no
|
||||
file is read, and the default is a harmless standard PulseAudio role. */
|
||||
pa_proplist * pl = WRAP(pa_proplist_new)();
|
||||
int have_props = 0;
|
||||
char const * sd = getenv("JIHAD_STATE_DIR");
|
||||
if (sd && *sd) {
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/pulse.props", sd);
|
||||
FILE * pf = fopen(path, "r");
|
||||
if (pf) {
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), pf)) {
|
||||
char * nl = strchr(line, '\n'); if (nl) { *nl = '\0'; }
|
||||
if (line[0] == '\0' || line[0] == '#') { continue; }
|
||||
char * eq = strchr(line, '=');
|
||||
if (eq) { *eq = '\0'; WRAP(pa_proplist_sets)(pl, line, eq + 1); have_props = 1; }
|
||||
}
|
||||
fclose(pf);
|
||||
}
|
||||
}
|
||||
if (!have_props) {
|
||||
/* Default: classify as media so the stream takes the webOS media-policy path (the pmedia
|
||||
virtual sink) instead of sitting anonymously on pcm_output at a fixed 100%. That classification
|
||||
is what lets the daemon's audio-on.sh hold the audiod media scenario (system-volume-respecting)
|
||||
and force the speaker route on top. See impl-audio-backend.md. Override/disable via the props
|
||||
file. */
|
||||
WRAP(pa_proplist_sets)(pl, "media.role", "music");
|
||||
}
|
||||
*pa_stm = WRAP(pa_stream_new_with_proplist)(stm->context->context, stream_name, &ss, NULL, pl);
|
||||
WRAP(pa_proplist_free)(pl);
|
||||
*pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
|
||||
return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK;
|
||||
}
|
||||
|
||||
@@ -802,16 +752,8 @@ pulse_stream_init(cubeb * context,
|
||||
WRAP(pa_stream_set_write_callback)(stm->output_stream, stream_write_callback, stm);
|
||||
|
||||
battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec);
|
||||
/* Jihad/webOS: module-palm-policy powers the hardware codec route only for a stream created on
|
||||
a named virtual sink (pmedia); a NULL/default sink lands on pcm_output and is muted to 0%.
|
||||
When no explicit device was requested, honour JIHAD_PULSE_SINK so the device build can
|
||||
target "pmedia". No effect on desktop (env unset -> NULL -> default sink, upstream behaviour). */
|
||||
char const * jihad_out_sink = (char const *) output_device;
|
||||
if (!jihad_out_sink) {
|
||||
jihad_out_sink = getenv("JIHAD_PULSE_SINK");
|
||||
}
|
||||
WRAP(pa_stream_connect_playback)(stm->output_stream,
|
||||
jihad_out_sink,
|
||||
output_device,
|
||||
&battr,
|
||||
PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
|
||||
PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY,
|
||||
@@ -879,7 +821,6 @@ static void
|
||||
pulse_stream_destroy(cubeb_stream * stm)
|
||||
{
|
||||
stream_cork(stm, CORK);
|
||||
if (stm->jihad_active) { stm->jihad_active = 0; if (jihad_pulse_active_count > 0) jihad_pulse_active_count--; }
|
||||
|
||||
WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
|
||||
if (stm->output_stream) {
|
||||
@@ -923,7 +864,6 @@ pulse_stream_start(cubeb_stream * stm)
|
||||
{
|
||||
stm->shutdown = 0;
|
||||
stream_cork(stm, UNCORK | NOTIFY);
|
||||
if (stm->output_stream && !stm->jihad_active) { stm->jihad_active = 1; jihad_pulse_active_count++; }
|
||||
|
||||
if (stm->output_stream && !stm->input_stream) {
|
||||
/* On output only case need to manually call user cb once in order to make
|
||||
@@ -950,7 +890,6 @@ pulse_stream_stop(cubeb_stream * stm)
|
||||
WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
|
||||
|
||||
stream_cork(stm, CORK | NOTIFY);
|
||||
if (stm->jihad_active) { stm->jihad_active = 0; if (jihad_pulse_active_count > 0) jihad_pulse_active_count--; }
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,13 +56,13 @@ template<typename T, typename Storage>
|
||||
struct EnumTypeFitsWithin
|
||||
: public detail::EnumFitsWithinHelper<
|
||||
sizeof(T),
|
||||
std::is_signed<typename std::underlying_type<T>::type>::value,
|
||||
std::is_signed_v<typename std::underlying_type_t<T>>,
|
||||
sizeof(Storage),
|
||||
std::is_signed<Storage>::value
|
||||
std::is_signed_v<Storage>
|
||||
>
|
||||
{
|
||||
static_assert(std::is_enum<T>::value, "must provide an enum type");
|
||||
static_assert(std::is_integral<Storage>::value, "must provide an integral type");
|
||||
static_assert(std::is_enum_v<T>, "must provide an enum type");
|
||||
static_assert(std::is_integral_v<Storage>, "must provide an integral type");
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
@@ -31,7 +31,7 @@ template<typename EnumTypeT>
|
||||
class EnumeratedIterator
|
||||
{
|
||||
public:
|
||||
typedef typename std::underlying_type<EnumTypeT>::type IntTypeT;
|
||||
typedef typename std::underlying_type_t<EnumTypeT> IntTypeT;
|
||||
|
||||
template<typename EnumType>
|
||||
explicit EnumeratedIterator(EnumType aCurrent)
|
||||
|
||||
+7
-7
@@ -115,7 +115,7 @@ public:
|
||||
*/
|
||||
template<typename U,
|
||||
typename =
|
||||
typename std::enable_if<std::is_convertible<U, T>::value>::type>
|
||||
typename std::enable_if_t<std::is_convertible_v<U, T>>>
|
||||
MOZ_IMPLICIT
|
||||
Maybe(const Maybe<U>& aOther)
|
||||
: mIsSome(false)
|
||||
@@ -139,7 +139,7 @@ public:
|
||||
*/
|
||||
template<typename U,
|
||||
typename =
|
||||
typename std::enable_if<std::is_convertible<U, T>::value>::type>
|
||||
typename std::enable_if_t<std::is_convertible_v<U, T>>>
|
||||
MOZ_IMPLICIT
|
||||
Maybe(Maybe<U>&& aOther)
|
||||
: mIsSome(false)
|
||||
@@ -168,7 +168,7 @@ public:
|
||||
|
||||
template<typename U,
|
||||
typename =
|
||||
typename std::enable_if<std::is_convertible<U, T>::value>::type>
|
||||
typename std::enable_if_t<std::is_convertible_v<U, T>>>
|
||||
Maybe& operator=(const Maybe<U>& aOther)
|
||||
{
|
||||
if (aOther.isSome()) {
|
||||
@@ -203,7 +203,7 @@ public:
|
||||
|
||||
template<typename U,
|
||||
typename =
|
||||
typename std::enable_if<std::is_convertible<U, T>::value>::type>
|
||||
typename std::enable_if_t<std::is_convertible_v<U, T>>>
|
||||
Maybe& operator=(Maybe<U>&& aOther)
|
||||
{
|
||||
if (aOther.isSome()) {
|
||||
@@ -539,8 +539,8 @@ class Maybe<T&> {
|
||||
* reference value, you need to use emplace() instead.
|
||||
*/
|
||||
template<typename T,
|
||||
typename U = typename std::remove_cv<
|
||||
typename std::remove_reference<T>::type>::type>
|
||||
typename U = typename std::remove_cv_t<
|
||||
typename std::remove_reference_t<T>>>
|
||||
Maybe<U>
|
||||
Some(T&& aValue)
|
||||
{
|
||||
@@ -574,7 +574,7 @@ ToMaybe(T* aPtr)
|
||||
template<typename T> bool
|
||||
operator==(const Maybe<T>& aLHS, const Maybe<T>& aRHS)
|
||||
{
|
||||
static_assert(!std::is_reference<T>::value,
|
||||
static_assert(!std::is_reference_v<T>,
|
||||
"operator== is not defined for Maybe<T&>, compare values or "
|
||||
"addresses explicitly instead");
|
||||
if (aLHS.isNothing() != aRHS.isNothing()) {
|
||||
|
||||
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user