mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to fx-team
This commit is contained in:
commit
648e688ead
@ -15,7 +15,7 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="779f05fead3d009f6e7fe713ad0fea16b6f2fb31"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="779f05fead3d009f6e7fe713ad0fea16b6f2fb31"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="779f05fead3d009f6e7fe713ad0fea16b6f2fb31"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0ad276e6d4ec40ae2ac214e42c6c81cfc8cd86c3"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="779f05fead3d009f6e7fe713ad0fea16b6f2fb31"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="779f05fead3d009f6e7fe713ad0fea16b6f2fb31"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="779f05fead3d009f6e7fe713ad0fea16b6f2fb31"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
</project>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="779f05fead3d009f6e7fe713ad0fea16b6f2fb31"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0ad276e6d4ec40ae2ac214e42c6c81cfc8cd86c3"/>
|
||||
|
@ -4,6 +4,6 @@
|
||||
"remote": "",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "ce1789cc91feafe53596cfd0360cd12f7cc69d3b",
|
||||
"revision": "cfca676ee9f96a5bb98e5ec2704381653d4e8ed8",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="779f05fead3d009f6e7fe713ad0fea16b6f2fb31"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="779f05fead3d009f6e7fe713ad0fea16b6f2fb31"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -2,9 +2,6 @@
|
||||
# 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/.
|
||||
|
||||
no_tooltool=1
|
||||
no_sccache=1
|
||||
|
||||
# This file is included at the top of all b2g mozconfigs
|
||||
|
||||
. "$topsrcdir/build/mozconfig.common"
|
||||
|
@ -17,7 +17,7 @@
|
||||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="779f05fead3d009f6e7fe713ad0fea16b6f2fb31"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0ad276e6d4ec40ae2ac214e42c6c81cfc8cd86c3"/>
|
||||
|
@ -6,6 +6,13 @@
|
||||
"filename": "setup.sh"
|
||||
},
|
||||
{
|
||||
"size": 80458572,
|
||||
"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
|
||||
"algorithm": "sha512",
|
||||
"filename": "gcc.tar.xz",
|
||||
"unpack": "True"
|
||||
},
|
||||
{
|
||||
"size": 168320,
|
||||
"digest": "c0f4a2da0b07ca6fc69290fbc5ed68f56c6b1ba4d593b220fd49b14ac4885e6ec949e695fd9a7ac464e0e86b652e99f6bd4af849fec072264b29a8f9686d2fc4",
|
||||
"algorithm": "sha512",
|
||||
|
@ -6,6 +6,13 @@
|
||||
"filename": "setup.sh"
|
||||
},
|
||||
{
|
||||
"size": 80458572,
|
||||
"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
|
||||
"algorithm": "sha512",
|
||||
"filename": "gcc.tar.xz",
|
||||
"unpack": "True"
|
||||
},
|
||||
{
|
||||
"size": 168320,
|
||||
"digest": "c0f4a2da0b07ca6fc69290fbc5ed68f56c6b1ba4d593b220fd49b14ac4885e6ec949e695fd9a7ac464e0e86b652e99f6bd4af849fec072264b29a8f9686d2fc4",
|
||||
"algorithm": "sha512",
|
||||
|
@ -17,7 +17,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="62c9bd93341fbfa8bf850c21e73465708f93b503"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="779f05fead3d009f6e7fe713ad0fea16b6f2fb31"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -237,12 +237,14 @@ const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
|
||||
const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1"
|
||||
|
||||
// clipboard helper
|
||||
try {
|
||||
const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
|
||||
}
|
||||
catch(e) {
|
||||
// do nothing, later code will handle the error
|
||||
function getClipboardHelper() {
|
||||
try {
|
||||
return Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
|
||||
} catch(e) {
|
||||
// do nothing, later code will handle the error
|
||||
}
|
||||
}
|
||||
const gClipboardHelper = getClipboardHelper();
|
||||
|
||||
// Interface for image loading content
|
||||
const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
|
||||
|
@ -21,8 +21,6 @@ __all__ = [
|
||||
"addCommonOptions",
|
||||
"dumpLeakLog",
|
||||
"processLeakLog",
|
||||
'KeyValueParseError',
|
||||
'parseKeyValue',
|
||||
'systemMemory',
|
||||
'environment',
|
||||
'dumpScreen',
|
||||
@ -371,27 +369,6 @@ def processLeakLog(leakLogFile, options):
|
||||
processSingleLeakFile(thisFile, processType, leakThreshold,
|
||||
processType in ignoreMissingLeaks)
|
||||
|
||||
class KeyValueParseError(Exception):
|
||||
"""error when parsing strings of serialized key-values"""
|
||||
def __init__(self, msg, errors=()):
|
||||
self.errors = errors
|
||||
Exception.__init__(self, msg)
|
||||
|
||||
def parseKeyValue(strings, separator='=', context='key, value: '):
|
||||
"""
|
||||
parse string-serialized key-value pairs in the form of
|
||||
`key = value`. Returns a list of 2-tuples.
|
||||
Note that whitespace is not stripped.
|
||||
"""
|
||||
|
||||
# syntax check
|
||||
missing = [string for string in strings if separator not in string]
|
||||
if missing:
|
||||
raise KeyValueParseError("Error: syntax error in %s" % (context,
|
||||
','.join(missing)),
|
||||
errors=missing)
|
||||
return [string.split(separator, 1) for string in strings]
|
||||
|
||||
def systemMemory():
|
||||
"""
|
||||
Returns total system memory in kilobytes.
|
||||
|
@ -76,6 +76,14 @@ def getUrlProperties(filename):
|
||||
properties = {prop: 'UNKNOWN' for prop, condition in property_conditions}
|
||||
return properties
|
||||
|
||||
def getPartialInfo(props):
|
||||
return [{
|
||||
"from_buildid": props.get("previous_buildid"),
|
||||
"size": props.get("partialMarSize"),
|
||||
"hash": props.get("partialMarHash"),
|
||||
"url": props.get("partialMarUrl"),
|
||||
}]
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = ArgumentParser(description='Generate mach_build_properties.json for automation builds.')
|
||||
parser.add_argument("--complete-mar-file", required=True,
|
||||
@ -90,9 +98,18 @@ if __name__ == '__main__':
|
||||
args = parser.parse_args()
|
||||
|
||||
json_data = getMarProperties(args.complete_mar_file)
|
||||
json_data.update(getUrlProperties(args.upload_output))
|
||||
if args.partial_mar_file:
|
||||
json_data.update(getMarProperties(args.partial_mar_file, partial=True))
|
||||
json_data.update(getUrlProperties(args.upload_output))
|
||||
|
||||
# Pull the previous buildid from the partial mar filename.
|
||||
res = re.match(r'.*\.([0-9]+)-[0-9]+.mar', args.partial_mar_file)
|
||||
if res:
|
||||
json_data['previous_buildid'] = res.group(1)
|
||||
|
||||
# Set partialInfo to be a collection of the partial mar properties
|
||||
# useful for balrog.
|
||||
json_data['partialInfo'] = getPartialInfo(json_data)
|
||||
|
||||
with open('mach_build_properties.json', 'w') as outfile:
|
||||
json.dump(json_data, outfile, indent=4)
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
import argparse
|
||||
import errno
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
@ -15,26 +16,24 @@ import pickle
|
||||
|
||||
import mozpack.path as mozpath
|
||||
|
||||
try:
|
||||
from multiprocessing import Pool, cpu_count
|
||||
except ImportError:
|
||||
import itertools
|
||||
|
||||
class Pool(object):
|
||||
def __init__(self, size):
|
||||
pass
|
||||
class Pool(object):
|
||||
def __new__(cls, size):
|
||||
try:
|
||||
import multiprocessing
|
||||
size = min(size, multiprocessing.cpu_count())
|
||||
return multiprocessing.Pool(size)
|
||||
except:
|
||||
return super(Pool, cls).__new__(cls)
|
||||
|
||||
def imap_unordered(self, fn, iterable):
|
||||
return itertools.imap(fn, iterable)
|
||||
def imap_unordered(self, fn, iterable):
|
||||
return itertools.imap(fn, iterable)
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def join(self):
|
||||
pass
|
||||
|
||||
def cpu_count():
|
||||
return 1
|
||||
def join(self):
|
||||
pass
|
||||
|
||||
|
||||
class File(object):
|
||||
@ -389,7 +388,7 @@ def subconfigure(args):
|
||||
# One would think using a ThreadPool would be faster, considering
|
||||
# everything happens in subprocesses anyways, but no, it's actually
|
||||
# slower on Windows. (20s difference overall!)
|
||||
pool = Pool(min(len(subconfigures), cpu_count()))
|
||||
pool = Pool(len(subconfigures))
|
||||
for relobjdir, returncode, output in \
|
||||
pool.imap_unordered(run, subconfigures):
|
||||
print prefix_lines(output, relobjdir)
|
||||
|
@ -503,16 +503,6 @@ WIN32_EXE_LDFLAGS += -STACK:2097152
|
||||
endif
|
||||
endif
|
||||
|
||||
# If we're building a component on MSVC, we don't want to generate an
|
||||
# import lib, because that import lib will collide with the name of a
|
||||
# static version of the same library.
|
||||
ifeq ($(GNU_LD)$(OS_ARCH),WINNT)
|
||||
ifdef IS_COMPONENT
|
||||
LDFLAGS += -IMPLIB:fake.lib
|
||||
DELETE_AFTER_LINK = fake.lib fake.exp
|
||||
endif
|
||||
endif
|
||||
|
||||
#
|
||||
# Include any personal overrides the user might think are needed.
|
||||
#
|
||||
|
@ -841,7 +841,6 @@ ifdef MOZ_PROFILE_GENERATE
|
||||
touch -t `date +%Y%m%d%H%M.%S -d 'now+5seconds'` pgo.relink
|
||||
endif
|
||||
endif # WINNT && !GCC
|
||||
@$(RM) foodummyfilefoo $(DELETE_AFTER_LINK)
|
||||
chmod +x $@
|
||||
ifdef ENABLE_STRIP
|
||||
$(STRIP) $(STRIP_FLAGS) $@
|
||||
@ -1208,26 +1207,6 @@ misc:: $(call mkdir_deps,$(FINAL_TARGET))
|
||||
$(call py_action,buildlist,$(FINAL_TARGET)/chrome.manifest $(patsubst %,'manifest components/%',$(notdir $(EXTRA_MANIFESTS))))
|
||||
endif
|
||||
|
||||
################################################################################
|
||||
# Copy each element of EXTRA_JS_MODULES to
|
||||
# $(FINAL_TARGET)/modules.
|
||||
FINAL_JS_MODULES_PATH := $(FINAL_TARGET)/modules
|
||||
|
||||
ifdef EXTRA_JS_MODULES
|
||||
ifndef NO_DIST_INSTALL
|
||||
EXTRA_JS_MODULES_FILES := $(EXTRA_JS_MODULES)
|
||||
EXTRA_JS_MODULES_DEST := $(FINAL_JS_MODULES_PATH)
|
||||
INSTALL_TARGETS += EXTRA_JS_MODULES
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef EXTRA_PP_JS_MODULES
|
||||
ifndef NO_DIST_INSTALL
|
||||
EXTRA_PP_JS_MODULES_PATH := $(FINAL_JS_MODULES_PATH)
|
||||
PP_TARGETS += EXTRA_PP_JS_MODULES
|
||||
endif
|
||||
endif
|
||||
|
||||
################################################################################
|
||||
# SDK
|
||||
|
||||
|
@ -12,10 +12,19 @@
|
||||
#include "nsILoadContext.h"
|
||||
#include "nsIPrincipal.h"
|
||||
#include "nsIScriptObjectPrincipal.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "prlog.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(ThirdPartyUtil, mozIThirdPartyUtil)
|
||||
|
||||
//
|
||||
// NSPR_LOG_MODULES=thirdPartyUtil:5
|
||||
//
|
||||
static PRLogModuleInfo *gThirdPartyLog;
|
||||
#undef LOG
|
||||
#define LOG(args) PR_LOG(gThirdPartyLog, PR_LOG_DEBUG, args)
|
||||
|
||||
nsresult
|
||||
ThirdPartyUtil::Init()
|
||||
{
|
||||
@ -23,6 +32,10 @@ ThirdPartyUtil::Init()
|
||||
|
||||
nsresult rv;
|
||||
mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
|
||||
|
||||
if (!gThirdPartyLog)
|
||||
gThirdPartyLog = PR_NewLogModule("thirdPartyUtil");
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -62,7 +75,11 @@ ThirdPartyUtil::GetURIFromWindow(nsIDOMWindow* aWin, nsIURI** result)
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
if (prin->GetIsNullPrincipal()) {
|
||||
LOG(("ThirdPartyUtil::GetURIFromWindow can't use null principal\n"));
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
rv = prin->GetURI(result);
|
||||
return rv;
|
||||
}
|
||||
|
@ -253,7 +253,6 @@ nsImageLoadingContent::OnStopRequest(imgIRequest* aRequest,
|
||||
|
||||
// XXXkhuey should this be GetOurCurrentDoc? Decoding if we're not in
|
||||
// the document seems silly.
|
||||
bool startedDecoding = false;
|
||||
nsIDocument* doc = GetOurOwnerDoc();
|
||||
nsIPresShell* shell = doc ? doc->GetShell() : nullptr;
|
||||
if (shell && shell->IsVisible() &&
|
||||
@ -274,9 +273,7 @@ nsImageLoadingContent::OnStopRequest(imgIRequest* aRequest,
|
||||
// visible.
|
||||
if (!mFrameCreateCalled || (f->GetStateBits() & NS_FRAME_FIRST_REFLOW) ||
|
||||
mVisibleCount > 0 || shell->AssumeAllImagesVisible()) {
|
||||
if (NS_SUCCEEDED(mCurrentRequest->StartDecoding())) {
|
||||
startedDecoding = true;
|
||||
}
|
||||
mCurrentRequest->StartDecoding();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -288,8 +285,8 @@ nsImageLoadingContent::OnStopRequest(imgIRequest* aRequest,
|
||||
uint32_t reqStatus;
|
||||
aRequest->GetImageStatus(&reqStatus);
|
||||
if (NS_SUCCEEDED(aStatus) && !(reqStatus & imgIRequest::STATUS_ERROR) &&
|
||||
(reqStatus & imgIRequest::STATUS_DECODE_STARTED ||
|
||||
(startedDecoding && !(reqStatus & imgIRequest::STATUS_DECODE_COMPLETE)))) {
|
||||
(reqStatus & imgIRequest::STATUS_DECODE_STARTED) &&
|
||||
!(reqStatus & imgIRequest::STATUS_DECODE_COMPLETE)) {
|
||||
mFireEventsOnDecode = true;
|
||||
} else {
|
||||
// Fire the appropriate DOM event.
|
||||
|
@ -45,6 +45,7 @@
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "jswrapper.h"
|
||||
#include "js/SliceBudget.h"
|
||||
#include "nsIArray.h"
|
||||
#include "nsIObjectInputStream.h"
|
||||
#include "nsIObjectOutputStream.h"
|
||||
@ -1692,24 +1693,24 @@ nsJSContext::RunCycleCollectorSlice()
|
||||
|
||||
// Decide how long we want to budget for this slice. By default,
|
||||
// use an unlimited budget.
|
||||
int64_t sliceBudget = -1;
|
||||
js::SliceBudget budget;
|
||||
|
||||
if (sIncrementalCC) {
|
||||
if (gCCStats.mBeginTime.IsNull()) {
|
||||
// If no CC is in progress, use the standard slice time.
|
||||
sliceBudget = kICCSliceBudget;
|
||||
budget = js::SliceBudget(js::TimeBudget(kICCSliceBudget));
|
||||
} else {
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
|
||||
// Only run a limited slice if we're within the max running time.
|
||||
if (TimeBetween(gCCStats.mBeginTime, now) < kMaxICCDuration) {
|
||||
float sliceMultiplier = std::max(TimeBetween(gCCStats.mEndSliceTime, now) / (float)kICCIntersliceDelay, 1.0f);
|
||||
sliceBudget = kICCSliceBudget * sliceMultiplier;
|
||||
budget = js::SliceBudget(js::TimeBudget(kICCSliceBudget * sliceMultiplier));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsCycleCollector_collectSlice(sliceBudget);
|
||||
nsCycleCollector_collectSlice(budget);
|
||||
|
||||
gCCStats.FinishCycleCollectionSlice();
|
||||
}
|
||||
@ -1726,7 +1727,10 @@ nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget)
|
||||
js::ProfileEntry::Category::CC);
|
||||
|
||||
gCCStats.PrepareForCycleCollectionSlice();
|
||||
nsCycleCollector_collectSliceWork(aWorkBudget);
|
||||
|
||||
js::SliceBudget budget = js::SliceBudget(js::WorkBudget(aWorkBudget));
|
||||
nsCycleCollector_collectSlice(budget);
|
||||
|
||||
gCCStats.FinishCycleCollectionSlice();
|
||||
}
|
||||
|
||||
|
@ -379,6 +379,10 @@ BrowserElementChild.prototype = {
|
||||
}
|
||||
debug("Nested event loop - finish");
|
||||
|
||||
if (win.modalDepth == 0) {
|
||||
delete this._windowIDDict[outerWindowID];
|
||||
}
|
||||
|
||||
// If we exited the loop because the inner window changed, then bail on the
|
||||
// modal prompt.
|
||||
if (innerWindowID !== this._tryGetInnerWindowID(win)) {
|
||||
@ -411,7 +415,6 @@ BrowserElementChild.prototype = {
|
||||
}
|
||||
|
||||
let win = this._windowIDDict[outerID].get();
|
||||
delete this._windowIDDict[outerID];
|
||||
|
||||
if (!win) {
|
||||
debug("recvStopWaiting, but window is gone\n");
|
||||
|
@ -124,7 +124,7 @@ function test4() {
|
||||
|
||||
// test4 is a mozbrowsershowmodalprompt listener.
|
||||
function test5(e) {
|
||||
iframe.removeEventListener('mozbrowsershowmodalprompt', test4);
|
||||
iframe.removeEventListener('mozbrowsershowmodalprompt', test5);
|
||||
|
||||
is(e.detail.message, 'test4');
|
||||
e.preventDefault(); // cause the page to block.
|
||||
@ -139,6 +139,140 @@ function test5a() {
|
||||
|
||||
function test5b() {
|
||||
iframe.removeEventListener('mozbrowserloadend', test5b);
|
||||
SimpleTest.executeSoon(test6);
|
||||
}
|
||||
|
||||
// Test nested alerts
|
||||
var promptBlockers = [];
|
||||
function test6() {
|
||||
iframe.addEventListener("mozbrowsershowmodalprompt", test6a);
|
||||
|
||||
var script = 'data:,\
|
||||
this.testState = 0; \
|
||||
content.alert(1); \
|
||||
this.testState = 3; \
|
||||
';
|
||||
mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
|
||||
}
|
||||
|
||||
function test6a(e) {
|
||||
iframe.removeEventListener("mozbrowsershowmodalprompt", test6a);
|
||||
|
||||
is(e.detail.message, '1');
|
||||
e.preventDefault(); // cause the alert to block.
|
||||
promptBlockers.push(e);
|
||||
|
||||
SimpleTest.executeSoon(test6b);
|
||||
}
|
||||
|
||||
function test6b() {
|
||||
var script = 'data:,\
|
||||
if (this.testState === 0) { \
|
||||
sendAsyncMessage("test-success", "1: Correct testState"); \
|
||||
} \
|
||||
else { \
|
||||
sendAsyncMessage("test-fail", "1: Wrong testState: " + this.testState); \
|
||||
}';
|
||||
mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
|
||||
numPendingChildTests++;
|
||||
|
||||
waitForPendingTests(test6c);
|
||||
}
|
||||
|
||||
function test6c() {
|
||||
iframe.addEventListener("mozbrowsershowmodalprompt", test6d);
|
||||
|
||||
var script = 'data:,\
|
||||
this.testState = 1; \
|
||||
content.alert(2); \
|
||||
this.testState = 2; \
|
||||
';
|
||||
mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
|
||||
}
|
||||
|
||||
function test6d(e) {
|
||||
iframe.removeEventListener("mozbrowsershowmodalprompt", test6d);
|
||||
|
||||
is(e.detail.message, '2');
|
||||
e.preventDefault(); // cause the alert to block.
|
||||
promptBlockers.push(e);
|
||||
|
||||
SimpleTest.executeSoon(test6e);
|
||||
}
|
||||
|
||||
function test6e() {
|
||||
var script = 'data:,\
|
||||
if (this.testState === 1) { \
|
||||
sendAsyncMessage("test-success", "2: Correct testState"); \
|
||||
} \
|
||||
else { \
|
||||
sendAsyncMessage("test-fail", "2: Wrong testState: " + this.testState); \
|
||||
}';
|
||||
mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
|
||||
numPendingChildTests++;
|
||||
|
||||
waitForPendingTests(test6f);
|
||||
}
|
||||
|
||||
function test6f() {
|
||||
var e = promptBlockers.pop();
|
||||
// Now unblock the iframe and check that the script completed.
|
||||
e.detail.unblock();
|
||||
|
||||
var script2 = 'data:,\
|
||||
if (this.testState === 2) { \
|
||||
sendAsyncMessage("test-success", "3: Correct testState"); \
|
||||
} \
|
||||
else { \
|
||||
sendAsyncMessage("test-try-again", "3: Wrong testState (for now): " + this.testState); \
|
||||
}';
|
||||
|
||||
// Urgh. e.unblock() didn't necessarily unblock us immediately, so we have
|
||||
// to spin and wait.
|
||||
function onTryAgain() {
|
||||
SimpleTest.executeSoon(function() {
|
||||
//dump('onTryAgain\n');
|
||||
mm.loadFrameScript(script2, /* allowDelayedLoad = */ false);
|
||||
});
|
||||
}
|
||||
|
||||
mm.addMessageListener('test-try-again', onTryAgain);
|
||||
numPendingChildTests++;
|
||||
|
||||
onTryAgain();
|
||||
waitForPendingTests(test6g);
|
||||
}
|
||||
|
||||
function test6g() {
|
||||
var e = promptBlockers.pop();
|
||||
// Now unblock the iframe and check that the script completed.
|
||||
e.detail.unblock();
|
||||
|
||||
var script2 = 'data:,\
|
||||
if (this.testState === 3) { \
|
||||
sendAsyncMessage("test-success", "4: Correct testState"); \
|
||||
} \
|
||||
else { \
|
||||
sendAsyncMessage("test-try-again", "4: Wrong testState (for now): " + this.testState); \
|
||||
}';
|
||||
|
||||
// Urgh. e.unblock() didn't necessarily unblock us immediately, so we have
|
||||
// to spin and wait.
|
||||
function onTryAgain() {
|
||||
SimpleTest.executeSoon(function() {
|
||||
//dump('onTryAgain\n');
|
||||
mm.loadFrameScript(script2, /* allowDelayedLoad = */ false);
|
||||
});
|
||||
}
|
||||
|
||||
mm.addMessageListener('test-try-again', onTryAgain);
|
||||
numPendingChildTests++;
|
||||
|
||||
onTryAgain();
|
||||
waitForPendingTests(test6h);
|
||||
}
|
||||
|
||||
function test6h() {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
@ -2886,11 +2886,11 @@ void HTMLMediaElement::ProcessMediaFragmentURI()
|
||||
}
|
||||
|
||||
void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
|
||||
const MetadataTags* aTags)
|
||||
nsAutoPtr<const MetadataTags> aTags)
|
||||
{
|
||||
mHasAudio = aInfo->HasAudio();
|
||||
mHasVideo = aInfo->HasVideo();
|
||||
mTags = aTags;
|
||||
mTags = aTags.forget();
|
||||
mLoadedDataFired = false;
|
||||
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
|
||||
@ -2903,11 +2903,11 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
|
||||
// If this element had a video track, but consists only of an audio track now,
|
||||
// delete the VideoFrameContainer. This happens when the src is changed to an
|
||||
// audio only file.
|
||||
if (!aInfo->HasVideo() && mVideoFrameContainer) {
|
||||
// call ForgetElement() such that callbacks from |mVideoFrameContainer|
|
||||
// won't reach us anymore.
|
||||
mVideoFrameContainer->ForgetElement();
|
||||
mVideoFrameContainer = nullptr;
|
||||
// Else update its dimensions.
|
||||
if (!aInfo->HasVideo()) {
|
||||
ResetState();
|
||||
} else {
|
||||
UpdateMediaSize(aInfo->mVideo.mDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,7 +160,7 @@ public:
|
||||
// when it has read the metadata containing video dimensions,
|
||||
// etc.
|
||||
virtual void MetadataLoaded(const MediaInfo* aInfo,
|
||||
const MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
nsAutoPtr<const MetadataTags> aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
|
||||
// Called by the decoder object, on the main thread,
|
||||
// when it has read the first frame of the video or audio.
|
||||
|
@ -45,7 +45,8 @@ function frameLoad() {
|
||||
function load() {
|
||||
window.loaded = true;
|
||||
|
||||
var imgsrc = "<img onload ='window.parent.imgLoad()' src='image.png'>\n";
|
||||
var imgsrc = "<img onload ='window.parent.imgLoad()' src='image.png?noCache="
|
||||
+ (new Date().getTime()) + "'>\n";
|
||||
var doc = $('iframe').contentWindow.document;
|
||||
doc.writeln(imgsrc);
|
||||
doc.close();
|
||||
|
@ -1130,10 +1130,10 @@ ContentChild::RecvSetProcessSandbox()
|
||||
#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 19
|
||||
// For B2G >= KitKat, sandboxing is mandatory; this has already
|
||||
// been enforced by ContentParent::StartUp().
|
||||
MOZ_ASSERT(CanSandboxContentProcess());
|
||||
MOZ_ASSERT(ContentProcessSandboxStatus() != kSandboxingWouldFail);
|
||||
#else
|
||||
// Otherwise, sandboxing is best-effort.
|
||||
if (!CanSandboxContentProcess()) {
|
||||
if (ContentProcessSandboxStatus() == kSandboxingWouldFail) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
@ -687,7 +687,7 @@ ContentParent::StartUp()
|
||||
#if defined(MOZ_CONTENT_SANDBOX) && defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 19
|
||||
// Require sandboxing on B2G >= KitKat. This condition must stay
|
||||
// in sync with ContentChild::RecvSetProcessSandbox.
|
||||
if (!CanSandboxContentProcess()) {
|
||||
if (ContentProcessSandboxStatus() == kSandboxingWouldFail) {
|
||||
// MOZ_CRASH strings are only for debug builds; make sure the
|
||||
// message is clear on non-debug builds as well:
|
||||
printf_stderr("Sandboxing support is required on this platform. "
|
||||
|
@ -88,8 +88,9 @@ public:
|
||||
// Return true if the transport layer supports seeking.
|
||||
virtual bool IsMediaSeekable() = 0;
|
||||
|
||||
virtual void MetadataLoaded(MediaInfo* aInfo, MetadataTags* aTags) = 0;
|
||||
virtual void QueueMetadata(int64_t aTime, MediaInfo* aInfo, MetadataTags* aTags) = 0;
|
||||
virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) = 0;
|
||||
virtual void QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) = 0;
|
||||
virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo) = 0;
|
||||
|
||||
virtual void RemoveMediaTracks() = 0;
|
||||
|
||||
@ -153,15 +154,29 @@ public:
|
||||
#endif
|
||||
};
|
||||
|
||||
class MetadataEventRunner : public nsRunnable
|
||||
class MetadataContainer
|
||||
{
|
||||
private:
|
||||
nsRefPtr<AbstractMediaDecoder> mDecoder;
|
||||
public:
|
||||
MetadataEventRunner(AbstractMediaDecoder* aDecoder, MediaInfo* aInfo, MetadataTags* aTags)
|
||||
: mDecoder(aDecoder),
|
||||
mInfo(aInfo),
|
||||
mTags(aTags)
|
||||
protected:
|
||||
MetadataContainer(AbstractMediaDecoder* aDecoder,
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags)
|
||||
: mDecoder(aDecoder),
|
||||
mInfo(aInfo),
|
||||
mTags(aTags)
|
||||
{}
|
||||
|
||||
nsRefPtr<AbstractMediaDecoder> mDecoder;
|
||||
nsAutoPtr<MediaInfo> mInfo;
|
||||
nsAutoPtr<MetadataTags> mTags;
|
||||
};
|
||||
|
||||
class MetadataEventRunner : public nsRunnable, private MetadataContainer
|
||||
{
|
||||
public:
|
||||
MetadataEventRunner(AbstractMediaDecoder* aDecoder,
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags)
|
||||
: MetadataContainer(aDecoder, aInfo, aTags)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run() MOZ_OVERRIDE
|
||||
@ -169,12 +184,40 @@ class MetadataEventRunner : public nsRunnable
|
||||
mDecoder->MetadataLoaded(mInfo, mTags);
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
// The ownership is transferred to MediaDecoder.
|
||||
MediaInfo* mInfo;
|
||||
class FirstFrameLoadedEventRunner : public nsRunnable, private MetadataContainer
|
||||
{
|
||||
public:
|
||||
FirstFrameLoadedEventRunner(AbstractMediaDecoder* aDecoder,
|
||||
nsAutoPtr<MediaInfo> aInfo)
|
||||
: MetadataContainer(aDecoder, aInfo, nsAutoPtr<MetadataTags>(nullptr))
|
||||
{}
|
||||
|
||||
// The ownership is transferred to its owning element.
|
||||
MetadataTags* mTags;
|
||||
NS_IMETHOD Run() MOZ_OVERRIDE
|
||||
{
|
||||
mDecoder->FirstFrameLoaded(mInfo);
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
class MetadataUpdatedEventRunner : public nsRunnable, private MetadataContainer
|
||||
{
|
||||
public:
|
||||
MetadataUpdatedEventRunner(AbstractMediaDecoder* aDecoder,
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags)
|
||||
: MetadataContainer(aDecoder, aInfo, aTags)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run() MOZ_OVERRIDE
|
||||
{
|
||||
nsAutoPtr<MediaInfo> info(new MediaInfo());
|
||||
*info = *mInfo;
|
||||
mDecoder->MetadataLoaded(info, mTags);
|
||||
mDecoder->FirstFrameLoaded(mInfo);
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
class RemoveMediaTracksEventRunner : public nsRunnable
|
||||
|
@ -437,7 +437,7 @@ MediaDecoder::MediaDecoder() :
|
||||
mReentrantMonitor("media.decoder"),
|
||||
mIsDormant(false),
|
||||
mIsExitingDormant(false),
|
||||
mPlayState(PLAY_STATE_PAUSED),
|
||||
mPlayState(PLAY_STATE_LOADING),
|
||||
mNextState(PLAY_STATE_PAUSED),
|
||||
mIgnoreProgressData(false),
|
||||
mInfiniteStream(false),
|
||||
@ -562,8 +562,6 @@ nsresult MediaDecoder::InitializeStateMachine(MediaDecoder* aCloneDonor)
|
||||
// set them now
|
||||
SetStateMachineParameters();
|
||||
|
||||
ChangeState(PLAY_STATE_LOADING);
|
||||
|
||||
return ScheduleStateMachineThread();
|
||||
}
|
||||
|
||||
@ -670,8 +668,8 @@ already_AddRefed<nsIPrincipal> MediaDecoder::GetCurrentPrincipal()
|
||||
}
|
||||
|
||||
void MediaDecoder::QueueMetadata(int64_t aPublishTime,
|
||||
MediaInfo* aInfo,
|
||||
MetadataTags* aTags)
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags)
|
||||
{
|
||||
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
|
||||
GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
@ -686,9 +684,11 @@ MediaDecoder::IsDataCachedToEndOfResource()
|
||||
mResource->IsDataCachedToEndOfResource(mDecoderPosition));
|
||||
}
|
||||
|
||||
void MediaDecoder::MetadataLoaded(MediaInfo* aInfo, MetadataTags* aTags)
|
||||
void MediaDecoder::MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mShuttingDown) {
|
||||
return;
|
||||
}
|
||||
@ -714,17 +714,37 @@ void MediaDecoder::MetadataLoaded(MediaInfo* aInfo, MetadataTags* aTags)
|
||||
SetInfinite(true);
|
||||
}
|
||||
|
||||
mInfo = aInfo;
|
||||
mInfo = aInfo.forget();
|
||||
ConstructMediaTracks();
|
||||
|
||||
if (mOwner) {
|
||||
// Make sure the element and the frame (if any) are told about
|
||||
// our new size.
|
||||
Invalidate();
|
||||
mOwner->MetadataLoaded(aInfo, aTags);
|
||||
mOwner->MetadataLoaded(mInfo, nsAutoPtr<const MetadataTags>(aTags.forget()));
|
||||
}
|
||||
}
|
||||
|
||||
void MediaDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
DECODER_LOG("FirstFrameLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d",
|
||||
aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
|
||||
aInfo->HasAudio(), aInfo->HasVideo());
|
||||
|
||||
if (mPlayState == PLAY_STATE_LOADING && mIsDormant && !mIsExitingDormant) {
|
||||
return;
|
||||
}
|
||||
|
||||
mInfo = aInfo.forget();
|
||||
|
||||
if (mOwner) {
|
||||
Invalidate();
|
||||
mOwner->FirstFrameLoaded();
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,9 @@ The state machine has the following states:
|
||||
|
||||
DECODING_METADATA
|
||||
The media headers are being loaded, and things like framerate, etc are
|
||||
being determined, and the first frame of audio/video data is being decoded.
|
||||
being determined.
|
||||
DECODING_FIRSTFRAME
|
||||
The first frame of audio/video data is being decoded.
|
||||
DECODING
|
||||
The decode has started. If the PlayState is PLAYING, the decode thread
|
||||
should be alive and decoding video and audio frame, the audio thread
|
||||
@ -104,32 +106,36 @@ Seek(double)
|
||||
|
||||
A state transition diagram:
|
||||
|
||||
DECODING_METADATA
|
||||
| |
|
||||
v | Shutdown()
|
||||
| |
|
||||
v -->-------------------->--------------------------|
|
||||
|---------------->----->------------------------| v
|
||||
DECODING | | | | |
|
||||
^ v Seek(t) | | | |
|
||||
| Play() | v | | |
|
||||
^-----------<----SEEKING | v Complete v v
|
||||
| | | | | |
|
||||
| | | COMPLETED SHUTDOWN-<-|
|
||||
^ ^ | |Shutdown() |
|
||||
| | | >-------->-----^
|
||||
| Play() |Seek(t) |Buffer() |
|
||||
-----------<--------<-------BUFFERING |
|
||||
| ^
|
||||
v Shutdown() |
|
||||
| |
|
||||
------------>-----|
|
||||
|---<-- DECODING_METADATA ----->--------|
|
||||
| | |
|
||||
Seek(t) v Shutdown()
|
||||
| | |
|
||||
-->--- DECODING_FIRSTFRAME |------->-----------------|
|
||||
| | |
|
||||
| Shutdown() |
|
||||
| | |
|
||||
v |-->----------------->--------------------------|
|
||||
|---------------->----->------------------------| v
|
||||
DECODING | | | | |
|
||||
^ v Seek(t) | | | |
|
||||
| Play() | v | | |
|
||||
^-----------<----SEEKING | v Complete v v
|
||||
| | | | | |
|
||||
| | | COMPLETED SHUTDOWN-<-|
|
||||
^ ^ | |Shutdown() |
|
||||
| | | >-------->-----^
|
||||
| Play() |Seek(t) |Buffer() |
|
||||
-----------<--------<-------BUFFERING |
|
||||
| ^
|
||||
v Shutdown() |
|
||||
| |
|
||||
------------>-----|
|
||||
|
||||
The following represents the states that the MediaDecoder object
|
||||
can be in, and the valid states the MediaDecoderStateMachine can be in at that
|
||||
time:
|
||||
|
||||
player LOADING decoder DECODING_METADATA
|
||||
player LOADING decoder DECODING_METADATA, DECODING_FIRSTFRAME
|
||||
player PLAYING decoder DECODING, BUFFERING, SEEKING, COMPLETED
|
||||
player PAUSED decoder DECODING, BUFFERING, SEEKING, COMPLETED
|
||||
player SEEKING decoder SEEKING
|
||||
@ -756,8 +762,8 @@ public:
|
||||
// main thread to be presented when the |currentTime| of the media is greater
|
||||
// or equal to aPublishTime.
|
||||
void QueueMetadata(int64_t aPublishTime,
|
||||
MediaInfo* aInfo,
|
||||
MetadataTags* aTags);
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags);
|
||||
|
||||
int64_t GetSeekTime() { return mRequestedSeekTarget.mTime; }
|
||||
void ResetSeekTime() { mRequestedSeekTarget.Reset(); }
|
||||
@ -782,8 +788,12 @@ public:
|
||||
|
||||
// Called when the metadata from the media file has been loaded by the
|
||||
// state machine. Call on the main thread only.
|
||||
virtual void MetadataLoaded(MediaInfo* aInfo,
|
||||
MetadataTags* aTags);
|
||||
virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags);
|
||||
|
||||
// Called when the first audio and/or video from the media file has been loaded
|
||||
// by the state machine. Call on the main thread only.
|
||||
virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo);
|
||||
|
||||
// Called from MetadataLoaded(). Creates audio tracks and adds them to its
|
||||
// owner's audio track list, and implies to video tracks respectively.
|
||||
@ -794,10 +804,6 @@ public:
|
||||
// the track list. Call on the main thread only.
|
||||
virtual void RemoveMediaTracks() MOZ_OVERRIDE;
|
||||
|
||||
// Called when the first frame has been loaded.
|
||||
// Call on the main thread only.
|
||||
void FirstFrameLoaded();
|
||||
|
||||
// Returns true if the resource has been loaded. Acquires the monitor.
|
||||
// Call from any thread.
|
||||
virtual bool IsDataCachedToEndOfResource();
|
||||
|
@ -51,8 +51,9 @@ public:
|
||||
// Called by the video decoder object, on the main thread,
|
||||
// when it has read the metadata containing video dimensions,
|
||||
// etc.
|
||||
// Must take ownership of MetadataTags aTags argument.
|
||||
virtual void MetadataLoaded(const MediaInfo* aInfo,
|
||||
const MetadataTags* aTags) = 0;
|
||||
nsAutoPtr<const MetadataTags> aTags) = 0;
|
||||
|
||||
// Called by the decoder object, on the main thread,
|
||||
// when it has read the first frame of the video or audio.
|
||||
|
@ -101,6 +101,10 @@ public:
|
||||
virtual nsresult ReadMetadata(MediaInfo* aInfo,
|
||||
MetadataTags** aTags) = 0;
|
||||
|
||||
// Fills aInfo with the latest cached data required to present the media,
|
||||
// ReadUpdatedMetadata will always be called once ReadMetadata has succeeded.
|
||||
virtual void ReadUpdatedMetadata(MediaInfo* aInfo) { };
|
||||
|
||||
// Requests the Reader to seek and call OnSeekCompleted on the callback
|
||||
// once completed.
|
||||
// Moves the decode head to aTime microseconds. aStartTime and aEndTime
|
||||
|
@ -371,7 +371,8 @@ void MediaDecoderStateMachine::SendStreamData()
|
||||
return;
|
||||
}
|
||||
|
||||
if (mState == DECODER_STATE_DECODING_METADATA) {
|
||||
if (mState == DECODER_STATE_DECODING_METADATA ||
|
||||
mState == DECODER_STATE_DECODING_FIRSTFRAME) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -705,9 +706,9 @@ MediaDecoderStateMachine::OnAudioDecoded(AudioData* aAudioSample)
|
||||
(audio ? audio->mDiscontinuity : 0));
|
||||
|
||||
switch (mState) {
|
||||
case DECODER_STATE_DECODING_METADATA: {
|
||||
case DECODER_STATE_DECODING_FIRSTFRAME: {
|
||||
Push(audio.forget());
|
||||
MaybeFinishDecodeMetadata();
|
||||
MaybeFinishDecodeFirstFrame();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -770,7 +771,7 @@ MediaDecoderStateMachine::Push(AudioData* aSample)
|
||||
// otherwise AdvanceFrame may pop the sample before we have a chance
|
||||
// to reach playing.
|
||||
AudioQueue().Push(aSample);
|
||||
if (mState > DECODER_STATE_DECODING_METADATA) {
|
||||
if (mState > DECODER_STATE_DECODING_FIRSTFRAME) {
|
||||
SendStreamData();
|
||||
// The ready state can change when we've decoded data, so update the
|
||||
// ready state, so that DOM events can fire.
|
||||
@ -788,7 +789,7 @@ MediaDecoderStateMachine::Push(VideoData* aSample)
|
||||
// otherwise AdvanceFrame may pop the sample before we have a chance
|
||||
// to reach playing.
|
||||
VideoQueue().Push(aSample);
|
||||
if (mState > DECODER_STATE_DECODING_METADATA) {
|
||||
if (mState > DECODER_STATE_DECODING_FIRSTFRAME) {
|
||||
SendStreamData();
|
||||
// The ready state can change when we've decoded data, so update the
|
||||
// ready state, so that DOM events can fire.
|
||||
@ -839,8 +840,8 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
|
||||
}
|
||||
isAudio ? AudioQueue().Finish() : VideoQueue().Finish();
|
||||
switch (mState) {
|
||||
case DECODER_STATE_DECODING_METADATA: {
|
||||
MaybeFinishDecodeMetadata();
|
||||
case DECODER_STATE_DECODING_FIRSTFRAME: {
|
||||
MaybeFinishDecodeFirstFrame();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -883,14 +884,14 @@ MediaDecoderStateMachine::AcquireMonitorAndInvokeDecodeError()
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::MaybeFinishDecodeMetadata()
|
||||
MediaDecoderStateMachine::MaybeFinishDecodeFirstFrame()
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
if ((IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
|
||||
(IsVideoDecoding() && VideoQueue().GetSize() == 0)) {
|
||||
return;
|
||||
}
|
||||
if (NS_FAILED(FinishDecodeMetadata())) {
|
||||
if (NS_FAILED(FinishDecodeFirstFrame())) {
|
||||
DecodeError();
|
||||
}
|
||||
}
|
||||
@ -908,9 +909,9 @@ MediaDecoderStateMachine::OnVideoDecoded(VideoData* aVideoSample)
|
||||
(video ? video->mDiscontinuity : 0));
|
||||
|
||||
switch (mState) {
|
||||
case DECODER_STATE_DECODING_METADATA: {
|
||||
case DECODER_STATE_DECODING_FIRSTFRAME: {
|
||||
Push(video.forget());
|
||||
MaybeFinishDecodeMetadata();
|
||||
MaybeFinishDecodeFirstFrame();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1221,6 +1222,7 @@ static const char* const gMachineStateStr[] = {
|
||||
"NONE",
|
||||
"DECODING_METADATA",
|
||||
"WAIT_FOR_RESOURCES",
|
||||
"DECODING_FIRSTFRAME",
|
||||
"DORMANT",
|
||||
"DECODING",
|
||||
"SEEKING",
|
||||
@ -1527,12 +1529,55 @@ void MediaDecoderStateMachine::Seek(const SeekTarget& aTarget)
|
||||
DECODER_WARN("Seek() function should not be called on a non-seekable state machine");
|
||||
return;
|
||||
}
|
||||
|
||||
// MediaDecoder::mPlayState should be SEEKING while we seek, and
|
||||
// in that case MediaDecoder shouldn't be calling us.
|
||||
NS_ASSERTION(mState != DECODER_STATE_SEEKING,
|
||||
"We shouldn't already be seeking");
|
||||
NS_ASSERTION(mState >= DECODER_STATE_DECODING,
|
||||
"We should have loaded metadata");
|
||||
NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA,
|
||||
"We should have got duration already");
|
||||
|
||||
if (mState <= DECODER_STATE_DECODING_FIRSTFRAME) {
|
||||
DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
|
||||
mQueuedSeekTarget = aTarget;
|
||||
return;
|
||||
}
|
||||
mQueuedSeekTarget.Reset();
|
||||
|
||||
StartSeek(aTarget);
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::EnqueueStartQueuedSeekTask()
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::StartQueuedSeek);
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::StartQueuedSeek()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
if (!mQueuedSeekTarget.IsValid()) {
|
||||
return;
|
||||
}
|
||||
StartSeek(mQueuedSeekTarget);
|
||||
mQueuedSeekTarget.Reset();
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::StartSeek(const SeekTarget& aTarget)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
AssertCurrentThreadInMonitor();
|
||||
|
||||
MOZ_ASSERT(mState >= DECODER_STATE_DECODING);
|
||||
|
||||
if (mState == DECODER_STATE_SHUTDOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bound the seek time to be inside the media range.
|
||||
NS_ASSERTION(mStartTime != -1, "Should know start time by now");
|
||||
@ -1554,8 +1599,8 @@ void MediaDecoderStateMachine::Seek(const SeekTarget& aTarget)
|
||||
|
||||
void MediaDecoderStateMachine::StopAudioThread()
|
||||
{
|
||||
NS_ASSERTION(OnDecodeThread() ||
|
||||
OnStateMachineThread(), "Should be on decode thread or state machine thread");
|
||||
NS_ASSERTION(OnDecodeThread() || OnStateMachineThread(),
|
||||
"Should be on decode thread or state machine thread");
|
||||
AssertCurrentThreadInMonitor();
|
||||
|
||||
if (mStopAudioThread) {
|
||||
@ -1598,6 +1643,19 @@ MediaDecoderStateMachine::EnqueueDecodeMetadataTask()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask()
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
|
||||
|
||||
RefPtr<nsIRunnable> task(
|
||||
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeFirstFrame));
|
||||
nsresult rv = mDecodeTaskQueue->Dispatch(task);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::SetReaderIdle()
|
||||
{
|
||||
@ -1733,7 +1791,7 @@ MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA);
|
||||
MOZ_ASSERT(mState > DECODER_STATE_DECODING_FIRSTFRAME);
|
||||
|
||||
if (IsAudioDecoding() && !mAudioRequestPending && !mWaitingForDecoderSeek) {
|
||||
RefPtr<nsIRunnable> task(
|
||||
@ -1778,7 +1836,7 @@ MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA);
|
||||
MOZ_ASSERT(mState > DECODER_STATE_DECODING_FIRSTFRAME);
|
||||
|
||||
if (IsVideoDecoding() && !mVideoRequestPending && !mWaitingForDecoderSeek) {
|
||||
RefPtr<nsIRunnable> task(
|
||||
@ -1856,8 +1914,8 @@ bool MediaDecoderStateMachine::HasLowUndecodedData()
|
||||
bool MediaDecoderStateMachine::HasLowUndecodedData(double aUsecs)
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA,
|
||||
"Must have loaded metadata for GetBuffered() to work");
|
||||
NS_ASSERTION(mState > DECODER_STATE_DECODING_FIRSTFRAME,
|
||||
"Must have loaded first frame for GetBuffered() to work");
|
||||
|
||||
bool reliable;
|
||||
double bytesPerSecond = mDecoder->ComputePlaybackRate(&reliable);
|
||||
@ -1967,6 +2025,55 @@ nsresult MediaDecoderStateMachine::DecodeMetadata()
|
||||
mDecoder->StartProgressUpdates();
|
||||
mGotDurationFromMetaData = (GetDuration() != -1);
|
||||
|
||||
if (mGotDurationFromMetaData) {
|
||||
// We have all the information required: duration and size
|
||||
// Inform the element that we've loaded the metadata.
|
||||
EnqueueLoadedMetadataEvent();
|
||||
}
|
||||
|
||||
if (mState == DECODER_STATE_DECODING_METADATA) {
|
||||
SetState(DECODER_STATE_DECODING_FIRSTFRAME);
|
||||
res = EnqueueDecodeFirstFrameTask();
|
||||
if (NS_FAILED(res)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
ScheduleStateMachine();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::EnqueueLoadedMetadataEvent()
|
||||
{
|
||||
nsAutoPtr<MediaInfo> info(new MediaInfo());
|
||||
*info = mInfo;
|
||||
nsCOMPtr<nsIRunnable> metadataLoadedEvent =
|
||||
new MetadataEventRunner(mDecoder, info, mMetadataTags);
|
||||
NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderStateMachine::CallDecodeFirstFrame()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
if (mState != DECODER_STATE_DECODING_FIRSTFRAME) {
|
||||
return;
|
||||
}
|
||||
if (NS_FAILED(DecodeFirstFrame())) {
|
||||
DECODER_WARN("Decode failed to start, shutting down decoder");
|
||||
DecodeError();
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaDecoderStateMachine::DecodeFirstFrame()
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
|
||||
MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
|
||||
DECODER_LOG("DecodeFirstFrame started");
|
||||
|
||||
if (HasAudio()) {
|
||||
RefPtr<nsIRunnable> decodeTask(
|
||||
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded));
|
||||
@ -1980,11 +2087,11 @@ nsresult MediaDecoderStateMachine::DecodeMetadata()
|
||||
|
||||
if (mScheduler->IsRealTime()) {
|
||||
SetStartTime(0);
|
||||
res = FinishDecodeMetadata();
|
||||
nsresult res = FinishDecodeFirstFrame();
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
} else if (mDecodingFrozenAtStateMetadata) {
|
||||
SetStartTime(mStartTime);
|
||||
res = FinishDecodeMetadata();
|
||||
nsresult res = FinishDecodeFirstFrame();
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
} else {
|
||||
if (HasAudio()) {
|
||||
@ -2001,11 +2108,11 @@ nsresult MediaDecoderStateMachine::DecodeMetadata()
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaDecoderStateMachine::FinishDecodeMetadata()
|
||||
MediaDecoderStateMachine::FinishDecodeFirstFrame()
|
||||
{
|
||||
AssertCurrentThreadInMonitor();
|
||||
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
|
||||
DECODER_LOG("FinishDecodeMetadata");
|
||||
DECODER_LOG("FinishDecodeFirstFrame");
|
||||
|
||||
if (mState == DECODER_STATE_SHUTDOWN) {
|
||||
return NS_ERROR_FAILURE;
|
||||
@ -2044,19 +2151,34 @@ MediaDecoderStateMachine::FinishDecodeMetadata()
|
||||
mLowAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
|
||||
}
|
||||
|
||||
// Inform the element that we've loaded the metadata and the first frame.
|
||||
// Get potentially updated metadata
|
||||
{
|
||||
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
|
||||
mReader->ReadUpdatedMetadata(&mInfo);
|
||||
}
|
||||
|
||||
nsAutoPtr<MediaInfo> info(new MediaInfo());
|
||||
*info = mInfo;
|
||||
nsCOMPtr<nsIRunnable> metadataLoadedEvent =
|
||||
new MetadataEventRunner(mDecoder, info.forget(), mMetadataTags.forget());
|
||||
NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
|
||||
nsCOMPtr<nsIRunnable> event;
|
||||
if (!mGotDurationFromMetaData) {
|
||||
// We now have a duration, we can fire the LoadedMetadata and
|
||||
// FirstFrame event.
|
||||
event =
|
||||
new MetadataUpdatedEventRunner(mDecoder,
|
||||
info,
|
||||
mMetadataTags);
|
||||
} else {
|
||||
// Inform the element that we've loaded the first frame.
|
||||
event =
|
||||
new FirstFrameLoadedEventRunner(mDecoder, info);
|
||||
}
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
|
||||
if (mState == DECODER_STATE_DECODING_METADATA) {
|
||||
DECODER_LOG("Changed state from DECODING_METADATA to DECODING");
|
||||
if (mState == DECODER_STATE_DECODING_FIRSTFRAME) {
|
||||
StartDecoding();
|
||||
}
|
||||
|
||||
// For very short media the metadata decode can decode the entire media.
|
||||
// For very short media the first frame decode can decode the entire media.
|
||||
// So we need to check if this has occurred, else our decode pipeline won't
|
||||
// run (since it doesn't need to) and we won't detect end of stream.
|
||||
CheckIfDecodeComplete();
|
||||
@ -2068,6 +2190,10 @@ MediaDecoderStateMachine::FinishDecodeMetadata()
|
||||
StartPlayback();
|
||||
}
|
||||
|
||||
if (mQueuedSeekTarget.IsValid()) {
|
||||
EnqueueStartQueuedSeekTask();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -2214,6 +2340,7 @@ MediaDecoderStateMachine::SeekCompleted()
|
||||
|
||||
mDecoder->StartProgressUpdates();
|
||||
if (mState == DECODER_STATE_DECODING_METADATA ||
|
||||
mState == DECODER_STATE_DECODING_FIRSTFRAME ||
|
||||
mState == DECODER_STATE_DORMANT ||
|
||||
mState == DECODER_STATE_SHUTDOWN) {
|
||||
return;
|
||||
@ -2399,6 +2526,11 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
case DECODER_STATE_DECODING_FIRSTFRAME: {
|
||||
// DECODER_STATE_DECODING_FIRSTFRAME will be started by DecodeMetadata
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
case DECODER_STATE_DECODING: {
|
||||
if (mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING &&
|
||||
IsPlaying())
|
||||
@ -3117,15 +3249,15 @@ bool MediaDecoderStateMachine::IsShutdown()
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::QueueMetadata(int64_t aPublishTime,
|
||||
MediaInfo* aInfo,
|
||||
MetadataTags* aTags)
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags)
|
||||
{
|
||||
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
|
||||
AssertCurrentThreadInMonitor();
|
||||
TimedMetadata* metadata = new TimedMetadata;
|
||||
metadata->mPublishTime = aPublishTime;
|
||||
metadata->mInfo = aInfo;
|
||||
metadata->mTags = aTags;
|
||||
metadata->mInfo = aInfo.forget();
|
||||
metadata->mTags = aTags.forget();
|
||||
mMetadataManager.QueueMetadata(metadata);
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,7 @@ public:
|
||||
DECODER_STATE_DECODING_NONE,
|
||||
DECODER_STATE_DECODING_METADATA,
|
||||
DECODER_STATE_WAIT_FOR_RESOURCES,
|
||||
DECODER_STATE_DECODING_FIRSTFRAME,
|
||||
DECODER_STATE_DORMANT,
|
||||
DECODER_STATE_DECODING,
|
||||
DECODER_STATE_SEEKING,
|
||||
@ -198,8 +199,22 @@ public:
|
||||
void Play();
|
||||
|
||||
// Seeks to the decoder to aTarget asynchronously.
|
||||
// Must be called from the main thread.
|
||||
void Seek(const SeekTarget& aTarget);
|
||||
|
||||
// Dispatches a task to the main thread to seek to mQueuedSeekTarget.
|
||||
// This is threadsafe and can be called on any thread.
|
||||
void EnqueueStartQueuedSeekTask();
|
||||
|
||||
// Seeks to the decoder to mQueuedSeekTarget asynchronously.
|
||||
// Must be called from the main thread.
|
||||
void StartQueuedSeek();
|
||||
|
||||
// Seeks to the decoder to aTarget asynchronously.
|
||||
// Must be called from the main thread.
|
||||
// The decoder monitor must be held with exactly one lock count.
|
||||
void StartSeek(const SeekTarget& aTarget);
|
||||
|
||||
// Returns the current playback position in seconds.
|
||||
// Called from the main thread to get the current frame time. The decoder
|
||||
// monitor must be obtained before calling this.
|
||||
@ -321,7 +336,9 @@ public:
|
||||
// shutting down. The decoder monitor must be held while calling this.
|
||||
bool IsShutdown();
|
||||
|
||||
void QueueMetadata(int64_t aPublishTime, MediaInfo* aInfo, MetadataTags* aTags);
|
||||
void QueueMetadata(int64_t aPublishTime,
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags);
|
||||
|
||||
// Returns true if we're currently playing. The decoder monitor must
|
||||
// be held.
|
||||
@ -397,7 +414,7 @@ protected:
|
||||
MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }
|
||||
MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; }
|
||||
|
||||
nsresult FinishDecodeMetadata();
|
||||
nsresult FinishDecodeFirstFrame();
|
||||
|
||||
RefPtr<MediaDataDecodedListener<MediaDecoderStateMachine>> mMediaDecodedListener;
|
||||
|
||||
@ -526,6 +543,16 @@ protected:
|
||||
// The decoder monitor must be held.
|
||||
nsresult EnqueueDecodeMetadataTask();
|
||||
|
||||
// Dispatches a LoadedMetadataEvent.
|
||||
// This is threadsafe and can be called on any thread.
|
||||
// The decoder monitor must be held.
|
||||
void EnqueueLoadedMetadataEvent();
|
||||
|
||||
// Dispatches a task to the decode task queue to begin decoding content.
|
||||
// This is threadsafe and can be called on any thread.
|
||||
// The decoder monitor must be held.
|
||||
nsresult EnqueueDecodeFirstFrameTask();
|
||||
|
||||
// Dispatches a task to the decode task queue to seek the decoder.
|
||||
// The decoder monitor must be held.
|
||||
nsresult EnqueueDecodeSeekTask();
|
||||
@ -583,9 +610,16 @@ protected:
|
||||
// Wraps the call to DecodeMetadata(), signals a DecodeError() on failure.
|
||||
void CallDecodeMetadata();
|
||||
|
||||
// Checks whether we're finished decoding metadata, and switches to DECODING
|
||||
// state if so.
|
||||
void MaybeFinishDecodeMetadata();
|
||||
// Initiate first content decoding. Called on the decode thread.
|
||||
// The decoder monitor must be held with exactly one lock count.
|
||||
nsresult DecodeFirstFrame();
|
||||
|
||||
// Wraps the call to DecodeFirstFrame(), signals a DecodeError() on failure.
|
||||
void CallDecodeFirstFrame();
|
||||
|
||||
// Checks whether we're finished decoding first audio and/or video packets,
|
||||
// and switches to DECODING state if so.
|
||||
void MaybeFinishDecodeFirstFrame();
|
||||
|
||||
// Seeks to mSeekTarget. Called on the decode thread. The decoder monitor
|
||||
// must be held with exactly one lock count.
|
||||
@ -727,6 +761,11 @@ protected:
|
||||
// this value. Accessed on main and decode thread.
|
||||
SeekTarget mSeekTarget;
|
||||
|
||||
// Position to seek to in microseconds when DecodeFirstFrame completes.
|
||||
// The decoder monitor lock must be obtained before reading or writing
|
||||
// this value. Accessed on main and decode thread.
|
||||
SeekTarget mQueuedSeekTarget;
|
||||
|
||||
// The position that we're currently seeking to. This differs from
|
||||
// mSeekTarget, as mSeekTarget is the target we'll seek to next, whereas
|
||||
// mCurrentSeekTarget is the position that the decode is in the process
|
||||
|
@ -52,9 +52,9 @@ namespace mozilla {
|
||||
NS_DispatchToMainThread(removeTracksEvent);
|
||||
|
||||
nsCOMPtr<nsIRunnable> metadataUpdatedEvent =
|
||||
new MetadataEventRunner(aDecoder,
|
||||
metadata->mInfo.forget(),
|
||||
metadata->mTags.forget());
|
||||
new MetadataUpdatedEventRunner(aDecoder,
|
||||
metadata->mInfo,
|
||||
metadata->mTags);
|
||||
NS_DispatchToMainThread(metadataUpdatedEvent);
|
||||
delete mMetadataQueue.popFirst();
|
||||
metadata = mMetadataQueue.getFirst();
|
||||
|
@ -395,10 +395,6 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo,
|
||||
NS_ENSURE_TRUE(mAudio.mDecoder != nullptr, NS_ERROR_FAILURE);
|
||||
nsresult rv = mAudio.mDecoder->Init();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Decode one audio frame to detect potentially incorrect channels count or
|
||||
// sampling rate from demuxer.
|
||||
Decode(kAudio);
|
||||
}
|
||||
|
||||
if (HasVideo()) {
|
||||
@ -431,6 +427,12 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
MP4Reader::ReadUpdatedMetadata(MediaInfo* aInfo)
|
||||
{
|
||||
*aInfo = mInfo;
|
||||
}
|
||||
|
||||
bool
|
||||
MP4Reader::IsMediaSeekable()
|
||||
{
|
||||
|
@ -45,6 +45,8 @@ public:
|
||||
virtual nsresult ReadMetadata(MediaInfo* aInfo,
|
||||
MetadataTags** aTags) MOZ_OVERRIDE;
|
||||
|
||||
virtual void ReadUpdatedMetadata(MediaInfo* aInfo) MOZ_OVERRIDE;
|
||||
|
||||
virtual void Seek(int64_t aTime,
|
||||
int64_t aStartTime,
|
||||
int64_t aEndTime,
|
||||
|
@ -411,7 +411,7 @@ GMPChild::LoadPluginLibrary(const std::string& aPluginPath)
|
||||
|
||||
// Enable sandboxing here -- we know the plugin file's path, but
|
||||
// this process's execution hasn't been affected by its content yet.
|
||||
if (mozilla::CanSandboxMediaPlugin()) {
|
||||
if (mozilla::MediaPluginSandboxStatus() != mozilla::kSandboxingWouldFail) {
|
||||
mozilla::SetMediaPluginSandbox(nativePath.get());
|
||||
} else {
|
||||
printf_stderr("GMPChild::LoadPluginLibrary: Loading media plugin %s unsandboxed.\n",
|
||||
|
@ -963,7 +963,7 @@ GMPParent::ReadGMPMetaData()
|
||||
|
||||
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
|
||||
if (cap->mAPIName.EqualsLiteral("eme-decrypt") &&
|
||||
!mozilla::CanSandboxMediaPlugin()) {
|
||||
mozilla::MediaPluginSandboxStatus() == mozilla::kSandboxingWouldFail) {
|
||||
printf_stderr("GMPParent::ReadGMPMetaData: Plugin \"%s\" is an EME CDM"
|
||||
" but this system can't sandbox it; not loading.\n",
|
||||
mDisplayName.get());
|
||||
|
@ -479,7 +479,7 @@ GeckoMediaPluginService::GetGMPDecryptor(nsTArray<nsCString>* aTags,
|
||||
GMPDecryptorProxy** aDecryptor)
|
||||
{
|
||||
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
|
||||
if (!mozilla::CanSandboxMediaPlugin()) {
|
||||
if (mozilla::MediaPluginSandboxStatus() == kSandboxingWouldFail) {
|
||||
NS_WARNING("GeckoMediaPluginService::GetGMPDecryptor: "
|
||||
"EME decryption not available without sandboxing support.");
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
|
@ -24,8 +24,8 @@ public:
|
||||
}
|
||||
virtual void FireTimeUpdate(bool aPeriodic) MOZ_OVERRIDE {}
|
||||
virtual bool GetPaused() MOZ_OVERRIDE { return false; }
|
||||
virtual void MetadataLoaded(const MediaInfo* aInfo, const MetadataTags* aTags)
|
||||
MOZ_OVERRIDE
|
||||
virtual void MetadataLoaded(const MediaInfo* aInfo,
|
||||
nsAutoPtr<const MetadataTags> aTags) MOZ_OVERRIDE
|
||||
{
|
||||
}
|
||||
virtual void NetworkError() MOZ_OVERRIDE {}
|
||||
|
@ -90,13 +90,22 @@ SourceBufferDecoder::IsMediaSeekable()
|
||||
}
|
||||
|
||||
void
|
||||
SourceBufferDecoder::MetadataLoaded(MediaInfo* aInfo, MetadataTags* aTags)
|
||||
SourceBufferDecoder::MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags)
|
||||
{
|
||||
MSE_DEBUG("SourceBufferDecoder(%p)::MetadataLoaded UNIMPLEMENTED", this);
|
||||
}
|
||||
|
||||
void
|
||||
SourceBufferDecoder::QueueMetadata(int64_t aTime, MediaInfo* aInfo, MetadataTags* aTags)
|
||||
SourceBufferDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo)
|
||||
{
|
||||
MSE_DEBUG("SourceBufferDecoder(%p)::FirstFrameLoaded UNIMPLEMENTED", this);
|
||||
}
|
||||
|
||||
void
|
||||
SourceBufferDecoder::QueueMetadata(int64_t aTime,
|
||||
nsAutoPtr<MediaInfo> aInfo,
|
||||
nsAutoPtr<MetadataTags> aTags)
|
||||
{
|
||||
MSE_DEBUG("SourceBufferDecoder(%p)::QueueMetadata UNIMPLEMENTED", this);
|
||||
}
|
||||
|
@ -47,13 +47,14 @@ public:
|
||||
virtual SourceBufferResource* GetResource() const MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual ReentrantMonitor& GetReentrantMonitor() MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void MetadataLoaded(MediaInfo* aInfo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void NotifyWaitingForResourcesStatusChanged() MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void OnReadMetadataCompleted() MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void QueueMetadata(int64_t aTime, MediaInfo* aInfo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void RemoveMediaTracks() MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void SetMediaDuration(int64_t aDuration) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void SetMediaEndTime(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE;
|
||||
|
@ -10,9 +10,11 @@ support-files =
|
||||
[test_BufferedSeek.html]
|
||||
[test_FrameSelection.html]
|
||||
[test_HaveMetadataUnbufferedSeek.html]
|
||||
[test_LoadedMetadataFired.html]
|
||||
[test_SeekableAfterEndOfStream.html]
|
||||
[test_SeekableAfterEndOfStreamSplit.html]
|
||||
[test_SeekableBeforeEndOfStream.html]
|
||||
[test_SeekableBeforeEndOfStreamSplit.html]
|
||||
[test_SplitAppendDelay.html]
|
||||
[test_SplitAppend.html]
|
||||
|
||||
|
35
dom/media/mediasource/test/test_LoadedMetadataFired.html
Normal file
35
dom/media/mediasource/test/test_LoadedMetadataFired.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>MSE: append initialization only</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="mediasource.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
runWithMSE(function (ms, v) {
|
||||
ms.addEventListener("sourceopen", function () {
|
||||
var sb = ms.addSourceBuffer("video/webm");
|
||||
|
||||
v.addEventListener("loadedmetadata", function () {
|
||||
ok(true, "Got loadedmetadata event");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
fetchWithXHR("seek.webm", function (arrayBuffer) {
|
||||
sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 318));
|
||||
v.play();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -753,7 +753,7 @@ bool OggReader::ReadOggChain()
|
||||
OpusState* newOpusState = nullptr;
|
||||
#endif /* MOZ_OPUS */
|
||||
VorbisState* newVorbisState = nullptr;
|
||||
MetadataTags* tags = nullptr;
|
||||
nsAutoPtr<MetadataTags> tags;
|
||||
|
||||
if (HasVideo() || HasSkeleton() || !HasAudio()) {
|
||||
return false;
|
||||
@ -846,7 +846,7 @@ bool OggReader::ReadOggChain()
|
||||
*info = mInfo;
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDecoder->QueueMetadata((mDecodedAudioFrames * USECS_PER_S) / mInfo.mAudio.mRate,
|
||||
info.forget(), tags);
|
||||
info, tags);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -57,11 +57,10 @@ MediaOmxCommonDecoder::CheckDecoderCanOffloadAudio()
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::MetadataLoaded(MediaInfo* aInfo,
|
||||
MetadataTags* aTags)
|
||||
MediaOmxCommonDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MediaDecoder::MetadataLoaded(aInfo, aTags);
|
||||
MediaDecoder::FirstFrameLoaded(aInfo);
|
||||
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
if (!CheckDecoderCanOffloadAudio()) {
|
||||
|
@ -23,8 +23,7 @@ class MediaOmxCommonDecoder : public MediaDecoder
|
||||
public:
|
||||
MediaOmxCommonDecoder();
|
||||
|
||||
virtual void MetadataLoaded(MediaInfo* aInfo,
|
||||
MetadataTags* aTags);
|
||||
virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo);
|
||||
virtual void ChangeState(PlayState aState);
|
||||
virtual void ApplyStateToStateMachine(PlayState aState);
|
||||
virtual void SetVolume(double aVolume);
|
||||
|
@ -139,8 +139,8 @@ function PlayFragmented(test, elem, token)
|
||||
sb.appendBuffer(new Uint8Array(req.response));
|
||||
});
|
||||
|
||||
req.addEventListener("error", bail(token + " error fetching " + fragmentFile));
|
||||
req.addEventListener("abort", bail(token + " aborted fetching " + fragmentFile));
|
||||
req.addEventListener("error", function(){info(token + " error fetching " + fragmentFile);});
|
||||
req.addEventListener("abort", function(){info(token + " aborted fetching " + fragmentFile);});
|
||||
|
||||
Log(token, "addNextFragment() fetching next fragment " + fragmentFile);
|
||||
req.send(null);
|
||||
|
@ -26,7 +26,7 @@ function finish(v) {
|
||||
manager.finished(v.token);
|
||||
}
|
||||
|
||||
function onLoadedMetadata_Audio(e) {
|
||||
function onLoadedData_Audio(e) {
|
||||
var t = e.target;
|
||||
is(t.videoHeight, 0, t.name + ": videoHeight should be zero when there is no video.");
|
||||
is(t.videoWidth, 0, t.name + ": videoWidth should be zero when there is no video.");
|
||||
@ -62,14 +62,14 @@ function onTimeUpdate_Video(e) {
|
||||
todo("No audio file available.")
|
||||
finish(t);
|
||||
} else {
|
||||
t.removeEventListener("loadedmetadata", onLoadedMetadata_Video);
|
||||
t.addEventListener("loadedmetadata", onLoadedMetadata_Audio);
|
||||
t.removeEventListener("loadeddata", onLoadedData_Video);
|
||||
t.addEventListener("loadeddata", onLoadedData_Audio);
|
||||
t.src = source.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onLoadedMetadata_Video(e) {
|
||||
function onLoadedData_Video(e) {
|
||||
var t = e.target;
|
||||
isnot(t.videoHeight, 0, t.name + ": We should have a videoHeight.");
|
||||
isnot(t.videoWidth, 0, t.name + ": We should have a videoWidth.");
|
||||
@ -80,12 +80,12 @@ function onLoadedMetadata_Video(e) {
|
||||
function startTest(test, token) {
|
||||
var v = document.createElement('video');
|
||||
document.body.appendChild(v);
|
||||
v.preload = "metadata";
|
||||
v._firstTime = true;
|
||||
v.addEventListener("loadedmetadata", onLoadedMetadata_Video);
|
||||
v.addEventListener("loadeddata", onLoadedData_Video);
|
||||
v.src = test.name;
|
||||
v.token = token;
|
||||
v.name = test.name;
|
||||
v.play();
|
||||
manager.started(token);
|
||||
}
|
||||
|
||||
|
@ -140,13 +140,19 @@ BufferDecoder::IsMediaSeekable()
|
||||
}
|
||||
|
||||
void
|
||||
BufferDecoder::MetadataLoaded(MediaInfo* aInfo, MetadataTags* aTags)
|
||||
BufferDecoder::MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
void
|
||||
BufferDecoder::QueueMetadata(int64_t aTime, MediaInfo* aInfo, MetadataTags* aTags)
|
||||
BufferDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
void
|
||||
BufferDecoder::QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
@ -58,8 +58,9 @@ public:
|
||||
|
||||
virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE;
|
||||
|
||||
virtual void MetadataLoaded(MediaInfo* aInfo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void QueueMetadata(int64_t aTime, MediaInfo* aInfo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void QueueMetadata(int64_t aTime, nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) MOZ_FINAL MOZ_OVERRIDE;
|
||||
virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo) MOZ_FINAL MOZ_OVERRIDE;
|
||||
|
||||
virtual void RemoveMediaTracks() MOZ_FINAL MOZ_OVERRIDE;
|
||||
|
||||
|
@ -19,8 +19,11 @@
|
||||
SimpleTest.expectChildProcessCrash();
|
||||
|
||||
crashAndGetCrashServiceRecord("crash", function (cm, crash) {
|
||||
ok(crash.isOfType(cm.PROCESS_TYPE_PLUGIN, cm.CRASH_TYPE_CRASH),
|
||||
"Record should be a plugin crash");
|
||||
var isPluginCrash = crash.isOfType(cm.PROCESS_TYPE_PLUGIN, cm.CRASH_TYPE_CRASH);
|
||||
ok(isPluginCrash, "Record should be a plugin crash");
|
||||
if (!isPluginCrash) {
|
||||
dump("Crash type: " + crash.type + "\n");
|
||||
}
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
|
@ -15,12 +15,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1027221
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var x = "x";
|
||||
// Trigger some incremental gc
|
||||
SpecialPowers.Cu.getJSTestingFunctions().gcslice(0);
|
||||
SpecialPowers.Cu.getJSTestingFunctions().gcslice(1);
|
||||
|
||||
// Kick off a worker that uses this same atom
|
||||
var w = new Worker("data:text/plain,Promise.resolve('x').then(function() { postMessage(1); });");
|
||||
// Maybe trigger some more incremental gc
|
||||
SpecialPowers.Cu.getJSTestingFunctions().gcslice(0);
|
||||
SpecialPowers.Cu.getJSTestingFunctions().gcslice(1);
|
||||
|
||||
w.onmessage = function() {
|
||||
ok(true, "Got here");
|
||||
|
@ -37,6 +37,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// 2^23
|
||||
#define CAIRO_COORD_MAX (Float(0x7fffff))
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCairoSurface, cairo_surface_t, cairo_surface_destroy);
|
||||
@ -90,6 +93,64 @@ private:
|
||||
cairo_t* mCtx;
|
||||
};
|
||||
|
||||
/* Clamp r to (0,0) (2^23,2^23)
|
||||
* these are to be device coordinates.
|
||||
*
|
||||
* Returns false if the rectangle is completely out of bounds,
|
||||
* true otherwise.
|
||||
*
|
||||
* This function assumes that it will be called with a rectangle being
|
||||
* drawn into a surface with an identity transformation matrix; that
|
||||
* is, anything above or to the left of (0,0) will be offscreen.
|
||||
*
|
||||
* First it checks if the rectangle is entirely beyond
|
||||
* CAIRO_COORD_MAX; if so, it can't ever appear on the screen --
|
||||
* false is returned.
|
||||
*
|
||||
* Then it shifts any rectangles with x/y < 0 so that x and y are = 0,
|
||||
* and adjusts the width and height appropriately. For example, a
|
||||
* rectangle from (0,-5) with dimensions (5,10) will become a
|
||||
* rectangle from (0,0) with dimensions (5,5).
|
||||
*
|
||||
* If after negative x/y adjustment to 0, either the width or height
|
||||
* is negative, then the rectangle is completely offscreen, and
|
||||
* nothing is drawn -- false is returned.
|
||||
*
|
||||
* Finally, if x+width or y+height are greater than CAIRO_COORD_MAX,
|
||||
* the width and height are clamped such x+width or y+height are equal
|
||||
* to CAIRO_COORD_MAX, and true is returned.
|
||||
*/
|
||||
static bool
|
||||
ConditionRect(Rect& r) {
|
||||
// if either x or y is way out of bounds;
|
||||
// note that we don't handle negative w/h here
|
||||
if (r.X() > CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX)
|
||||
return false;
|
||||
|
||||
if (r.X() < 0.f) {
|
||||
r.width += r.X();
|
||||
if (r.width < 0.f)
|
||||
return false;
|
||||
r.x = 0.f;
|
||||
}
|
||||
|
||||
if (r.XMost() > CAIRO_COORD_MAX) {
|
||||
r.width = CAIRO_COORD_MAX - r.X();
|
||||
}
|
||||
|
||||
if (r.Y() < 0.f) {
|
||||
r.height += r.Y();
|
||||
if (r.Height() < 0.f)
|
||||
return false;
|
||||
|
||||
r.y = 0.f;
|
||||
}
|
||||
|
||||
if (r.YMost() > CAIRO_COORD_MAX) {
|
||||
r.height = CAIRO_COORD_MAX - r.Y();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
@ -856,16 +917,50 @@ DrawTargetCairo::FillRect(const Rect &aRect,
|
||||
{
|
||||
AutoPrepareForDrawing prep(this, mContext);
|
||||
|
||||
bool restoreTransform = false;
|
||||
Matrix mat;
|
||||
Rect r = aRect;
|
||||
|
||||
/* Clamp coordinates to work around a design bug in cairo */
|
||||
if (r.width > CAIRO_COORD_MAX ||
|
||||
r.height > CAIRO_COORD_MAX ||
|
||||
r.x < -CAIRO_COORD_MAX ||
|
||||
r.x > CAIRO_COORD_MAX ||
|
||||
r.y < -CAIRO_COORD_MAX ||
|
||||
r.y > CAIRO_COORD_MAX)
|
||||
{
|
||||
if (!mat.IsRectilinear()) {
|
||||
gfxWarning() << "DrawTargetCairo::FillRect() misdrawing huge Rect "
|
||||
"with non-rectilinear transform";
|
||||
}
|
||||
|
||||
mat = GetTransform();
|
||||
r = mat.TransformBounds(r);
|
||||
|
||||
if (!ConditionRect(r)) {
|
||||
gfxWarning() << "Ignoring DrawTargetCairo::FillRect() call with "
|
||||
"out-of-bounds Rect";
|
||||
return;
|
||||
}
|
||||
|
||||
restoreTransform = true;
|
||||
SetTransform(Matrix());
|
||||
}
|
||||
|
||||
cairo_new_path(mContext);
|
||||
cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height());
|
||||
cairo_rectangle(mContext, r.x, r.y, r.Width(), r.Height());
|
||||
|
||||
bool pathBoundsClip = false;
|
||||
|
||||
if (aRect.Contains(GetUserSpaceClip())) {
|
||||
if (r.Contains(GetUserSpaceClip())) {
|
||||
pathBoundsClip = true;
|
||||
}
|
||||
|
||||
DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL, pathBoundsClip);
|
||||
|
||||
if (restoreTransform) {
|
||||
SetTransform(mat);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -31,7 +31,6 @@
|
||||
#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE
|
||||
#include "nsIWidget.h" // for nsIWidget, NS_NATIVE_WINDOW
|
||||
#include "nsRect.h" // for nsRect
|
||||
#include "nsRenderingContext.h" // for nsRenderingContext
|
||||
#include "nsServiceManagerUtils.h" // for do_GetService
|
||||
#include "nsString.h" // for nsDependentString
|
||||
#include "nsTArray.h" // for nsTArray, nsTArray_Impl
|
||||
|
@ -19,7 +19,7 @@ interface nsIPrincipal;
|
||||
* @version 0.1
|
||||
* @see imagelib2
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(078f159c-28ee-4653-9cfe-633132152f47)]
|
||||
[scriptable, builtinclass, uuid(710f22f0-558b-11e4-8ed6-0800200c9a66)]
|
||||
interface imgIRequest : nsIRequest
|
||||
{
|
||||
/**
|
||||
@ -42,14 +42,6 @@ interface imgIRequest : nsIRequest
|
||||
* and height of the image, and have thus called SetSize()
|
||||
* on the container.
|
||||
*
|
||||
* STATUS_LOAD_PARTIAL: Used internally by imgRequest to
|
||||
* flag that a request is being cancelled as a result of
|
||||
* a failure of a proxy holder and not an internal failure.
|
||||
* At least I think that's what it does. Regardless, there's
|
||||
* no reason for this flag to be public, and it should either
|
||||
* go away or become a private state flag within imgRequest.
|
||||
* Don't rely on it.
|
||||
*
|
||||
* STATUS_LOAD_COMPLETE: The data has been fully loaded
|
||||
* to memory, but not necessarily fully decoded.
|
||||
*
|
||||
@ -66,12 +58,11 @@ interface imgIRequest : nsIRequest
|
||||
//@{
|
||||
const long STATUS_NONE = 0x0;
|
||||
const long STATUS_SIZE_AVAILABLE = 0x1;
|
||||
const long STATUS_LOAD_PARTIAL = 0x2;
|
||||
const long STATUS_LOAD_COMPLETE = 0x4;
|
||||
const long STATUS_ERROR = 0x8;
|
||||
const long STATUS_DECODE_STARTED = 0x10;
|
||||
const long STATUS_FRAME_COMPLETE = 0x20;
|
||||
const long STATUS_DECODE_COMPLETE = 0x40;
|
||||
const long STATUS_LOAD_COMPLETE = 0x2;
|
||||
const long STATUS_ERROR = 0x4;
|
||||
const long STATUS_DECODE_STARTED = 0x8;
|
||||
const long STATUS_FRAME_COMPLETE = 0x10;
|
||||
const long STATUS_DECODE_COMPLETE = 0x20;
|
||||
//@}
|
||||
|
||||
/**
|
||||
|
@ -109,13 +109,6 @@ public:
|
||||
*/
|
||||
virtual void OnStopRequest(bool aIsLastPart, nsresult aStatus) = 0;
|
||||
|
||||
/**
|
||||
* Called when the decoded image data is discarded. This means that the frames
|
||||
* no longer exist in decoded form, and any attempt to access or draw the
|
||||
* image will initiate a new series of progressive decode notifications.
|
||||
*/
|
||||
virtual void OnDiscard() = 0;
|
||||
|
||||
/**
|
||||
* Called when we are asked to Draw an image that is not locked.
|
||||
*/
|
||||
|
@ -107,14 +107,6 @@ public:
|
||||
tracker->RecordStopRequest(aLastPart, aStatus);
|
||||
}
|
||||
|
||||
virtual void OnDiscard() MOZ_OVERRIDE
|
||||
{
|
||||
LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnDiscard");
|
||||
nsRefPtr<imgStatusTracker> tracker = mTracker.get();
|
||||
if (!tracker) { return; }
|
||||
tracker->RecordDiscard();
|
||||
}
|
||||
|
||||
virtual void OnUnlockedDraw() MOZ_OVERRIDE
|
||||
{
|
||||
LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnUnlockedDraw");
|
||||
@ -151,24 +143,16 @@ private:
|
||||
// imgStatusTracker methods
|
||||
|
||||
imgStatusTracker::imgStatusTracker(Image* aImage)
|
||||
: mImage(aImage),
|
||||
mState(0),
|
||||
mImageStatus(imgIRequest::STATUS_NONE),
|
||||
mIsMultipart(false),
|
||||
mHadLastPart(false),
|
||||
mHasBeenDecoded(false)
|
||||
: mImage(aImage)
|
||||
, mState(0)
|
||||
{
|
||||
mTrackerObserver = new imgStatusTrackerObserver(this);
|
||||
}
|
||||
|
||||
// Private, used only by CloneForRecording.
|
||||
imgStatusTracker::imgStatusTracker(const imgStatusTracker& aOther)
|
||||
: mImage(aOther.mImage),
|
||||
mState(aOther.mState),
|
||||
mImageStatus(aOther.mImageStatus),
|
||||
mIsMultipart(aOther.mIsMultipart),
|
||||
mHadLastPart(aOther.mHadLastPart),
|
||||
mHasBeenDecoded(aOther.mHasBeenDecoded)
|
||||
: mImage(aOther.mImage)
|
||||
, mState(aOther.mState)
|
||||
// Note: we explicitly don't copy several fields:
|
||||
// - mRequestRunnable, because it won't be nulled out when the
|
||||
// mRequestRunnable's Run function eventually gets called.
|
||||
@ -218,19 +202,46 @@ imgStatusTracker::ResetImage()
|
||||
mImage = nullptr;
|
||||
}
|
||||
|
||||
void imgStatusTracker::SetIsMultipart()
|
||||
{
|
||||
mState |= FLAG_IS_MULTIPART;
|
||||
}
|
||||
|
||||
bool
|
||||
imgStatusTracker::IsLoading() const
|
||||
{
|
||||
// Checking for whether OnStopRequest has fired allows us to say we're
|
||||
// loading before OnStartRequest gets called, letting the request properly
|
||||
// get removed from the cache in certain cases.
|
||||
return !(mState & stateRequestStopped);
|
||||
return !(mState & FLAG_REQUEST_STOPPED);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
imgStatusTracker::GetImageStatus() const
|
||||
{
|
||||
return mImageStatus;
|
||||
uint32_t status = imgIRequest::STATUS_NONE;
|
||||
|
||||
// Translate our current state to a set of imgIRequest::STATE_* flags.
|
||||
if (mState & FLAG_HAS_SIZE) {
|
||||
status |= imgIRequest::STATUS_SIZE_AVAILABLE;
|
||||
}
|
||||
if (mState & FLAG_DECODE_STARTED) {
|
||||
status |= imgIRequest::STATUS_DECODE_STARTED;
|
||||
}
|
||||
if (mState & FLAG_DECODE_STOPPED) {
|
||||
status |= imgIRequest::STATUS_DECODE_COMPLETE;
|
||||
}
|
||||
if (mState & FLAG_FRAME_STOPPED) {
|
||||
status |= imgIRequest::STATUS_FRAME_COMPLETE;
|
||||
}
|
||||
if (mState & FLAG_REQUEST_STOPPED) {
|
||||
status |= imgIRequest::STATUS_LOAD_COMPLETE;
|
||||
}
|
||||
if (mState & FLAG_HAS_ERROR) {
|
||||
status |= imgIRequest::STATUS_ERROR;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// A helper class to allow us to call SyncNotify asynchronously.
|
||||
@ -371,23 +382,23 @@ imgStatusTracker::NotifyCurrentState(imgRequestProxy* proxy)
|
||||
/* static */ void
|
||||
imgStatusTracker::SyncNotifyState(ProxyArray& proxies,
|
||||
bool hasImage, uint32_t state,
|
||||
nsIntRect& dirtyRect, bool hadLastPart)
|
||||
nsIntRect& dirtyRect)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// OnStartRequest
|
||||
if (state & stateRequestStarted)
|
||||
if (state & FLAG_REQUEST_STARTED)
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStartRequest());
|
||||
|
||||
// OnStartContainer
|
||||
if (state & stateHasSize)
|
||||
if (state & FLAG_HAS_SIZE)
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStartContainer());
|
||||
|
||||
// OnStartDecode
|
||||
if (state & stateDecodeStarted)
|
||||
if (state & FLAG_DECODE_STARTED)
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStartDecode());
|
||||
|
||||
// BlockOnload
|
||||
if (state & stateBlockingOnload)
|
||||
if (state & FLAG_ONLOAD_BLOCKED)
|
||||
NOTIFY_IMAGE_OBSERVERS(BlockOnload());
|
||||
|
||||
if (hasImage) {
|
||||
@ -398,21 +409,28 @@ imgStatusTracker::SyncNotifyState(ProxyArray& proxies,
|
||||
if (!dirtyRect.IsEmpty())
|
||||
NOTIFY_IMAGE_OBSERVERS(OnFrameUpdate(&dirtyRect));
|
||||
|
||||
if (state & stateFrameStopped)
|
||||
if (state & FLAG_FRAME_STOPPED)
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStopFrame());
|
||||
|
||||
// OnImageIsAnimated
|
||||
if (state & stateImageIsAnimated)
|
||||
if (state & FLAG_IS_ANIMATED)
|
||||
NOTIFY_IMAGE_OBSERVERS(OnImageIsAnimated());
|
||||
}
|
||||
|
||||
if (state & stateDecodeStopped) {
|
||||
// Send UnblockOnload before OnStopDecode and OnStopRequest. This allows
|
||||
// observers that can fire events when they receive those notifications to do
|
||||
// so then, instead of being forced to wait for UnblockOnload.
|
||||
if (state & FLAG_ONLOAD_UNBLOCKED) {
|
||||
NOTIFY_IMAGE_OBSERVERS(UnblockOnload());
|
||||
}
|
||||
|
||||
if (state & FLAG_DECODE_STOPPED) {
|
||||
NS_ABORT_IF_FALSE(hasImage, "stopped decoding without ever having an image?");
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStopDecode());
|
||||
}
|
||||
|
||||
if (state & stateRequestStopped) {
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStopRequest(hadLastPart));
|
||||
if (state & FLAG_REQUEST_STOPPED) {
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStopRequest(state & FLAG_MULTIPART_STOPPED));
|
||||
}
|
||||
}
|
||||
|
||||
@ -421,27 +439,15 @@ imgStatusTracker::Difference(imgStatusTracker* aOther) const
|
||||
{
|
||||
MOZ_ASSERT(aOther, "aOther cannot be null");
|
||||
ImageStatusDiff diff;
|
||||
diff.diffState = ~mState & aOther->mState & ~stateRequestStarted;
|
||||
diff.diffImageStatus = ~mImageStatus & aOther->mImageStatus;
|
||||
diff.unblockedOnload = mState & stateBlockingOnload && !(aOther->mState & stateBlockingOnload);
|
||||
diff.unsetDecodeStarted = mImageStatus & imgIRequest::STATUS_DECODE_STARTED
|
||||
&& !(aOther->mImageStatus & imgIRequest::STATUS_DECODE_STARTED);
|
||||
diff.foundError = (mImageStatus != imgIRequest::STATUS_ERROR)
|
||||
&& (aOther->mImageStatus == imgIRequest::STATUS_ERROR);
|
||||
|
||||
MOZ_ASSERT(!mIsMultipart || aOther->mIsMultipart, "mIsMultipart should be monotonic");
|
||||
diff.foundIsMultipart = !mIsMultipart && aOther->mIsMultipart;
|
||||
diff.foundLastPart = !mHadLastPart && aOther->mHadLastPart;
|
||||
|
||||
diff.gotDecoded = !mHasBeenDecoded && aOther->mHasBeenDecoded;
|
||||
diff.diffState = ~mState & aOther->mState;
|
||||
|
||||
// Only record partial invalidations if we haven't been decoded before.
|
||||
// When images are re-decoded after discarding, we don't want to display
|
||||
// partially decoded versions to the user.
|
||||
const uint32_t combinedStatus = mImageStatus | aOther->mImageStatus;
|
||||
const bool doInvalidations = !(mHasBeenDecoded || aOther->mHasBeenDecoded)
|
||||
|| combinedStatus & imgIRequest::STATUS_ERROR
|
||||
|| combinedStatus & imgIRequest::STATUS_DECODE_COMPLETE;
|
||||
const uint32_t combinedState = mState | aOther->mState;
|
||||
const bool doInvalidations = !(mState & FLAG_DECODE_STOPPED) ||
|
||||
aOther->mState & FLAG_DECODE_STOPPED ||
|
||||
combinedState & FLAG_HAS_ERROR;
|
||||
|
||||
// Record and reset the invalid rectangle.
|
||||
// XXX(seth): We shouldn't be resetting anything here; see bug 910441.
|
||||
@ -457,11 +463,8 @@ ImageStatusDiff
|
||||
imgStatusTracker::DecodeStateAsDifference() const
|
||||
{
|
||||
ImageStatusDiff diff;
|
||||
diff.diffState = mState & ~stateRequestStarted;
|
||||
|
||||
// All other ImageStatusDiff fields are intentionally left at their default
|
||||
// values; we only want to notify decode state changes.
|
||||
|
||||
// XXX(seth): Is FLAG_REQUEST_STARTED really the only non-"decode state" flag?
|
||||
diff.diffState = mState & ~FLAG_REQUEST_STARTED;
|
||||
return diff;
|
||||
}
|
||||
|
||||
@ -469,29 +472,7 @@ void
|
||||
imgStatusTracker::ApplyDifference(const ImageStatusDiff& aDiff)
|
||||
{
|
||||
LOG_SCOPE(GetImgLog(), "imgStatusTracker::ApplyDifference");
|
||||
|
||||
// We must not modify or notify for the start-load state, which happens from Necko callbacks.
|
||||
uint32_t loadState = mState & stateRequestStarted;
|
||||
|
||||
// Synchronize our state.
|
||||
mState |= aDiff.diffState | loadState;
|
||||
if (aDiff.unblockedOnload)
|
||||
mState &= ~stateBlockingOnload;
|
||||
|
||||
mIsMultipart = mIsMultipart || aDiff.foundIsMultipart;
|
||||
mHadLastPart = mHadLastPart || aDiff.foundLastPart;
|
||||
mHasBeenDecoded = mHasBeenDecoded || aDiff.gotDecoded;
|
||||
|
||||
// Update the image status. There are some subtle points which are handled below.
|
||||
mImageStatus |= aDiff.diffImageStatus;
|
||||
|
||||
// Unset bits which can get unset as part of the decoding process.
|
||||
if (aDiff.unsetDecodeStarted)
|
||||
mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED;
|
||||
|
||||
// The error state is sticky and overrides all other bits.
|
||||
if (mImageStatus & imgIRequest::STATUS_ERROR)
|
||||
mImageStatus = imgIRequest::STATUS_ERROR;
|
||||
mState |= aDiff.diffState;
|
||||
}
|
||||
|
||||
void
|
||||
@ -502,24 +483,11 @@ imgStatusTracker::SyncNotifyDifference(const ImageStatusDiff& diff)
|
||||
|
||||
nsIntRect invalidRect = mInvalidRect.Union(diff.invalidRect);
|
||||
|
||||
SyncNotifyState(mConsumers, !!mImage, diff.diffState, invalidRect, mHadLastPart);
|
||||
SyncNotifyState(mConsumers, !!mImage, diff.diffState, invalidRect);
|
||||
|
||||
mInvalidRect.SetEmpty();
|
||||
|
||||
if (diff.unblockedOnload) {
|
||||
ProxyArray::ForwardIterator iter(mConsumers);
|
||||
while (iter.HasMore()) {
|
||||
// Hold on to a reference to this proxy, since notifying the state can
|
||||
// cause it to disappear.
|
||||
nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
|
||||
|
||||
if (proxy && !proxy->NotificationsDeferred()) {
|
||||
SendUnblockOnload(proxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (diff.foundError) {
|
||||
if (diff.diffState & FLAG_HAS_ERROR) {
|
||||
FireFailureNotification();
|
||||
}
|
||||
}
|
||||
@ -554,7 +522,7 @@ imgStatusTracker::SyncNotify(imgRequestProxy* proxy)
|
||||
|
||||
ProxyArray array;
|
||||
array.AppendElement(proxy);
|
||||
SyncNotifyState(array, !!mImage, mState, r, mHadLastPart);
|
||||
SyncNotifyState(array, !!mImage, mState, r);
|
||||
}
|
||||
|
||||
void
|
||||
@ -567,15 +535,15 @@ imgStatusTracker::EmulateRequestFinished(imgRequestProxy* aProxy,
|
||||
|
||||
// In certain cases the request might not have started yet.
|
||||
// We still need to fulfill the contract.
|
||||
if (!(mState & stateRequestStarted)) {
|
||||
if (!(mState & FLAG_REQUEST_STARTED)) {
|
||||
aProxy->OnStartRequest();
|
||||
}
|
||||
|
||||
if (mState & stateBlockingOnload) {
|
||||
if (mState & FLAG_ONLOAD_BLOCKED && !(mState & FLAG_ONLOAD_UNBLOCKED)) {
|
||||
aProxy->UnblockOnload();
|
||||
}
|
||||
|
||||
if (!(mState & stateRequestStopped)) {
|
||||
if (!(mState & FLAG_REQUEST_STOPPED)) {
|
||||
aProxy->OnStopRequest(true);
|
||||
}
|
||||
}
|
||||
@ -629,34 +597,29 @@ imgStatusTracker::FirstConsumerIs(imgRequestProxy* aConsumer)
|
||||
void
|
||||
imgStatusTracker::RecordCancel()
|
||||
{
|
||||
if (!(mImageStatus & imgIRequest::STATUS_LOAD_PARTIAL))
|
||||
mImageStatus = imgIRequest::STATUS_ERROR;
|
||||
mState |= FLAG_HAS_ERROR;
|
||||
}
|
||||
|
||||
void
|
||||
imgStatusTracker::RecordLoaded()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mImage, "RecordLoaded called before we have an Image");
|
||||
mState |= stateRequestStarted | stateHasSize | stateRequestStopped;
|
||||
mImageStatus |= imgIRequest::STATUS_SIZE_AVAILABLE | imgIRequest::STATUS_LOAD_COMPLETE;
|
||||
mHadLastPart = true;
|
||||
mState |= FLAG_REQUEST_STARTED | FLAG_HAS_SIZE |
|
||||
FLAG_REQUEST_STOPPED | FLAG_MULTIPART_STOPPED;
|
||||
}
|
||||
|
||||
void
|
||||
imgStatusTracker::RecordDecoded()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mImage, "RecordDecoded called before we have an Image");
|
||||
mState |= stateDecodeStarted | stateDecodeStopped | stateFrameStopped;
|
||||
mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE | imgIRequest::STATUS_DECODE_COMPLETE;
|
||||
mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED;
|
||||
mState |= FLAG_DECODE_STARTED | FLAG_DECODE_STOPPED | FLAG_FRAME_STOPPED;
|
||||
}
|
||||
|
||||
void
|
||||
imgStatusTracker::RecordStartDecode()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mImage, "RecordStartDecode without an Image");
|
||||
mState |= stateDecodeStarted;
|
||||
mImageStatus |= imgIRequest::STATUS_DECODE_STARTED;
|
||||
mState |= FLAG_DECODE_STARTED;
|
||||
}
|
||||
|
||||
void
|
||||
@ -674,8 +637,7 @@ imgStatusTracker::RecordStartContainer(imgIContainer* aContainer)
|
||||
"RecordStartContainer called before we have an Image");
|
||||
NS_ABORT_IF_FALSE(mImage == aContainer,
|
||||
"RecordStartContainer called with wrong Image");
|
||||
mState |= stateHasSize;
|
||||
mImageStatus |= imgIRequest::STATUS_SIZE_AVAILABLE;
|
||||
mState |= FLAG_HAS_SIZE;
|
||||
}
|
||||
|
||||
void
|
||||
@ -698,8 +660,7 @@ void
|
||||
imgStatusTracker::RecordStopFrame()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mImage, "RecordStopFrame called before we have an Image");
|
||||
mState |= stateFrameStopped;
|
||||
mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE;
|
||||
mState |= FLAG_FRAME_STOPPED;
|
||||
}
|
||||
|
||||
void
|
||||
@ -713,17 +674,12 @@ imgStatusTracker::SendStopFrame(imgRequestProxy* aProxy)
|
||||
void
|
||||
imgStatusTracker::RecordStopDecode(nsresult aStatus)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mImage,
|
||||
"RecordStopDecode called before we have an Image");
|
||||
mState |= stateDecodeStopped;
|
||||
MOZ_ASSERT(mImage, "RecordStopDecode called before we have an Image");
|
||||
|
||||
if (NS_SUCCEEDED(aStatus) && mImageStatus != imgIRequest::STATUS_ERROR) {
|
||||
mImageStatus |= imgIRequest::STATUS_DECODE_COMPLETE;
|
||||
mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED;
|
||||
mHasBeenDecoded = true;
|
||||
// If we weren't successful, clear all success status bits and set error.
|
||||
} else {
|
||||
mImageStatus = imgIRequest::STATUS_ERROR;
|
||||
mState |= FLAG_DECODE_STOPPED;
|
||||
|
||||
if (NS_FAILED(aStatus)) {
|
||||
mState |= FLAG_HAS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@ -736,22 +692,6 @@ imgStatusTracker::SendStopDecode(imgRequestProxy* aProxy,
|
||||
aProxy->OnStopDecode();
|
||||
}
|
||||
|
||||
void
|
||||
imgStatusTracker::RecordDiscard()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mImage,
|
||||
"RecordDiscard called before we have an Image");
|
||||
// Clear the state bits we no longer deserve.
|
||||
uint32_t stateBitsToClear = stateDecodeStopped;
|
||||
mState &= ~stateBitsToClear;
|
||||
|
||||
// Clear the status bits we no longer deserve.
|
||||
uint32_t statusBitsToClear = imgIRequest::STATUS_DECODE_STARTED |
|
||||
imgIRequest::STATUS_FRAME_COMPLETE |
|
||||
imgIRequest::STATUS_DECODE_COMPLETE;
|
||||
mImageStatus &= ~statusBitsToClear;
|
||||
}
|
||||
|
||||
void
|
||||
imgStatusTracker::SendDiscard(imgRequestProxy* aProxy)
|
||||
{
|
||||
@ -773,7 +713,7 @@ imgStatusTracker::RecordImageIsAnimated()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mImage,
|
||||
"RecordImageIsAnimated called before we have an Image");
|
||||
mState |= stateImageIsAnimated;
|
||||
mState |= FLAG_IS_ANIMATED;
|
||||
}
|
||||
|
||||
void
|
||||
@ -828,20 +768,17 @@ void
|
||||
imgStatusTracker::RecordStartRequest()
|
||||
{
|
||||
// We're starting a new load, so clear any status and state bits indicating
|
||||
// load/decode
|
||||
mImageStatus &= ~imgIRequest::STATUS_LOAD_PARTIAL;
|
||||
mImageStatus &= ~imgIRequest::STATUS_LOAD_COMPLETE;
|
||||
mImageStatus &= ~imgIRequest::STATUS_FRAME_COMPLETE;
|
||||
mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED;
|
||||
mImageStatus &= ~imgIRequest::STATUS_DECODE_COMPLETE;
|
||||
mState &= ~stateRequestStarted;
|
||||
mState &= ~stateDecodeStarted;
|
||||
mState &= ~stateDecodeStopped;
|
||||
mState &= ~stateRequestStopped;
|
||||
mState &= ~stateBlockingOnload;
|
||||
mState &= ~stateImageIsAnimated;
|
||||
// load/decode.
|
||||
// XXX(seth): Are these really the only flags we want to clear?
|
||||
mState &= ~FLAG_REQUEST_STARTED;
|
||||
mState &= ~FLAG_DECODE_STARTED;
|
||||
mState &= ~FLAG_DECODE_STOPPED;
|
||||
mState &= ~FLAG_REQUEST_STOPPED;
|
||||
mState &= ~FLAG_ONLOAD_BLOCKED;
|
||||
mState &= ~FLAG_ONLOAD_UNBLOCKED;
|
||||
mState &= ~FLAG_IS_ANIMATED;
|
||||
|
||||
mState |= stateRequestStarted;
|
||||
mState |= FLAG_REQUEST_STARTED;
|
||||
}
|
||||
|
||||
void
|
||||
@ -870,14 +807,13 @@ void
|
||||
imgStatusTracker::RecordStopRequest(bool aLastPart,
|
||||
nsresult aStatus)
|
||||
{
|
||||
mHadLastPart = aLastPart;
|
||||
mState |= stateRequestStopped;
|
||||
|
||||
// If we were successful in loading, note that the image is complete.
|
||||
if (NS_SUCCEEDED(aStatus) && mImageStatus != imgIRequest::STATUS_ERROR)
|
||||
mImageStatus |= imgIRequest::STATUS_LOAD_COMPLETE;
|
||||
else
|
||||
mImageStatus = imgIRequest::STATUS_ERROR;
|
||||
mState |= FLAG_REQUEST_STOPPED;
|
||||
if (aLastPart) {
|
||||
mState |= FLAG_MULTIPART_STOPPED;
|
||||
}
|
||||
if (NS_FAILED(aStatus)) {
|
||||
mState |= FLAG_HAS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -927,7 +863,7 @@ imgStatusTracker::OnStopRequest(bool aLastPart,
|
||||
new OnStopRequestEvent(this, aLastPart, aStatus));
|
||||
return;
|
||||
}
|
||||
bool preexistingError = mImageStatus == imgIRequest::STATUS_ERROR;
|
||||
bool preexistingError = mState & FLAG_HAS_ERROR;
|
||||
|
||||
RecordStopRequest(aLastPart, aStatus);
|
||||
/* notify the kids */
|
||||
@ -948,7 +884,6 @@ void
|
||||
imgStatusTracker::OnDiscard()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
RecordDiscard();
|
||||
|
||||
/* notify the kids */
|
||||
ProxyArray::ForwardIterator iter(mConsumers);
|
||||
@ -1016,8 +951,7 @@ imgStatusTracker::OnDataAvailable()
|
||||
void
|
||||
imgStatusTracker::RecordBlockOnload()
|
||||
{
|
||||
MOZ_ASSERT(!(mState & stateBlockingOnload));
|
||||
mState |= stateBlockingOnload;
|
||||
mState |= FLAG_ONLOAD_BLOCKED;
|
||||
}
|
||||
|
||||
void
|
||||
@ -1032,7 +966,11 @@ imgStatusTracker::SendBlockOnload(imgRequestProxy* aProxy)
|
||||
void
|
||||
imgStatusTracker::RecordUnblockOnload()
|
||||
{
|
||||
mState &= ~stateBlockingOnload;
|
||||
// We sometimes unblock speculatively, so only actually unblock if we've
|
||||
// previously blocked.
|
||||
if (mState & FLAG_ONLOAD_BLOCKED) {
|
||||
mState |= FLAG_ONLOAD_UNBLOCKED;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -1052,7 +990,7 @@ imgStatusTracker::MaybeUnblockOnload()
|
||||
NS_NewRunnableMethod(this, &imgStatusTracker::MaybeUnblockOnload));
|
||||
return;
|
||||
}
|
||||
if (!(mState & stateBlockingOnload)) {
|
||||
if (!(mState & FLAG_ONLOAD_BLOCKED) || (mState & FLAG_ONLOAD_UNBLOCKED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1070,7 +1008,7 @@ imgStatusTracker::MaybeUnblockOnload()
|
||||
void
|
||||
imgStatusTracker::RecordError()
|
||||
{
|
||||
mImageStatus = imgIRequest::STATUS_ERROR;
|
||||
mState |= FLAG_HAS_ERROR;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -27,18 +27,27 @@ namespace image {
|
||||
|
||||
class Image;
|
||||
|
||||
// Image state bitflags.
|
||||
enum {
|
||||
FLAG_REQUEST_STARTED = 1u << 0,
|
||||
FLAG_HAS_SIZE = 1u << 1, // STATUS_SIZE_AVAILABLE
|
||||
FLAG_DECODE_STARTED = 1u << 2, // STATUS_DECODE_STARTED
|
||||
FLAG_DECODE_STOPPED = 1u << 3, // STATUS_DECODE_COMPLETE
|
||||
FLAG_FRAME_STOPPED = 1u << 4, // STATUS_FRAME_COMPLETE
|
||||
FLAG_REQUEST_STOPPED = 1u << 5, // STATUS_LOAD_COMPLETE
|
||||
FLAG_ONLOAD_BLOCKED = 1u << 6,
|
||||
FLAG_ONLOAD_UNBLOCKED = 1u << 7,
|
||||
FLAG_IS_ANIMATED = 1u << 8,
|
||||
FLAG_IS_MULTIPART = 1u << 9,
|
||||
FLAG_MULTIPART_STOPPED = 1u << 10,
|
||||
FLAG_HAS_ERROR = 1u << 11 // STATUS_ERROR
|
||||
};
|
||||
|
||||
struct ImageStatusDiff
|
||||
{
|
||||
ImageStatusDiff()
|
||||
: invalidRect()
|
||||
, diffState(0)
|
||||
, diffImageStatus(0)
|
||||
, unblockedOnload(false)
|
||||
, unsetDecodeStarted(false)
|
||||
, foundError(false)
|
||||
, foundIsMultipart(false)
|
||||
, foundLastPart(false)
|
||||
, gotDecoded(false)
|
||||
{ }
|
||||
|
||||
static ImageStatusDiff NoChange() { return ImageStatusDiff(); }
|
||||
@ -47,48 +56,16 @@ struct ImageStatusDiff
|
||||
bool operator!=(const ImageStatusDiff& aOther) const { return !(*this == aOther); }
|
||||
bool operator==(const ImageStatusDiff& aOther) const {
|
||||
return aOther.invalidRect == invalidRect
|
||||
&& aOther.diffState == diffState
|
||||
&& aOther.diffImageStatus == diffImageStatus
|
||||
&& aOther.unblockedOnload == unblockedOnload
|
||||
&& aOther.unsetDecodeStarted == unsetDecodeStarted
|
||||
&& aOther.foundError == foundError
|
||||
&& aOther.foundIsMultipart == foundIsMultipart
|
||||
&& aOther.foundLastPart == foundLastPart
|
||||
&& aOther.gotDecoded == gotDecoded;
|
||||
&& aOther.diffState == diffState;
|
||||
}
|
||||
|
||||
void Combine(const ImageStatusDiff& aOther) {
|
||||
invalidRect = invalidRect.Union(aOther.invalidRect);
|
||||
diffState |= aOther.diffState;
|
||||
diffImageStatus |= aOther.diffImageStatus;
|
||||
unblockedOnload = unblockedOnload || aOther.unblockedOnload;
|
||||
unsetDecodeStarted = unsetDecodeStarted || aOther.unsetDecodeStarted;
|
||||
foundError = foundError || aOther.foundError;
|
||||
foundIsMultipart = foundIsMultipart || aOther.foundIsMultipart;
|
||||
foundLastPart = foundLastPart || aOther.foundLastPart;
|
||||
gotDecoded = gotDecoded || aOther.gotDecoded;
|
||||
}
|
||||
|
||||
nsIntRect invalidRect;
|
||||
uint32_t diffState;
|
||||
uint32_t diffImageStatus;
|
||||
bool unblockedOnload : 1;
|
||||
bool unsetDecodeStarted : 1;
|
||||
bool foundError : 1;
|
||||
bool foundIsMultipart : 1;
|
||||
bool foundLastPart : 1;
|
||||
bool gotDecoded : 1;
|
||||
};
|
||||
|
||||
enum {
|
||||
stateRequestStarted = 1u << 0,
|
||||
stateHasSize = 1u << 1,
|
||||
stateDecodeStarted = 1u << 2,
|
||||
stateDecodeStopped = 1u << 3,
|
||||
stateFrameStopped = 1u << 4,
|
||||
stateRequestStopped = 1u << 5,
|
||||
stateBlockingOnload = 1u << 6,
|
||||
stateImageIsAnimated = 1u << 7
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
@ -130,7 +107,7 @@ public:
|
||||
void ResetImage();
|
||||
|
||||
// Inform this status tracker that it is associated with a multipart image.
|
||||
void SetIsMultipart() { mIsMultipart = true; }
|
||||
void SetIsMultipart();
|
||||
|
||||
// Schedule an asynchronous "replaying" of all the notifications that would
|
||||
// have to happen to put us in the current state.
|
||||
@ -220,7 +197,6 @@ public:
|
||||
void SendStopFrame(imgRequestProxy* aProxy);
|
||||
void RecordStopDecode(nsresult statusg);
|
||||
void SendStopDecode(imgRequestProxy* aProxy, nsresult aStatus);
|
||||
void RecordDiscard();
|
||||
void SendDiscard(imgRequestProxy* aProxy);
|
||||
void RecordUnlockedDraw();
|
||||
void SendUnlockedDraw(imgRequestProxy* aProxy);
|
||||
@ -263,7 +239,7 @@ public:
|
||||
|
||||
void RecordError();
|
||||
|
||||
bool IsMultipart() const { return mIsMultipart; }
|
||||
bool IsMultipart() const { return mState & mozilla::image::FLAG_IS_MULTIPART; }
|
||||
|
||||
// Weak pointer getters - no AddRefs.
|
||||
inline already_AddRefed<mozilla::image::Image> GetImage() const {
|
||||
@ -308,7 +284,7 @@ private:
|
||||
// thread, and mConsumers is not threadsafe.
|
||||
static void SyncNotifyState(ProxyArray& proxies,
|
||||
bool hasImage, uint32_t state,
|
||||
nsIntRect& dirtyRect, bool hadLastPart);
|
||||
nsIntRect& dirtyRect);
|
||||
|
||||
nsCOMPtr<nsIRunnable> mRequestRunnable;
|
||||
|
||||
@ -327,10 +303,6 @@ private:
|
||||
mozilla::RefPtr<imgDecoderObserver> mTrackerObserver;
|
||||
|
||||
uint32_t mState;
|
||||
uint32_t mImageStatus;
|
||||
bool mIsMultipart : 1;
|
||||
bool mHadLastPart : 1;
|
||||
bool mHasBeenDecoded : 1;
|
||||
};
|
||||
|
||||
class imgStatusTrackerInit
|
||||
|
@ -10,11 +10,37 @@ let prefBranch = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefService)
|
||||
.getBranch('image.mem.');
|
||||
|
||||
function isImgDecoded() {
|
||||
function ImageDiscardObserver(result) {
|
||||
this.discard = function onDiscard(request)
|
||||
{
|
||||
result.wasDiscarded = true;
|
||||
this.synchronous = false;
|
||||
}
|
||||
|
||||
this.synchronous = true;
|
||||
}
|
||||
|
||||
function currentRequest() {
|
||||
let img = gBrowser.getBrowserForTab(newTab).contentWindow
|
||||
.document.getElementById('testImg');
|
||||
img.QueryInterface(Ci.nsIImageLoadingContent);
|
||||
let request = img.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
|
||||
return img.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
|
||||
}
|
||||
|
||||
function attachDiscardObserver(result) {
|
||||
// Create the discard observer.
|
||||
let observer = new ImageDiscardObserver(result);
|
||||
let scriptedObserver = Cc["@mozilla.org/image/tools;1"]
|
||||
.getService(Ci.imgITools)
|
||||
.createScriptedObserver(observer);
|
||||
|
||||
// Clone the current imgIRequest with our new observer.
|
||||
let request = currentRequest();
|
||||
return request.clone(scriptedObserver);
|
||||
}
|
||||
|
||||
function isImgDecoded() {
|
||||
let request = currentRequest();
|
||||
return request.imageStatus & Ci.imgIRequest.STATUS_FRAME_COMPLETE ? true : false;
|
||||
}
|
||||
|
||||
@ -43,6 +69,10 @@ function test() {
|
||||
}
|
||||
|
||||
function step2() {
|
||||
// Attach a discard listener and create a place to hold the result.
|
||||
var result = { wasDiscarded: false };
|
||||
var clonedRequest = attachDiscardObserver(result);
|
||||
|
||||
// Check that the image is decoded.
|
||||
forceDecodeImg();
|
||||
ok(isImgDecoded(), 'Image should initially be decoded.');
|
||||
@ -53,10 +83,11 @@ function step2() {
|
||||
var os = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
os.notifyObservers(null, 'memory-pressure', 'heap-minimize');
|
||||
ok(!isImgDecoded(), 'Image should be discarded.');
|
||||
ok(result.wasDiscarded, 'Image should be discarded.');
|
||||
|
||||
// And we're done.
|
||||
gBrowser.removeTab(newTab);
|
||||
prefBranch.setBoolPref('discardable', oldDiscardingPref);
|
||||
clonedRequest.cancelAndForgetObserver(0);
|
||||
finish();
|
||||
}
|
||||
|
@ -11,11 +11,25 @@
|
||||
|
||||
namespace js {
|
||||
|
||||
struct JS_PUBLIC_API(TimeBudget)
|
||||
{
|
||||
int64_t budget;
|
||||
|
||||
explicit TimeBudget(int64_t milliseconds) { budget = milliseconds; }
|
||||
};
|
||||
|
||||
struct JS_PUBLIC_API(WorkBudget)
|
||||
{
|
||||
int64_t budget;
|
||||
|
||||
explicit WorkBudget(int64_t work) { budget = work; }
|
||||
};
|
||||
|
||||
/*
|
||||
* This class records how much work has been done in a given collection slice, so that
|
||||
* we can return before pausing for too long. Some slices are allowed to run for
|
||||
* unlimited time, and others are bounded. To reduce the number of gettimeofday
|
||||
* calls, we only check the time every 1000 operations.
|
||||
* This class records how much work has been done in a given collection slice,
|
||||
* so that we can return before pausing for too long. Some slices are allowed
|
||||
* to run for unlimited time, and others are bounded. To reduce the number of
|
||||
* gettimeofday calls, we only check the time every 1000 operations.
|
||||
*/
|
||||
struct JS_PUBLIC_API(SliceBudget)
|
||||
{
|
||||
@ -24,15 +38,16 @@ struct JS_PUBLIC_API(SliceBudget)
|
||||
|
||||
static const intptr_t CounterReset = 1000;
|
||||
|
||||
static const int64_t Unlimited = 0;
|
||||
static int64_t TimeBudget(int64_t millis);
|
||||
static int64_t WorkBudget(int64_t work);
|
||||
static const int64_t Unlimited = -1;
|
||||
|
||||
/* Equivalent to SliceBudget(UnlimitedBudget). */
|
||||
/* Use to create an unlimited budget. */
|
||||
SliceBudget();
|
||||
|
||||
/* Instantiate as SliceBudget(Time/WorkBudget(n)). */
|
||||
explicit SliceBudget(int64_t budget);
|
||||
/* Instantiate as SliceBudget(TimeBudget(n)). */
|
||||
explicit SliceBudget(TimeBudget time);
|
||||
|
||||
/* Instantiate as SliceBudget(WorkBudget(n)). */
|
||||
explicit SliceBudget(WorkBudget work);
|
||||
|
||||
void reset() {
|
||||
deadline = unlimitedDeadline;
|
||||
@ -43,10 +58,8 @@ struct JS_PUBLIC_API(SliceBudget)
|
||||
counter -= amt;
|
||||
}
|
||||
|
||||
bool checkOverBudget();
|
||||
|
||||
bool isOverBudget() {
|
||||
if (counter >= 0)
|
||||
if (counter > 0)
|
||||
return false;
|
||||
return checkOverBudget();
|
||||
}
|
||||
@ -55,10 +68,11 @@ struct JS_PUBLIC_API(SliceBudget)
|
||||
return deadline == unlimitedDeadline;
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
bool checkOverBudget();
|
||||
|
||||
static const int64_t unlimitedDeadline = INT64_MAX;
|
||||
static const intptr_t unlimitedStartCounter = INTPTR_MAX;
|
||||
|
||||
};
|
||||
|
||||
} // namespace js
|
||||
|
@ -628,16 +628,15 @@ GCSlice(JSContext *cx, unsigned argc, Value *vp)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool limit = true;
|
||||
uint32_t budget = 0;
|
||||
SliceBudget budget;
|
||||
if (args.length() == 1) {
|
||||
if (!ToUint32(cx, args[0], &budget))
|
||||
uint32_t work = 0;
|
||||
if (!ToUint32(cx, args[0], &work))
|
||||
return false;
|
||||
} else {
|
||||
limit = false;
|
||||
budget = SliceBudget(WorkBudget(work));
|
||||
}
|
||||
|
||||
cx->runtime()->gc.gcDebugSlice(limit, budget);
|
||||
cx->runtime()->gc.gcDebugSlice(budget);
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
@ -899,7 +899,8 @@ Parser<FullParseHandler>::checkFunctionArguments()
|
||||
// ES6 9.2.13.17 says that a lexical binding of 'arguments' shadows the
|
||||
// arguments object.
|
||||
bool argumentsHasLocalBinding = maybeArgDef && (maybeArgDef->kind() != Definition::ARG &&
|
||||
maybeArgDef->kind() != Definition::LET);
|
||||
maybeArgDef->kind() != Definition::LET &&
|
||||
maybeArgDef->kind() != Definition::CONST);
|
||||
bool hasRest = pc->sc->asFunctionBox()->function()->hasRest();
|
||||
if (hasRest && argumentsHasLocalBinding) {
|
||||
report(ParseError, false, nullptr, JSMSG_ARGUMENTS_AND_REST);
|
||||
|
@ -281,7 +281,7 @@ class GCRuntime
|
||||
void gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason);
|
||||
void gcSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis = 0);
|
||||
void gcFinalSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason);
|
||||
void gcDebugSlice(bool limit, int64_t objCount);
|
||||
void gcDebugSlice(SliceBudget &budget);
|
||||
|
||||
void runDebugGC();
|
||||
inline void poke();
|
||||
@ -510,14 +510,14 @@ class GCRuntime
|
||||
|
||||
bool initZeal();
|
||||
void requestMajorGC(JS::gcreason::Reason reason);
|
||||
void collect(bool incremental, int64_t budget, JSGCInvocationKind gckind,
|
||||
void collect(bool incremental, SliceBudget &budget, JSGCInvocationKind gckind,
|
||||
JS::gcreason::Reason reason);
|
||||
bool gcCycle(bool incremental, int64_t budget, JSGCInvocationKind gckind,
|
||||
bool gcCycle(bool incremental, SliceBudget &budget, JSGCInvocationKind gckind,
|
||||
JS::gcreason::Reason reason);
|
||||
gcstats::ZoneGCStats scanZonesBeforeGC();
|
||||
void budgetIncrementalGC(int64_t *budget);
|
||||
void budgetIncrementalGC(SliceBudget &budget);
|
||||
void resetIncrementalGC(const char *reason);
|
||||
void incrementalCollectSlice(int64_t budget, JS::gcreason::Reason reason);
|
||||
void incrementalCollectSlice(SliceBudget &budget, JS::gcreason::Reason reason);
|
||||
void pushZealSelectedObjects();
|
||||
void purgeRuntime();
|
||||
bool beginMarkPhase(JS::gcreason::Reason reason);
|
||||
|
@ -28,7 +28,7 @@ gcPreserveCode();
|
||||
try {
|
||||
mjitChunkLimit(1);
|
||||
} catch(exc1) {}
|
||||
gcslice(0);
|
||||
gcslice(1);
|
||||
m(1);
|
||||
gc();
|
||||
m(2);
|
||||
|
@ -3,4 +3,4 @@
|
||||
//
|
||||
|
||||
gc();
|
||||
evaluate("gcslice(0);");
|
||||
evaluate("gcslice(1);");
|
||||
|
@ -20,7 +20,7 @@ function h(code) {
|
||||
h("\
|
||||
p=m();\
|
||||
gcPreserveCode();\
|
||||
gcslice(7);\
|
||||
gcslice(8);\
|
||||
")
|
||||
h("\"\"")
|
||||
h("")
|
||||
|
@ -3,7 +3,7 @@
|
||||
//
|
||||
gc()
|
||||
schedulegc(this)
|
||||
gcslice(2)
|
||||
gcslice(3)
|
||||
function f() {
|
||||
this["x"] = this["x"] = {}
|
||||
}
|
||||
|
@ -5,8 +5,8 @@
|
||||
function printBugNumber (num) {
|
||||
BUGNUMBER = num;
|
||||
}
|
||||
gcslice(0)
|
||||
gcslice(1)
|
||||
schedulegc(this);
|
||||
gcslice(1);
|
||||
gcslice(2);
|
||||
var BUGNUMBER = ("one");
|
||||
printBugNumber();
|
||||
|
@ -1,6 +1,6 @@
|
||||
var g1 = newGlobal();
|
||||
schedulegc(g1);
|
||||
gcslice(0);
|
||||
gcslice(1);
|
||||
function testEq(b) {
|
||||
var a = deserialize(serialize(b));
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ function gen()
|
||||
yield 1;
|
||||
local = null;
|
||||
gc();
|
||||
gcslice(0);
|
||||
gcslice(0); // Start IGC, but don't mark anything.
|
||||
yield 2;
|
||||
}
|
||||
|
||||
|
@ -12,5 +12,5 @@ function recur(n)
|
||||
}
|
||||
|
||||
validategc(false);
|
||||
gcslice(0);
|
||||
gcslice(1);
|
||||
recur(10);
|
||||
|
@ -11,8 +11,8 @@ expect = "generator function foo returns a value";
|
||||
actual = (function (j) {}).message;
|
||||
reportCompare(expect, actual, summary + ": 1");
|
||||
reportCompare(expect, actual, summary + ": 2");
|
||||
gcslice(0);
|
||||
gcslice(1);
|
||||
gcslice(2);
|
||||
gc();
|
||||
var strings = [ (0), ];
|
||||
for (var i = 0; i < strings.length; i++)
|
||||
|
@ -4,7 +4,7 @@ evalcx("\
|
||||
gcslice = function() { };\
|
||||
array = new Uint8Array;\
|
||||
t0 = array.subarray();\
|
||||
gcslice(11); \
|
||||
gcslice(12); \
|
||||
array.subarray();\
|
||||
gc();\
|
||||
gc();\
|
||||
|
@ -3,7 +3,7 @@
|
||||
if (typeof evalInWorker == "undefined")
|
||||
quit();
|
||||
|
||||
gcslice(10);
|
||||
gcslice(11);
|
||||
evalInWorker("print('helo world');");
|
||||
for (i = 0; i < 100000; i++) {}
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
// |jit-test| error: TypeError
|
||||
|
||||
(function () { const [arguments] = 0; })();
|
@ -1,6 +1,6 @@
|
||||
if (this.hasOwnProperty('Intl')) {
|
||||
gc();
|
||||
gcslice(0);
|
||||
gcslice(1);
|
||||
var thisValues = [ "x" ];
|
||||
thisValues.forEach(function (value) {
|
||||
var format = Intl.DateTimeFormat.call(value);
|
||||
|
@ -1,5 +1,5 @@
|
||||
gc();
|
||||
gcslice(0);
|
||||
gcslice(1);
|
||||
function isClone(a, b) {
|
||||
var rmemory = new WeakMap();
|
||||
rmemory.set(a,b);
|
||||
|
@ -9,5 +9,5 @@ var recursiveFunctions = [{
|
||||
eval(a.text.replace(/@/g, ""))
|
||||
}
|
||||
})();
|
||||
gcslice(2868);
|
||||
gcslice(2869);
|
||||
Function("v={c:[{x:[[]],N:{x:[{}[d]]}}]}=minorgc(true)")()
|
||||
|
@ -10,5 +10,5 @@ var recursiveFunctions = [{
|
||||
eval(a.text.replace(/@/g, ""))
|
||||
}
|
||||
})();
|
||||
gcslice(2868);
|
||||
gcslice(2869);
|
||||
Function("v={c:[{x:[[]],N:{x:[{}[d]]}}]}=minorgc(true)")()
|
||||
|
@ -10,5 +10,5 @@ var recursiveFunctions = [{
|
||||
eval(a.text.replace(/@/g, ""))
|
||||
}
|
||||
})();
|
||||
gcslice(2868);
|
||||
gcslice(2869);
|
||||
Function("v={c:[{x:[[]],N:{x:[{}[d]]}}]}=minorgc(true)")()
|
||||
|
@ -4,6 +4,6 @@ var juneDate = new Date(2000, 5, 20, 0, 0, 0, 0);
|
||||
for (var i = 0; i < function(x) myObj(Date.prototype.toString.apply(x)); void i) {
|
||||
eval(a.text.replace(/@/g, ""))
|
||||
}
|
||||
gcslice(2600);
|
||||
gcslice(2601);
|
||||
function testcase() {}
|
||||
new Uint16Array(testcase);
|
||||
|
@ -23,7 +23,7 @@ function init()
|
||||
*/
|
||||
eval("init()");
|
||||
|
||||
gcslice(0);
|
||||
gcslice(0); // Start IGC, but don't mark anything.
|
||||
selectforgc(objs.root2);
|
||||
gcslice(1);
|
||||
objs.root2.ptr = objs.root1.ptr;
|
||||
|
@ -22,7 +22,7 @@ function init()
|
||||
*/
|
||||
eval("init()");
|
||||
|
||||
gcslice(0);
|
||||
gcslice(0); // Start IGC, but don't mark anything.
|
||||
selectforgc(objs.root);
|
||||
gcslice(1);
|
||||
delete objs.root.b;
|
||||
|
@ -4,7 +4,7 @@ var g1 = newGlobal();
|
||||
var g2 = newGlobal();
|
||||
|
||||
schedulegc(g1);
|
||||
gcslice(0);
|
||||
gcslice(0); // Start IGC, but don't mark anything.
|
||||
schedulegc(g2);
|
||||
gcslice(1);
|
||||
gcslice();
|
||||
|
@ -5,7 +5,7 @@ var g2 = newGlobal();
|
||||
|
||||
schedulegc(g1);
|
||||
schedulegc(g2);
|
||||
gcslice(0);
|
||||
gcslice(0); // Start IGC, but don't mark anything.
|
||||
schedulegc(g1);
|
||||
gcslice(1);
|
||||
gcslice();
|
||||
|
@ -3545,7 +3545,7 @@ IsCacheableSetPropAddSlot(JSContext *cx, HandleObject obj, HandleShape oldShape,
|
||||
|
||||
static bool
|
||||
IsCacheableSetPropCall(JSContext *cx, JSObject *obj, JSObject *holder, Shape *shape,
|
||||
Shape* oldShape, bool *isScripted, bool *isTemporarilyUnoptimizable)
|
||||
bool *isScripted, bool *isTemporarilyUnoptimizable)
|
||||
{
|
||||
MOZ_ASSERT(isScripted);
|
||||
|
||||
@ -3553,13 +3553,6 @@ IsCacheableSetPropCall(JSContext *cx, JSObject *obj, JSObject *holder, Shape *sh
|
||||
if (obj == holder)
|
||||
return false;
|
||||
|
||||
if (obj->lastProperty() != oldShape) {
|
||||
// During the property set, the shape of the object changed. Not much
|
||||
// point caching this based on the post-set shape, since the object
|
||||
// wouldn't have matched such an IC stub anyway.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!shape || !IsCacheableProtoChain(obj, holder))
|
||||
return false;
|
||||
|
||||
@ -5773,17 +5766,9 @@ UpdateExistingSetPropCallStubs(ICSetProp_Fallback* fallbackStub,
|
||||
if (setPropStub->holder() == holder) {
|
||||
// We want to update the holder shape to match the new one no
|
||||
// matter what, even if the receiver shape is different.
|
||||
//
|
||||
// We would like to assert that either
|
||||
// setPropStub->holderShape() != holder->lastProperty() or
|
||||
// setPropStub->shape() != receiverShape, but that assertion can
|
||||
// fail if there is something in a setter that changes something
|
||||
// that we guard on in our stub but don't check for before/after
|
||||
// differences across the set during stub generation. For
|
||||
// example, a setter mutating the shape of the proto the setter
|
||||
// lives on would cause us to create an IC stub that never
|
||||
// matches as protos with the old shape flow into it, but always
|
||||
// matches post-set, which is where we are now.
|
||||
MOZ_ASSERT(setPropStub->holderShape() != holder->lastProperty() ||
|
||||
setPropStub->shape() != receiverShape,
|
||||
"Why didn't we end up using this stub?");
|
||||
setPropStub->holderShape() = holder->lastProperty();
|
||||
// Make sure to update the setter, since a shape change might
|
||||
// have changed which setter we want to use.
|
||||
@ -7730,15 +7715,14 @@ BaselineScript::noteAccessedGetter(uint32_t pcOffset)
|
||||
// SetProp_Fallback
|
||||
//
|
||||
|
||||
// Attach an optimized stub for a SETPROP/SETGNAME/SETNAME op.
|
||||
// Attach an optimized property set stub for a SETPROP/SETGNAME/SETNAME op on a
|
||||
// value property.
|
||||
static bool
|
||||
TryAttachSetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetProp_Fallback *stub,
|
||||
HandleObject obj, HandleShape oldShape, HandleTypeObject oldType, uint32_t oldSlots,
|
||||
HandlePropertyName name, HandleId id, HandleValue rhs, bool *attached,
|
||||
bool *isTemporarilyUnoptimizable)
|
||||
TryAttachSetValuePropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetProp_Fallback *stub,
|
||||
HandleObject obj, HandleShape oldShape, HandleTypeObject oldType, uint32_t oldSlots,
|
||||
HandlePropertyName name, HandleId id, HandleValue rhs, bool *attached)
|
||||
{
|
||||
MOZ_ASSERT(!*attached);
|
||||
MOZ_ASSERT(!*isTemporarilyUnoptimizable);
|
||||
|
||||
if (!obj->isNative() || obj->watched())
|
||||
return true;
|
||||
@ -7816,8 +7800,30 @@ TryAttachSetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetPr
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attach an optimized property set stub for a SETPROP/SETGNAME/SETNAME op on
|
||||
// an accessor property.
|
||||
static bool
|
||||
TryAttachSetAccessorPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetProp_Fallback *stub,
|
||||
HandleObject obj, HandleShape receiverShape, HandlePropertyName name,
|
||||
HandleId id, HandleValue rhs, bool *attached,
|
||||
bool *isTemporarilyUnoptimizable)
|
||||
{
|
||||
MOZ_ASSERT(!*attached);
|
||||
MOZ_ASSERT(!*isTemporarilyUnoptimizable);
|
||||
|
||||
if (!obj->isNative() || obj->watched())
|
||||
return true;
|
||||
|
||||
RootedShape shape(cx);
|
||||
RootedObject holder(cx);
|
||||
if (!EffectlesslyLookupProperty(cx, obj, name, &holder, &shape))
|
||||
return false;
|
||||
|
||||
bool isScripted = false;
|
||||
bool cacheableCall = IsCacheableSetPropCall(cx, obj, holder, shape, oldShape,
|
||||
bool cacheableCall = IsCacheableSetPropCall(cx, obj, holder, shape,
|
||||
&isScripted, isTemporarilyUnoptimizable);
|
||||
|
||||
// Try handling scripted setters.
|
||||
@ -7827,7 +7833,7 @@ TryAttachSetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetPr
|
||||
MOZ_ASSERT(callee->hasScript());
|
||||
|
||||
if (UpdateExistingSetPropCallStubs(stub, ICStub::SetProp_CallScripted,
|
||||
holder, oldShape, callee)) {
|
||||
holder, receiverShape, callee)) {
|
||||
*attached = true;
|
||||
return true;
|
||||
}
|
||||
@ -7852,7 +7858,7 @@ TryAttachSetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetPr
|
||||
MOZ_ASSERT(callee->isNative());
|
||||
|
||||
if (UpdateExistingSetPropCallStubs(stub, ICStub::SetProp_CallNative,
|
||||
holder, oldShape, callee)) {
|
||||
holder, receiverShape, callee)) {
|
||||
*attached = true;
|
||||
return true;
|
||||
}
|
||||
@ -7908,6 +7914,19 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub_
|
||||
return false;
|
||||
uint32_t oldSlots = obj->isNative() ? obj->as<NativeObject>().numDynamicSlots() : 0;
|
||||
|
||||
bool attached = false;
|
||||
// There are some reasons we can fail to attach a stub that are temporary.
|
||||
// We want to avoid calling noteUnoptimizableAccess() if the reason we
|
||||
// failed to attach a stub is one of those temporary reasons, since we might
|
||||
// end up attaching a stub for the exact same access later.
|
||||
bool isTemporarilyUnoptimizable = false;
|
||||
if (stub->numOptimizedStubs() < ICSetProp_Fallback::MAX_OPTIMIZED_STUBS &&
|
||||
!TryAttachSetAccessorPropStub(cx, script, pc, stub, obj, oldShape, name, id,
|
||||
rhs, &attached, &isTemporarilyUnoptimizable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (op == JSOP_INITPROP) {
|
||||
MOZ_ASSERT(obj->is<JSObject>());
|
||||
if (!DefineNativeProperty(cx, obj.as<NativeObject>(), id, rhs,
|
||||
@ -7943,14 +7962,9 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub_
|
||||
return true;
|
||||
}
|
||||
|
||||
bool attached = false;
|
||||
// There are some reasons we can fail to attach a stub that are temporary.
|
||||
// We want to avoid calling noteUnoptimizableAccess() if the reason we
|
||||
// failed to attach a stub is one of those temporary reasons, since we might
|
||||
// end up attaching a stub for the exact same access later.
|
||||
bool isTemporarilyUnoptimizable = false;
|
||||
if (!TryAttachSetPropStub(cx, script, pc, stub, obj, oldShape, oldType, oldSlots,
|
||||
name, id, rhs, &attached, &isTemporarilyUnoptimizable))
|
||||
if (!attached &&
|
||||
!TryAttachSetValuePropStub(cx, script, pc, stub, obj, oldShape,
|
||||
oldType, oldSlots, name, id, rhs, &attached))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -87,12 +87,14 @@ BEGIN_TEST(testGCFinalizeCallback)
|
||||
FinalizeCalls = 0;
|
||||
JS_SetGCZeal(cx, 9, 1000000);
|
||||
JS::PrepareForFullGC(rt);
|
||||
rt->gc.gcDebugSlice(true, 1);
|
||||
js::SliceBudget budget(js::WorkBudget(1));
|
||||
rt->gc.gcDebugSlice(budget);
|
||||
CHECK(rt->gc.state() == js::gc::MARK);
|
||||
CHECK(rt->gc.isFullGc());
|
||||
|
||||
JS::RootedObject global4(cx, createTestGlobal());
|
||||
rt->gc.gcDebugSlice(true, 1);
|
||||
budget = js::SliceBudget(js::WorkBudget(1));
|
||||
rt->gc.gcDebugSlice(budget);
|
||||
CHECK(rt->gc.state() == js::gc::NO_INCREMENTAL);
|
||||
CHECK(!rt->gc.isFullGc());
|
||||
CHECK(checkMultipleGroups());
|
||||
|
@ -89,7 +89,8 @@ BEGIN_TEST(testWeakMap_keyDelegates)
|
||||
* zone to finish marking before the delegate zone.
|
||||
*/
|
||||
CHECK(newCCW(map, delegate));
|
||||
rt->gc.gcDebugSlice(true, 1000000);
|
||||
js::SliceBudget budget(js::WorkBudget(1000000));
|
||||
rt->gc.gcDebugSlice(budget);
|
||||
#ifdef DEBUG
|
||||
CHECK(map->zone()->lastZoneGroupIndex() < delegate->zone()->lastZoneGroupIndex());
|
||||
#endif
|
||||
@ -102,7 +103,8 @@ BEGIN_TEST(testWeakMap_keyDelegates)
|
||||
/* Check the delegate keeps the entry alive even if the key is not reachable. */
|
||||
key = nullptr;
|
||||
CHECK(newCCW(map, delegate));
|
||||
rt->gc.gcDebugSlice(true, 100000);
|
||||
budget = js::SliceBudget(js::WorkBudget(100000));
|
||||
rt->gc.gcDebugSlice(budget);
|
||||
CHECK(checkSize(map, 1));
|
||||
|
||||
/*
|
||||
|
113
js/src/jsgc.cpp
113
js/src/jsgc.cpp
@ -1457,7 +1457,7 @@ GCRuntime::setParameter(JSGCParamKey key, uint32_t value)
|
||||
setMaxMallocBytes(value);
|
||||
break;
|
||||
case JSGC_SLICE_TIME_BUDGET:
|
||||
sliceBudget = SliceBudget::TimeBudget(value);
|
||||
sliceBudget = value ? value : SliceBudget::Unlimited;
|
||||
break;
|
||||
case JSGC_MARK_STACK_LIMIT:
|
||||
setMarkStackLimit(value);
|
||||
@ -1554,7 +1554,7 @@ GCRuntime::getParameter(JSGCParamKey key, const AutoLockGC &lock)
|
||||
case JSGC_TOTAL_CHUNKS:
|
||||
return uint32_t(chunkSet.count() + emptyChunks(lock).count());
|
||||
case JSGC_SLICE_TIME_BUDGET:
|
||||
return uint32_t(sliceBudget > 0 ? sliceBudget / PRMJ_USEC_PER_MSEC : 0);
|
||||
return uint32_t(sliceBudget > 0 ? sliceBudget : 0);
|
||||
case JSGC_MARK_STACK_LIMIT:
|
||||
return marker.maxCapacity();
|
||||
case JSGC_HIGH_FREQUENCY_TIME_LIMIT:
|
||||
@ -2930,41 +2930,36 @@ GCRuntime::refillFreeListInGC(Zone *zone, AllocKind thingKind)
|
||||
return allocator.arenas.allocateFromArena(zone, thingKind);
|
||||
}
|
||||
|
||||
/* static */ int64_t
|
||||
SliceBudget::TimeBudget(int64_t millis)
|
||||
{
|
||||
return millis * PRMJ_USEC_PER_MSEC;
|
||||
}
|
||||
|
||||
/* static */ int64_t
|
||||
SliceBudget::WorkBudget(int64_t work)
|
||||
{
|
||||
/* For work = 0 not to mean Unlimited, we subtract 1. */
|
||||
return -work - 1;
|
||||
}
|
||||
|
||||
SliceBudget::SliceBudget()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
SliceBudget::SliceBudget(int64_t budget)
|
||||
SliceBudget::SliceBudget(TimeBudget time)
|
||||
{
|
||||
if (budget == Unlimited) {
|
||||
if (time.budget < 0) {
|
||||
reset();
|
||||
} else if (budget > 0) {
|
||||
deadline = PRMJ_Now() + budget;
|
||||
} else {
|
||||
// Note: TimeBudget(0) is equivalent to WorkBudget(CounterReset).
|
||||
deadline = PRMJ_Now() + time.budget * PRMJ_USEC_PER_MSEC;
|
||||
counter = CounterReset;
|
||||
}
|
||||
}
|
||||
|
||||
SliceBudget::SliceBudget(WorkBudget work)
|
||||
{
|
||||
if (work.budget < 0) {
|
||||
reset();
|
||||
} else {
|
||||
deadline = 0;
|
||||
counter = -budget - 1;
|
||||
counter = work.budget;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SliceBudget::checkOverBudget()
|
||||
{
|
||||
bool over = PRMJ_Now() > deadline;
|
||||
bool over = PRMJ_Now() >= deadline;
|
||||
if (!over)
|
||||
counter = CounterReset;
|
||||
return over;
|
||||
@ -5536,7 +5531,7 @@ GCRuntime::resetIncrementalGC(const char *reason)
|
||||
break;
|
||||
}
|
||||
|
||||
case SWEEP:
|
||||
case SWEEP: {
|
||||
marker.reset();
|
||||
|
||||
for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
|
||||
@ -5544,13 +5539,15 @@ GCRuntime::resetIncrementalGC(const char *reason)
|
||||
|
||||
/* Finish sweeping the current zone group, then abort. */
|
||||
abortSweepAfterCurrentGroup = true;
|
||||
incrementalCollectSlice(SliceBudget::Unlimited, JS::gcreason::RESET);
|
||||
SliceBudget budget;
|
||||
incrementalCollectSlice(budget, JS::gcreason::RESET);
|
||||
|
||||
{
|
||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
|
||||
rt->gc.waitBackgroundSweepOrAllocEnd();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Invalid incremental GC state");
|
||||
@ -5638,8 +5635,7 @@ GCRuntime::pushZealSelectedObjects()
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::incrementalCollectSlice(int64_t budget,
|
||||
JS::gcreason::Reason reason)
|
||||
GCRuntime::incrementalCollectSlice(SliceBudget &budget, JS::gcreason::Reason reason)
|
||||
{
|
||||
MOZ_ASSERT(rt->currentThreadHasExclusiveAccess());
|
||||
|
||||
@ -5652,7 +5648,7 @@ GCRuntime::incrementalCollectSlice(int64_t budget,
|
||||
|
||||
int zeal = 0;
|
||||
#ifdef JS_GC_ZEAL
|
||||
if (reason == JS::gcreason::DEBUG_GC && budget != SliceBudget::Unlimited) {
|
||||
if (reason == JS::gcreason::DEBUG_GC && !budget.isUnlimited()) {
|
||||
/*
|
||||
* Do the incremental collection type specified by zeal mode if the
|
||||
* collection was triggered by runDebugGC() and incremental GC has not
|
||||
@ -5663,18 +5659,16 @@ GCRuntime::incrementalCollectSlice(int64_t budget,
|
||||
#endif
|
||||
|
||||
MOZ_ASSERT_IF(incrementalState != NO_INCREMENTAL, isIncremental);
|
||||
isIncremental = budget != SliceBudget::Unlimited;
|
||||
isIncremental = !budget.isUnlimited();
|
||||
|
||||
if (zeal == ZealIncrementalRootsThenFinish || zeal == ZealIncrementalMarkAllThenFinish) {
|
||||
/*
|
||||
* Yields between slices occurs at predetermined points in these modes;
|
||||
* the budget is not used.
|
||||
*/
|
||||
budget = SliceBudget::Unlimited;
|
||||
budget.reset();
|
||||
}
|
||||
|
||||
SliceBudget sliceBudget(budget);
|
||||
|
||||
if (incrementalState == NO_INCREMENTAL) {
|
||||
incrementalState = MARK_ROOTS;
|
||||
lastMarkSlice = false;
|
||||
@ -5704,11 +5698,11 @@ GCRuntime::incrementalCollectSlice(int64_t budget,
|
||||
case MARK: {
|
||||
/* If we needed delayed marking for gray roots, then collect until done. */
|
||||
if (!marker.hasBufferedGrayRoots()) {
|
||||
sliceBudget.reset();
|
||||
budget.reset();
|
||||
isIncremental = false;
|
||||
}
|
||||
|
||||
bool finished = drainMarkStack(sliceBudget, gcstats::PHASE_MARK);
|
||||
bool finished = drainMarkStack(budget, gcstats::PHASE_MARK);
|
||||
if (!finished)
|
||||
break;
|
||||
|
||||
@ -5734,7 +5728,7 @@ GCRuntime::incrementalCollectSlice(int64_t budget,
|
||||
* now exhasted.
|
||||
*/
|
||||
beginSweepPhase(lastGC);
|
||||
if (sliceBudget.isOverBudget())
|
||||
if (budget.isOverBudget())
|
||||
break;
|
||||
|
||||
/*
|
||||
@ -5748,7 +5742,7 @@ GCRuntime::incrementalCollectSlice(int64_t budget,
|
||||
}
|
||||
|
||||
case SWEEP: {
|
||||
bool finished = sweepPhase(sliceBudget);
|
||||
bool finished = sweepPhase(budget);
|
||||
if (!finished)
|
||||
break;
|
||||
|
||||
@ -5786,32 +5780,32 @@ gc::IsIncrementalGCSafe(JSRuntime *rt)
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::budgetIncrementalGC(int64_t *budget)
|
||||
GCRuntime::budgetIncrementalGC(SliceBudget &budget)
|
||||
{
|
||||
IncrementalSafety safe = IsIncrementalGCSafe(rt);
|
||||
if (!safe) {
|
||||
resetIncrementalGC(safe.reason());
|
||||
*budget = SliceBudget::Unlimited;
|
||||
budget.reset();
|
||||
stats.nonincremental(safe.reason());
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode != JSGC_MODE_INCREMENTAL) {
|
||||
resetIncrementalGC("GC mode change");
|
||||
*budget = SliceBudget::Unlimited;
|
||||
budget.reset();
|
||||
stats.nonincremental("GC mode");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTooMuchMalloc()) {
|
||||
*budget = SliceBudget::Unlimited;
|
||||
budget.reset();
|
||||
stats.nonincremental("malloc bytes trigger");
|
||||
}
|
||||
|
||||
bool reset = false;
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
if (zone->usage.gcBytes() >= zone->threshold.gcTriggerBytes()) {
|
||||
*budget = SliceBudget::Unlimited;
|
||||
budget.reset();
|
||||
stats.nonincremental("allocation trigger");
|
||||
}
|
||||
|
||||
@ -5822,7 +5816,7 @@ GCRuntime::budgetIncrementalGC(int64_t *budget)
|
||||
}
|
||||
|
||||
if (zone->isTooMuchMalloc()) {
|
||||
*budget = SliceBudget::Unlimited;
|
||||
budget.reset();
|
||||
stats.nonincremental("malloc bytes trigger");
|
||||
}
|
||||
}
|
||||
@ -5868,7 +5862,7 @@ struct AutoDisableStoreBuffer
|
||||
* to run another cycle.
|
||||
*/
|
||||
MOZ_NEVER_INLINE bool
|
||||
GCRuntime::gcCycle(bool incremental, int64_t budget, JSGCInvocationKind gckind,
|
||||
GCRuntime::gcCycle(bool incremental, SliceBudget &budget, JSGCInvocationKind gckind,
|
||||
JS::gcreason::Reason reason)
|
||||
{
|
||||
minorGC(reason);
|
||||
@ -5922,9 +5916,9 @@ GCRuntime::gcCycle(bool incremental, int64_t budget, JSGCInvocationKind gckind,
|
||||
resetIncrementalGC("requested");
|
||||
|
||||
stats.nonincremental("requested");
|
||||
budget = SliceBudget::Unlimited;
|
||||
budget.reset();
|
||||
} else {
|
||||
budgetIncrementalGC(&budget);
|
||||
budgetIncrementalGC(budget);
|
||||
}
|
||||
|
||||
/* The GC was reset, so we need a do-over. */
|
||||
@ -6017,7 +6011,7 @@ GCRuntime::scanZonesBeforeGC()
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::collect(bool incremental, int64_t budget, JSGCInvocationKind gckind,
|
||||
GCRuntime::collect(bool incremental, SliceBudget &budget, JSGCInvocationKind gckind,
|
||||
JS::gcreason::Reason reason)
|
||||
{
|
||||
/* GC shouldn't be running in parallel execution mode */
|
||||
@ -6042,7 +6036,7 @@ GCRuntime::collect(bool incremental, int64_t budget, JSGCInvocationKind gckind,
|
||||
return;
|
||||
#endif
|
||||
|
||||
MOZ_ASSERT_IF(!incremental || budget != SliceBudget::Unlimited, JSGC_INCREMENTAL);
|
||||
MOZ_ASSERT_IF(!incremental || !budget.isUnlimited(), JSGC_INCREMENTAL);
|
||||
|
||||
AutoStopVerifyingBarriers av(rt, reason == JS::gcreason::SHUTDOWN_CC ||
|
||||
reason == JS::gcreason::DESTROY_RUNTIME);
|
||||
@ -6109,21 +6103,22 @@ GCRuntime::collect(bool incremental, int64_t budget, JSGCInvocationKind gckind,
|
||||
void
|
||||
GCRuntime::gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason)
|
||||
{
|
||||
collect(false, SliceBudget::Unlimited, gckind, reason);
|
||||
SliceBudget budget;
|
||||
collect(false, budget, gckind, reason);
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::gcSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis)
|
||||
{
|
||||
int64_t budget;
|
||||
SliceBudget budget;
|
||||
if (millis)
|
||||
budget = SliceBudget::TimeBudget(millis);
|
||||
budget = SliceBudget(TimeBudget(millis));
|
||||
else if (reason == JS::gcreason::ALLOC_TRIGGER)
|
||||
budget = sliceBudget;
|
||||
budget = SliceBudget(TimeBudget(sliceBudget));
|
||||
else if (schedulingState.inHighFrequencyGCMode() && tunables.isDynamicMarkSliceEnabled())
|
||||
budget = sliceBudget * IGC_MARK_SLICE_MULTIPLIER;
|
||||
budget = SliceBudget(TimeBudget(sliceBudget * IGC_MARK_SLICE_MULTIPLIER));
|
||||
else
|
||||
budget = sliceBudget;
|
||||
budget = SliceBudget(TimeBudget(sliceBudget));
|
||||
|
||||
collect(true, budget, gckind, reason);
|
||||
}
|
||||
@ -6131,7 +6126,8 @@ GCRuntime::gcSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64
|
||||
void
|
||||
GCRuntime::gcFinalSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason)
|
||||
{
|
||||
collect(true, SliceBudget::Unlimited, gckind, reason);
|
||||
SliceBudget budget;
|
||||
collect(true, budget, gckind, reason);
|
||||
}
|
||||
|
||||
void
|
||||
@ -6174,9 +6170,8 @@ ZonesSelected(JSRuntime *rt)
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::gcDebugSlice(bool limit, int64_t objCount)
|
||||
GCRuntime::gcDebugSlice(SliceBudget &budget)
|
||||
{
|
||||
int64_t budget = limit ? SliceBudget::WorkBudget(objCount) : SliceBudget::Unlimited;
|
||||
if (!ZonesSelected(rt)) {
|
||||
if (JS::IsIncrementalGCInProgress(rt))
|
||||
JS::PrepareForIncrementalGC(rt);
|
||||
@ -6438,12 +6433,12 @@ GCRuntime::runDebugGC()
|
||||
|
||||
PrepareForDebugGC(rt);
|
||||
|
||||
SliceBudget budget;
|
||||
if (type == ZealIncrementalRootsThenFinish ||
|
||||
type == ZealIncrementalMarkAllThenFinish ||
|
||||
type == ZealIncrementalMultipleSlices)
|
||||
{
|
||||
js::gc::State initialState = incrementalState;
|
||||
int64_t budget;
|
||||
if (type == ZealIncrementalMultipleSlices) {
|
||||
/*
|
||||
* Start with a small slice limit and double it every slice. This
|
||||
@ -6454,10 +6449,10 @@ GCRuntime::runDebugGC()
|
||||
incrementalLimit = zealFrequency / 2;
|
||||
else
|
||||
incrementalLimit *= 2;
|
||||
budget = SliceBudget::WorkBudget(incrementalLimit);
|
||||
budget = SliceBudget(WorkBudget(incrementalLimit));
|
||||
} else {
|
||||
// This triggers incremental GC but is actually ignored by IncrementalMarkSlice.
|
||||
budget = SliceBudget::WorkBudget(1);
|
||||
budget = SliceBudget(WorkBudget(1));
|
||||
}
|
||||
|
||||
collect(true, budget, GC_NORMAL, JS::gcreason::DEBUG_GC);
|
||||
@ -6472,9 +6467,9 @@ GCRuntime::runDebugGC()
|
||||
incrementalLimit = zealFrequency / 2;
|
||||
}
|
||||
} else if (type == ZealCompactValue) {
|
||||
collect(false, SliceBudget::Unlimited, GC_SHRINK, JS::gcreason::DEBUG_GC);
|
||||
collect(false, budget, GC_SHRINK, JS::gcreason::DEBUG_GC);
|
||||
} else {
|
||||
collect(false, SliceBudget::Unlimited, GC_NORMAL, JS::gcreason::DEBUG_GC);
|
||||
collect(false, budget, GC_NORMAL, JS::gcreason::DEBUG_GC);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -203,7 +203,7 @@ function test()
|
||||
gc();
|
||||
buffer = null;
|
||||
views = null;
|
||||
gcslice(2); gcslice(2); gcslice(2); gcslice(2); gcslice(2); gcslice(2); gc();
|
||||
gcslice(3); gcslice(3); gcslice(3); gcslice(3); gcslice(3); gcslice(3); gc();
|
||||
}
|
||||
|
||||
var buf, buf2;
|
||||
|
@ -22,17 +22,32 @@ def error(message):
|
||||
sys.exit(1)
|
||||
|
||||
def get_xdr_version(dir):
|
||||
version_pat = re.compile('XDR_BYTECODE_VERSION = uint32_t\(0xb973c0de - (\d+)\);')
|
||||
subtrahend_pat = re.compile('XDR_BYTECODE_VERSION_SUBTRAHEND\s*=\s*(\d+);', re.S)
|
||||
version_expr_pat = re.compile('XDR_BYTECODE_VERSION\s*=\s*uint32_t\(0xb973c0de\s*-\s*(.+?)\);', re.S)
|
||||
# FIXME: Bug 1066322 - Enable ES6 symbols in all builds.
|
||||
bug1066322_pat = re.compile('#ifdef\s+JS_HAS_SYMBOLS\s+\+\s*1*\s+#endif', re.S)
|
||||
|
||||
version = ''
|
||||
with open('{dir}/js/src/vm/Xdr.h'.format(dir=dir), 'r') as f:
|
||||
data = f.read()
|
||||
|
||||
m = version_pat.search(data)
|
||||
if m:
|
||||
version = int(m.group(1))
|
||||
m = subtrahend_pat.search(data)
|
||||
if not m:
|
||||
error('XDR_BYTECODE_VERSION_SUBTRAHEND is not recognized.')
|
||||
|
||||
return version
|
||||
subtrahend = int(m.group(1))
|
||||
|
||||
m = version_expr_pat.search(data)
|
||||
if not m:
|
||||
error('XDR_BYTECODE_VERSION is not recognized.')
|
||||
|
||||
version_expr = m.group(1)
|
||||
|
||||
bug1066322 = False
|
||||
m = bug1066322_pat.search(version_expr)
|
||||
if m:
|
||||
bug1066322 = True
|
||||
|
||||
return (subtrahend, bug1066322)
|
||||
|
||||
quoted_pat = re.compile(r"([^A-Za-z0-9]|^)'([^']+)'")
|
||||
js_pat = re.compile(r"([^A-Za-z0-9]|^)(JS[A-Z0-9_\*]+)")
|
||||
@ -327,7 +342,7 @@ def print_opcode(opcode):
|
||||
def make_element_id(name):
|
||||
return name.replace(' ', '-')
|
||||
|
||||
def print_doc(version, index):
|
||||
def print_doc(version, bug1066322, index):
|
||||
print("""<h2 id="Bytecode_Listing">Bytecode Listing</h2>
|
||||
|
||||
<p>This document is automatically generated from
|
||||
@ -341,6 +356,15 @@ def print_doc(version, index):
|
||||
version=version,
|
||||
actual_version=0xb973c0de - version))
|
||||
|
||||
if bug1066322:
|
||||
symbol_version = version + 1
|
||||
print("""
|
||||
<p>Until {{{{bug(1066322)}}}} is fixed, JSOP_SYMBOL exists only in Nightly, and it uses different version.</p>
|
||||
<p>Bytecode version with JSOP_SYMBOL: <code>{version}</code>
|
||||
(<code>0x{actual_version:08x}</code>).</p>
|
||||
""".format(version=symbol_version,
|
||||
actual_version=0xb973c0de - symbol_version))
|
||||
|
||||
for (category_name, types) in index:
|
||||
print('<h3 id="{id}">{name}</h3>'.format(name=category_name,
|
||||
id=make_element_id(category_name)))
|
||||
@ -360,6 +384,6 @@ if __name__ == '__main__':
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
dir = sys.argv[1]
|
||||
version = get_xdr_version(dir)
|
||||
(version, bug1066322) = get_xdr_version(dir)
|
||||
index = get_opcodes(dir)
|
||||
print_doc(version, index)
|
||||
print_doc(version, bug1066322, index)
|
||||
|
@ -527,8 +527,7 @@ nsCSSBorderRenderer::FillSolidBorder(const Rect& aOuterRect,
|
||||
// If we have a border radius, do full rounded rectangles
|
||||
// and fill, regardless of what sides we're asked to draw.
|
||||
if (!AllCornersZeroSize(aBorderRadii)) {
|
||||
RefPtr<PathBuilder> builder =
|
||||
mDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
|
||||
RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
|
||||
|
||||
RectCornerRadii innerRadii;
|
||||
ComputeInnerRadii(aBorderRadii, aBorderSizes, &innerRadii);
|
||||
@ -1473,12 +1472,12 @@ nsCSSBorderRenderer::DrawBorders()
|
||||
// doesn't need to compute an offset curve to stroke the path. We know that
|
||||
// the rounded parts are elipses we can offset exactly and can just compute
|
||||
// a new cubic approximation.
|
||||
RefPtr<PathBuilder> builder =
|
||||
mDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
|
||||
RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
|
||||
AppendRoundedRectToPath(builder, mOuterRect, mBorderRadii, true);
|
||||
AppendRoundedRectToPath(builder, ToRect(borderInnerRect.rect), borderInnerRect.corners, false);
|
||||
RefPtr<Path> path = builder->Finish();
|
||||
mDrawTarget->Fill(path, color);
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasCompositeColors;
|
||||
|
@ -6755,7 +6755,9 @@ ShouldInflateFontsForContainer(const nsIFrame *aFrame)
|
||||
!(aFrame->GetStateBits() & NS_FRAME_IN_CONSTRAINED_HEIGHT) &&
|
||||
// We also want to disable font inflation for containers that have
|
||||
// preformatted text.
|
||||
styleText->WhiteSpaceCanWrap(aFrame);
|
||||
// MathML cells need special treatment. See bug 1002526 comment 56.
|
||||
(styleText->WhiteSpaceCanWrap(aFrame) ||
|
||||
aFrame->IsFrameOfType(nsIFrame::eMathML));
|
||||
}
|
||||
|
||||
nscoord
|
||||
@ -7224,7 +7226,9 @@ AutoMaybeDisableFontInflation::AutoMaybeDisableFontInflation(nsIFrame *aFrame)
|
||||
// root's NCA's (nearest common ancestor of its inflatable
|
||||
// descendants) width, we could probably disable inflation in
|
||||
// fewer cases than we currently do.
|
||||
if (aFrame->IsContainerForFontSizeInflation()) {
|
||||
// MathML cells need special treatment. See bug 1002526 comment 56.
|
||||
if (aFrame->IsContainerForFontSizeInflation() &&
|
||||
!aFrame->IsFrameOfType(nsIFrame::eMathML)) {
|
||||
mPresContext = aFrame->PresContext();
|
||||
mOldValue = mPresContext->mInflationDisabledForShrinkWrap;
|
||||
mPresContext->mInflationDisabledForShrinkWrap = true;
|
||||
|
@ -97,6 +97,7 @@ nsListControlFrame::nsListControlFrame(
|
||||
mMightNeedSecondPass(false),
|
||||
mHasPendingInterruptAtStartOfReflow(false),
|
||||
mDropdownCanGrow(false),
|
||||
mForceSelection(false),
|
||||
mLastDropdownComputedHeight(NS_UNCONSTRAINEDSIZE)
|
||||
{
|
||||
mComboboxFrame = nullptr;
|
||||
@ -739,9 +740,9 @@ nsListControlFrame::PerformSelection(int32_t aClickedIndex,
|
||||
{
|
||||
bool wasChanged = false;
|
||||
|
||||
if (aClickedIndex == kNothingSelected) {
|
||||
}
|
||||
else if (GetMultiple()) {
|
||||
if (aClickedIndex == kNothingSelected && !mForceSelection) {
|
||||
// Ignore kNothingSelected unless the selection is forced
|
||||
} else if (GetMultiple()) {
|
||||
if (aIsShift) {
|
||||
// Make sure shift+click actually does something expected when
|
||||
// the user has never clicked on the select
|
||||
@ -1237,10 +1238,12 @@ nsListControlFrame::SetOptionsSelectedFromFrame(int32_t aStartIndex,
|
||||
dom::HTMLSelectElement::FromContent(mContent);
|
||||
|
||||
uint32_t mask = dom::HTMLSelectElement::NOTIFY;
|
||||
if (mForceSelection) {
|
||||
mask |= dom::HTMLSelectElement::SET_DISABLED;
|
||||
}
|
||||
if (aValue) {
|
||||
mask |= dom::HTMLSelectElement::IS_SELECTED;
|
||||
}
|
||||
|
||||
if (aClearAll) {
|
||||
mask |= dom::HTMLSelectElement::CLEAR_ALL;
|
||||
}
|
||||
@ -1292,13 +1295,16 @@ nsListControlFrame::ComboboxFinish(int32_t aIndex)
|
||||
gLastKeyTime = 0;
|
||||
|
||||
if (mComboboxFrame) {
|
||||
int32_t displayIndex = mComboboxFrame->GetIndexOfDisplayArea();
|
||||
// Make sure we can always reset to the displayed index
|
||||
mForceSelection = displayIndex == aIndex;
|
||||
|
||||
nsWeakFrame weakFrame(this);
|
||||
PerformSelection(aIndex, false, false); // might destroy us
|
||||
if (!weakFrame.IsAlive() || !mComboboxFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t displayIndex = mComboboxFrame->GetIndexOfDisplayArea();
|
||||
if (displayIndex != aIndex) {
|
||||
mComboboxFrame->RedisplaySelectedText(); // might destroy us
|
||||
}
|
||||
@ -1415,6 +1421,7 @@ nsListControlFrame::AboutToDropDown()
|
||||
#endif
|
||||
}
|
||||
mItemSelectionStarted = false;
|
||||
mForceSelection = false;
|
||||
}
|
||||
|
||||
// We are about to be rolledup from the outside (ComboboxFrame)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user