diff --git a/Makefile.in b/Makefile.in index 6ae7359f5a2b..fba5cd3b3881 100644 --- a/Makefile.in +++ b/Makefile.in @@ -253,13 +253,6 @@ endif ifdef BUILD_JS js/src/Makefile: subsrcdir := js/src -ifdef ENABLE_TESTS -# Incorporate static tier directories into tests. This should be incorporated -# into moz.build files someday. -check:: - $(call SUBMAKE,$@,js/src) -endif - ifdef MOZ_PSEUDO_DERECURSE # Interdependencies for parallel export. js/xpconnect/src/export: dom/bindings/export xpcom/xpidl/export diff --git a/accessible/src/base/Logging.cpp b/accessible/src/base/Logging.cpp index 0e5b79629abc..435b79f358b1 100644 --- a/accessible/src/base/Logging.cpp +++ b/accessible/src/base/Logging.cpp @@ -99,22 +99,19 @@ LogDocShellState(nsIDocument* aDocumentNode) printf("docshell busy: "); nsAutoCString docShellBusy; - nsCOMPtr container = aDocumentNode->GetContainer(); - if (container) { - nsCOMPtr docShell = do_QueryInterface(container); - uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE; - docShell->GetBusyFlags(&busyFlags); - if (busyFlags == nsIDocShell::BUSY_FLAGS_NONE) - printf("'none'"); - if (busyFlags & nsIDocShell::BUSY_FLAGS_BUSY) - printf("'busy'"); - if (busyFlags & nsIDocShell::BUSY_FLAGS_BEFORE_PAGE_LOAD) - printf(", 'before page load'"); - if (busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING) - printf(", 'page loading'"); - } else { + nsCOMPtr docShell = aDocumentNode->GetDocShell(); + uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE; + docShell->GetBusyFlags(&busyFlags); + if (busyFlags == nsIDocShell::BUSY_FLAGS_NONE) + printf("'none'"); + if (busyFlags & nsIDocShell::BUSY_FLAGS_BUSY) + printf("'busy'"); + if (busyFlags & nsIDocShell::BUSY_FLAGS_BEFORE_PAGE_LOAD) + printf(", 'before page load'"); + if (busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING) + printf(", 'page loading'"); + printf("[failed]"); - } } static void @@ -132,8 +129,7 @@ static void LogDocShellTree(nsIDocument* aDocumentNode) { if (aDocumentNode->IsActive()) { - nsCOMPtr container = aDocumentNode->GetContainer(); - nsCOMPtr treeItem(do_QueryInterface(container)); + nsCOMPtr treeItem(aDocumentNode->GetDocShell()); nsCOMPtr parentTreeItem; treeItem->GetParent(getter_AddRefs(parentTreeItem)); nsCOMPtr rootTreeItem; diff --git a/accessible/src/base/nsAccessibilityService.cpp b/accessible/src/base/nsAccessibilityService.cpp index 5804fb435284..0ea3d82ef5f6 100644 --- a/accessible/src/base/nsAccessibilityService.cpp +++ b/accessible/src/base/nsAccessibilityService.cpp @@ -200,8 +200,7 @@ nsAccessibilityService::GetRootDocumentAccessible(nsIPresShell* aPresShell, nsIPresShell* ps = aPresShell; nsIDocument* documentNode = aPresShell->GetDocument(); if (documentNode) { - nsCOMPtr container = documentNode->GetContainer(); - nsCOMPtr treeItem(do_QueryInterface(container)); + nsCOMPtr treeItem(documentNode->GetDocShell()); if (treeItem) { nsCOMPtr rootTreeItem; treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem)); diff --git a/accessible/src/base/nsCoreUtils.cpp b/accessible/src/base/nsCoreUtils.cpp index 00ccb9a702aa..13aee385f1ff 100644 --- a/accessible/src/base/nsCoreUtils.cpp +++ b/accessible/src/base/nsCoreUtils.cpp @@ -394,17 +394,14 @@ nsCoreUtils::GetDocShellFor(nsINode *aNode) if (!aNode) return nullptr; - nsCOMPtr container = aNode->OwnerDoc()->GetContainer(); - nsCOMPtr docShell = do_QueryInterface(container); + nsCOMPtr docShell = aNode->OwnerDoc()->GetDocShell(); return docShell.forget(); } bool nsCoreUtils::IsRootDocument(nsIDocument *aDocument) { - nsCOMPtr container = aDocument->GetContainer(); - nsCOMPtr docShellTreeItem = - do_QueryInterface(container); + nsCOMPtr docShellTreeItem = aDocument->GetDocShell(); NS_ASSERTION(docShellTreeItem, "No document shell for document!"); nsCOMPtr parentTreeItem; @@ -416,9 +413,7 @@ nsCoreUtils::IsRootDocument(nsIDocument *aDocument) bool nsCoreUtils::IsContentDocument(nsIDocument *aDocument) { - nsCOMPtr container = aDocument->GetContainer(); - nsCOMPtr docShellTreeItem = - do_QueryInterface(container); + nsCOMPtr docShellTreeItem = aDocument->GetDocShell(); NS_ASSERTION(docShellTreeItem, "No document shell tree item for document!"); int32_t contentType; @@ -429,8 +424,7 @@ nsCoreUtils::IsContentDocument(nsIDocument *aDocument) bool nsCoreUtils::IsTabDocument(nsIDocument* aDocumentNode) { - nsCOMPtr container = aDocumentNode->GetContainer(); - nsCOMPtr treeItem(do_QueryInterface(container)); + nsCOMPtr treeItem(aDocumentNode->GetDocShell()); nsCOMPtr parentTreeItem; treeItem->GetParent(getter_AddRefs(parentTreeItem)); diff --git a/accessible/src/generic/Accessible.cpp b/accessible/src/generic/Accessible.cpp index 6a02c47efb22..b4b6a103b679 100644 --- a/accessible/src/generic/Accessible.cpp +++ b/accessible/src/generic/Accessible.cpp @@ -377,10 +377,8 @@ Accessible::AccessKey() const nsIDocument* document = mContent->GetCurrentDoc(); if (!document) return KeyBinding(); - nsCOMPtr container = document->GetContainer(); - if (!container) - return KeyBinding(); - nsCOMPtr treeItem(do_QueryInterface(container)); + + nsCOMPtr treeItem(document->GetDocShell()); if (!treeItem) return KeyBinding(); @@ -1294,9 +1292,7 @@ Accessible::NativeAttributes() nsCoreUtils::GetRoleContent(doc)); // Allow ARIA live region markup from outer documents to override - nsCOMPtr container = doc->GetContainer(); - nsCOMPtr docShellTreeItem = - do_QueryInterface(container); + nsCOMPtr docShellTreeItem = doc->GetDocShell(); if (!docShellTreeItem) break; diff --git a/accessible/src/generic/Accessible.h b/accessible/src/generic/Accessible.h index 61468f9abfdd..279745603305 100644 --- a/accessible/src/generic/Accessible.h +++ b/accessible/src/generic/Accessible.h @@ -244,6 +244,16 @@ public: return state; } + /** + * Return if accessible is unavailable. + */ + bool Unavailable() const + { + uint64_t state = NativelyUnavailable() ? states::UNAVAILABLE : 0; + ApplyARIAState(&state); + return state & states::UNAVAILABLE; + } + /** * Return the states of accessible, not taking into account ARIA states. * Use State() to get complete set of states. diff --git a/accessible/src/generic/DocAccessible.cpp b/accessible/src/generic/DocAccessible.cpp index 909c41ab1ba7..5963ce45c03d 100644 --- a/accessible/src/generic/DocAccessible.cpp +++ b/accessible/src/generic/DocAccessible.cpp @@ -688,8 +688,7 @@ DocAccessible::GetBoundsRect(nsRect& aBounds, nsIFrame** aRelativeFrame) nsresult DocAccessible::AddEventListeners() { - nsCOMPtr container = mDocumentNode->GetContainer(); - nsCOMPtr docShellTreeItem(do_QueryInterface(container)); + nsCOMPtr docShellTreeItem(mDocumentNode->GetDocShell()); // We want to add a command observer only if the document is content and has // an editor. @@ -721,8 +720,7 @@ DocAccessible::RemoveEventListeners() if (mDocumentNode) { mDocumentNode->RemoveObserver(this); - nsCOMPtr container = mDocumentNode->GetContainer(); - nsCOMPtr docShellTreeItem(do_QueryInterface(container)); + nsCOMPtr docShellTreeItem(mDocumentNode->GetDocShell()); NS_ASSERTION(docShellTreeItem, "doc should support nsIDocShellTreeItem."); if (docShellTreeItem) { @@ -870,7 +868,12 @@ DocAccessible::AttributeWillChange(nsIDocument* aDocument, aAttribute == nsGkAtoms::aria_pressed) { mARIAAttrOldValue = (aModType != nsIDOMMutationEvent::ADDITION) ? nsAccUtils::GetARIAToken(aElement, aAttribute) : nullptr; + return; } + + if (aAttribute == nsGkAtoms::aria_disabled || + aAttribute == nsGkAtoms::disabled) + mStateBitWasOn = accessible->Unavailable(); } void @@ -938,22 +941,24 @@ DocAccessible::AttributeChangedImpl(Accessible* aAccessible, // Universal boolean properties that don't require a role. Fire the state // change when disabled or aria-disabled attribute is set. + // Note. Checking the XUL or HTML namespace would not seem to gain us + // anything, because disabled attribute really is going to mean the same + // thing in any namespace. + // Note. We use the attribute instead of the disabled state bit because + // ARIA's aria-disabled does not affect the disabled state bit. if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::aria_disabled) { - - // Note. Checking the XUL or HTML namespace would not seem to gain us - // anything, because disabled attribute really is going to mean the same - // thing in any namespace. - - // Note. We use the attribute instead of the disabled state bit because - // ARIA's aria-disabled does not affect the disabled state bit. + // Do nothing if state wasn't changed (like @aria-disabled was removed but + // @disabled is still presented). + if (aAccessible->Unavailable() == mStateBitWasOn) + return; nsRefPtr enabledChangeEvent = - new AccStateChangeEvent(aAccessible, states::ENABLED); + new AccStateChangeEvent(aAccessible, states::ENABLED, mStateBitWasOn); FireDelayedEvent(enabledChangeEvent); nsRefPtr sensitiveChangeEvent = - new AccStateChangeEvent(aAccessible, states::SENSITIVE); + new AccStateChangeEvent(aAccessible, states::SENSITIVE, mStateBitWasOn); FireDelayedEvent(sensitiveChangeEvent); return; } @@ -1987,8 +1992,7 @@ DocAccessible::ShutdownChildrenInSubtree(Accessible* aAccessible) bool DocAccessible::IsLoadEventTarget() const { - nsCOMPtr container = mDocumentNode->GetContainer(); - nsCOMPtr treeItem = do_QueryInterface(container); + nsCOMPtr treeItem = mDocumentNode->GetDocShell(); NS_ASSERTION(treeItem, "No document shell for document!"); nsCOMPtr parentTreeItem; diff --git a/accessible/src/generic/DocAccessible.h b/accessible/src/generic/DocAccessible.h index 6de2c3d7cc2e..b17737d43077 100644 --- a/accessible/src/generic/DocAccessible.h +++ b/accessible/src/generic/DocAccessible.h @@ -533,10 +533,16 @@ protected: nsCOMPtr mAnchorJumpElm; /** - * Keep the ARIA attribute old value that is initialized by - * AttributeWillChange and used by AttributeChanged notifications. + * A generic state (see items below) before the attribute value was changed. + * @see AttributeWillChange and AttributeChanged notifications. */ - nsIAtom* mARIAAttrOldValue; + union { + // ARIA attribute value + nsIAtom* mARIAAttrOldValue; + + // True if the accessible state bit was on + bool mStateBitWasOn; + }; nsTArray > mChildDocuments; diff --git a/accessible/src/windows/msaa/IUnknownImpl.cpp b/accessible/src/windows/msaa/IUnknownImpl.cpp index f6ae9f6a132c..4a9fa5383b23 100644 --- a/accessible/src/windows/msaa/IUnknownImpl.cpp +++ b/accessible/src/windows/msaa/IUnknownImpl.cpp @@ -23,7 +23,7 @@ GetHRESULT(nsresult aResult) case NS_OK: return S_OK; - case NS_ERROR_INVALID_ARG: case NS_ERROR_INVALID_POINTER: + case NS_ERROR_INVALID_ARG: return E_INVALIDARG; case NS_ERROR_OUT_OF_MEMORY: diff --git a/accessible/tests/mochitest/events.js b/accessible/tests/mochitest/events.js index 2676a8187826..525d56d6631d 100644 --- a/accessible/tests/mochitest/events.js +++ b/accessible/tests/mochitest/events.js @@ -1959,7 +1959,12 @@ var gA11yEventObserver = var type = eventTypeToString(event.eventType); var info = "Event type: " + type; - if (event instanceof nsIAccessibleTextChangeEvent) { + if (event instanceof nsIAccessibleStateChangeEvent) { + var stateStr = statesToString(event.isExtraState ? 0 : event.state, + event.isExtraState ? event.state : 0); + info += ", state: " + stateStr + ", is enabled: " + event.isEnabled; + + } else if (event instanceof nsIAccessibleTextChangeEvent) { info += ", start: " + event.start + ", length: " + event.length + ", " + (event.isInserted ? "inserted" : "removed") + " text: " + event.modifiedText; diff --git a/accessible/tests/mochitest/events/test_statechange.html b/accessible/tests/mochitest/events/test_statechange.html index d877f6dd4b86..f4557376b548 100644 --- a/accessible/tests/mochitest/events/test_statechange.html +++ b/accessible/tests/mochitest/events/test_statechange.html @@ -143,18 +143,46 @@ new stateChangeChecker(aState, aIsExtraState, true, getNode(aID)) ]; - this.invoke = function dupeStateChange_invoke() + this.invoke = function oppositeStateChange_invoke() { getNode(aID).setAttribute(aAttr, "false"); getNode(aID).setAttribute(aAttr, "true"); } - this.getID = function dupeStateChange_getID() + this.getID = function oppositeStateChange_getID() { return "opposite state change events"; } } + /** + * Change concomitant ARIA and native attribute at once. + */ + function echoingStateChange(aID, aARIAAttr, aAttr, aValue, + aState, aIsExtraState, aIsEnabled) + { + this.eventSeq = [ + new stateChangeChecker(aState, aIsExtraState, aIsEnabled, getNode(aID)) + ]; + + this.invoke = function echoingStateChange_invoke() + { + if (aValue == null) { + getNode(aID).removeAttribute(aARIAAttr); + getNode(aID).removeAttribute(aAttr); + + } else { + getNode(aID).setAttribute(aARIAAttr, aValue); + getNode(aID).setAttribute(aAttr, aValue); + } + } + + this.getID = function echoingStateChange_getID() + { + return "enchoing ARIA and native attributes change"; + } + } + //////////////////////////////////////////////////////////////////////////// // Do tests @@ -193,6 +221,11 @@ gQueue.push(new oppositeStateChange("div", "aria-busy", STATE_BUSY, false)); + gQueue.push(new echoingStateChange("text1", "aria-disabled", "disabled", "true", + EXT_STATE_ENABLED, true, false)); + gQueue.push(new echoingStateChange("text1", "aria-disabled", "disabled", null, + EXT_STATE_ENABLED, true, true)); + gQueue.invoke(); // Will call SimpleTest.finish(); } @@ -223,6 +256,11 @@ title="Fire statechange event whenever checked state is changed not depending on focused state"> Bug 788389 + + Bug 926812 +

@@ -242,6 +280,8 @@
+ +
diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index eeace1170aaa..ab8d7cb2e790 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "14a570c0af0ad29420a47318576fc365ebf7b10a", + "revision": "89c57a31cebe0d8d94cbce4bcb47788b18f4fe84", "repo_path": "/integration/gaia-central" } diff --git a/browser/base/content/abouthealthreport/abouthealth.js b/browser/base/content/abouthealthreport/abouthealth.js index 84c054bca44b..1475ce3599ed 100644 --- a/browser/base/content/abouthealthreport/abouthealth.js +++ b/browser/base/content/abouthealthreport/abouthealth.js @@ -24,6 +24,11 @@ const prefs = new Preferences("datareporting.healthreport."); let healthReportWrapper = { init: function () { + if (!reporter) { + healthReportWrapper.handleInitFailure(); + return; + } + reporter.onInit().then(healthReportWrapper.refreshPayload, healthReportWrapper.handleInitFailure); diff --git a/browser/base/content/pageinfo/pageInfo.js b/browser/base/content/pageinfo/pageInfo.js index f3ba788a8c1b..4999a17b29a6 100644 --- a/browser/base/content/pageinfo/pageInfo.js +++ b/browser/base/content/pageinfo/pageInfo.js @@ -475,8 +475,7 @@ function openCacheEntry(key, cb) }, onCacheEntryAvailable: function(entry, isNew, appCache, status) { cb(entry); - }, - get mainThreadOnly() { return true; } + } }; diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "", nsICacheStorage.OPEN_READONLY, checkCacheListener); } diff --git a/browser/components/privatebrowsing/test/browser/browser.ini b/browser/components/privatebrowsing/test/browser/browser.ini index 493e3e1a333d..2b71c0723132 100644 --- a/browser/components/privatebrowsing/test/browser/browser.ini +++ b/browser/components/privatebrowsing/test/browser/browser.ini @@ -17,8 +17,7 @@ support-files = [browser_privatebrowsing_DownloadLastDirWithCPS.js] [browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js] [browser_privatebrowsing_aboutSessionRestore.js] -# [browser_privatebrowsing_cache.js] -# Disabled for too many intermittent failures (bug 895390) +[browser_privatebrowsing_cache.js] [browser_privatebrowsing_certexceptionsui.js] [browser_privatebrowsing_concurrent.js] [browser_privatebrowsing_cookieacceptdialog.js] diff --git a/build/automationutils.py b/build/automationutils.py index 053246683c95..e0a3364667a3 100644 --- a/build/automationutils.py +++ b/build/automationutils.py @@ -60,10 +60,6 @@ DEBUGGER_INFO = { "args": "-q --args" }, - "cgdb": { - "interactive": True, - "args": "-q --args" - }, "cgdb": { "interactive": True, "args": "-q --args" diff --git a/build/docs/index.rst b/build/docs/index.rst index 0dd663db96af..8a7cb973b782 100644 --- a/build/docs/index.rst +++ b/build/docs/index.rst @@ -16,6 +16,7 @@ Important Concepts :maxdepth: 1 build-overview + supported-configurations Mozconfig Files mozbuild-files mozbuild-symbols diff --git a/build/docs/supported-configurations.rst b/build/docs/supported-configurations.rst new file mode 100644 index 000000000000..cc2c1ea7282d --- /dev/null +++ b/build/docs/supported-configurations.rst @@ -0,0 +1,55 @@ +.. _build_supported_configurations: + +======================== +Supported Configurations +======================== + +This page attempts to document supported build configurations. + +Windows +======= + +We support building on Windows XP and newer operating systems using +Visual Studio 2010 and newer. + +The following are not fully supported by Mozilla (but may work): + +* Building without the latest *MozillaBuild* Windows development + environment +* Building with Mingw or any other non-Visual Studio toolchain. + +OS X +==== + +We support building on OS X 10.6 and newer with the OS X 10.6 SDK. + +The tree should build with the following OS X releases and SDK versions: + +* 10.6 Snow Leopard +* 10.7 Lion +* 10.8 Mountain Lion +* 10.9 Mavericks + +The tree requires building with Clang 3.3 and newer. This corresponds to +version of 4.2 of Apple's Clang that ships with Xcode. This corresponds +to Xcode 4.6 and newer. Xcode 4.6 only runs on OS X 10.7.4 and newer. +So, OS X 10.6 users will need to install a non-Apple toolchain. Running +``mach bootstrap`` should install an appropriate toolchain from Homebrew +or MacPorts automatically. + +The tree should build with GCC 4.4 and newer on OS X. However, this +build configuration isn't as widely used (and differs from what Mozilla +uses to produce OS X builds), so it's recommended to stick with Clang. + +Linux +===== + +Linux 2.6 and later kernels are supported. + +Most distributions are supported as long as the proper package +dependencies are in place. Running ``mach bootstrap`` should install +packages for popular Linux distributions. ``configure`` will typically +detect missing dependencies and inform you how to disable features to +work around unsatisfied dependencies. + +Clang 3.3 or GCC 4.4 is required to build the tree. diff --git a/chrome/src/nsChromeRegistry.cpp b/chrome/src/nsChromeRegistry.cpp index d7dabde70631..1601fe88e29e 100644 --- a/chrome/src/nsChromeRegistry.cpp +++ b/chrome/src/nsChromeRegistry.cpp @@ -27,8 +27,10 @@ #include "nsIPresShell.h" #include "nsIScriptError.h" #include "nsIWindowMediator.h" +#include "mozilla/dom/URL.h" nsChromeRegistry* nsChromeRegistry::gChromeRegistry; +using mozilla::dom::IsChromeURI; //////////////////////////////////////////////////////////////////////////////// @@ -371,14 +373,6 @@ nsChromeRegistry::FlushSkinCaches() NS_CHROME_FLUSH_SKINS_TOPIC, nullptr); } -static bool IsChromeURI(nsIURI* aURI) -{ - bool isChrome=false; - if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) - return true; - return false; -} - // XXXbsmedberg: move this to windowmediator nsresult nsChromeRegistry::RefreshWindow(nsIDOMWindow* aWindow) { diff --git a/config/check_vanilla_allocations.py b/config/check_vanilla_allocations.py new file mode 100644 index 000000000000..74ea5e61d0c0 --- /dev/null +++ b/config/check_vanilla_allocations.py @@ -0,0 +1,159 @@ +# vim: set ts=8 sts=4 et sw=4 tw=79: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#---------------------------------------------------------------------------- +# All heap allocations in SpiderMonkey must go through js_malloc, js_calloc, +# js_realloc, and js_free. This is so that any embedder who uses a custom +# allocator (by defining JS_USE_CUSTOM_ALLOCATOR) will see all heap allocation +# go through that custom allocator. +# +# Therefore, the presence of any calls to "vanilla" allocation/free functions +# (e.g. malloc(), free()) is a bug. +# +# This script checks for the presence of such disallowed vanilla +# allocation/free function in SpiderMonkey when it's built as a library. It +# relies on |nm| from the GNU binutils, and so only works on Linux, but one +# platform is good enough to catch almost all violations. +# +# This checking is only 100% reliable in a JS_USE_CUSTOM_ALLOCATOR build in +# which the default definitions of js_malloc et al (in Utility.h) -- which call +# malloc et al -- are replaced with empty definitions. This is because the +# presence and possible inlining of the default js_malloc et al can cause +# malloc/calloc/realloc/free calls show up in unpredictable places. +# +# Unfortunately, that configuration cannot be tested on Mozilla's standard +# testing infrastructure. Instead, by default this script only tests that none +# of the other vanilla allocation/free functions (operator new, memalign, etc) +# are present. If given the --aggressive flag, it will also check for +# malloc/calloc/realloc/free. +# +# Note: We don't check for |operator delete| and |operator delete[]|. These +# can be present somehow due to virtual destructors, but this is not too +# because vanilla delete/delete[] calls don't make sense without corresponding +# vanilla new/new[] calls, and any explicit calls will be caught by Valgrind's +# mismatched alloc/free checking. +#---------------------------------------------------------------------------- + +from __future__ import print_function + +import argparse +import re +import subprocess +import sys + +# The obvious way to implement this script is to search for occurrences of +# malloc et al, succeed if none are found, and fail is some are found. +# However, "none are found" does not necessarily mean "none are present" -- +# this script could be buggy. (Or the output format of |nm| might change in +# the future.) +# +# So jsutil.cpp deliberately contains a (never-called) function that contains a +# single use of all the vanilla allocation/free functions. And this script +# fails if it (a) finds uses of those functions in files other than jsutil.cpp, +# *or* (b) fails to find them in jsutil.cpp. + +# Tracks overall success of the test. +has_failed = False + + +def fail(msg): + print('TEST-UNEXPECTED-FAIL | check_vanilla_allocations.py |', msg) + global has_failed + has_failed = True + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--aggressive', action='store_true', + help='also check for malloc, calloc, realloc and free') + parser.add_argument('file', type=str, + help='name of the file to check') + args = parser.parse_args() + + # Run |nm|. Options: + # -u: show only undefined symbols + # -C: demangle symbol names + # -l: show a filename and line number for each undefined symbol + cmd = ['nm', '-u', '-C', '-l', args.file] + lines = subprocess.check_output(cmd, universal_newlines=True, + stderr=subprocess.PIPE).split('\n') + + # alloc_fns contains all the vanilla allocation/free functions that we look + # for. Regexp chars are escaped appropriately. + + alloc_fns = [ + # Matches |operator new(unsigned T)|, where |T| is |int| or |long|. + r'operator new\(unsigned', + + # Matches |operator new[](unsigned T)|, where |T| is |int| or |long|. + r'operator new\[\]\(unsigned', + + r'memalign', + # These two aren't available on all Linux configurations. + #r'posix_memalign', + #r'aligned_alloc', + r'valloc', + r'strdup' + ] + + if args.aggressive: + alloc_fns += [ + r'malloc', + r'calloc', + r'realloc', + r'free' + ] + + # This is like alloc_fns, but regexp chars are not escaped. + alloc_fns_unescaped = [fn.translate(None, r'\\') for fn in alloc_fns] + + # This regexp matches the relevant lines in the output of |nm|, which look + # like the following. + # + # U malloc /path/to/objdir/dist/include/js/Utility.h:142 + # + alloc_fns_re = r'U (' + r'|'.join(alloc_fns) + r').*\/([\w\.]+):(\d+)$' + + # This tracks which allocation/free functions have been seen in jsutil.cpp. + jsutil_cpp = set([]) + + for line in lines: + m = re.search(alloc_fns_re, line) + if m is None: + continue + + fn = m.group(1) + filename = m.group(2) + linenum = m.group(3) + if filename == 'jsutil.cpp': + jsutil_cpp.add(fn) + else: + # An allocation is present in a non-special file. Fail! + fail("'" + fn + "' present at " + filename + ':' + linenum) + + + # Check that all functions we expect are used in jsutil.cpp. (This will + # fail if the function-detection code breaks at any point.) + for fn in alloc_fns_unescaped: + if fn not in jsutil_cpp: + fail("'" + fn + "' isn't used as expected in jsutil.cpp") + else: + jsutil_cpp.remove(fn) + + # This should never happen, but check just in case. + if jsutil_cpp: + fail('unexpected allocation fns used in jsutil.cpp: ' + + ', '.join(jsutil_cpp)) + + if has_failed: + sys.exit(1) + + print('TEST-PASS | check_vanilla_allocations.py | ok') + sys.exit(0) + + +if __name__ == '__main__': + main() + diff --git a/config/find_vanilla_new_calls b/config/find_vanilla_new_calls deleted file mode 100755 index 320abc1403eb..000000000000 --- a/config/find_vanilla_new_calls +++ /dev/null @@ -1,80 +0,0 @@ -# /bin/bash - -#---------------------------------------------------------------------------- -# We must avoid using the vanilla new/new[] operators (and consequently, the -# vanilla delete/delete[] operators) in SpiderMonkey, see bug 624878 for why. -# -# This script: -# - Detects if any of the vanilla new/new[] operators are used in a file. -# Its exit code is 1 if it found some, and 0 if it didn't. -# - Doesn't detect delete/delete[] because it appears they can be present -# somehow due to virtual destructors, but this is ok because vanilla -# delete/delete[] calls don't make sense without corresponding new/new[] -# calls, and any explicit calls will be caught by Valgrind's mismatched -# alloc/free checking. -# - Doesn't detect the 'nothrow' variants, which are ok but probably still -# best avoided. -# - Is designed to only run on Linux (though it may also work on Mac); one -# platform will be enough to catch any violations. -# -# If this script fails: -# - You need to find the uses of vanilla new/delete and replace them with -# {js::OffTheBooks,JSContext,JSRuntime}::{new_,/array_new}. -# - Run this script on each of the .o files, that should narrow it down. -# - After that, one way to find them is to run 'objdump -r -C' on the -# relevant .o files. For example, you might search for 'operator new' and -# find a record like this: -# -# RELOCATION RECORDS FOR [.text._ZN3JSC14ExecutablePool6createEj]: -# OFFSET TYPE VALUE -# 00000009 R_386_PC32 __i686.get_pc_thunk.bx -# 0000000f R_386_GOTPC _GLOBAL_OFFSET_TABLE_ -# 0000001b R_386_PLT32 operator new(unsigned int) -# 0000002e R_386_PC32 JSC::ExecutablePool::ExecutablePool(unsigned int) -# 0000004a R_386_PC32 JSC::ExecutablePool::~ExecutablePool() -# 00000052 R_386_PLT32 operator delete(void*) -# -# This says that vanilla 'new' and 'delete' are both used in -# JSC::ExecutablePool::create(unsigned int). This doesn't always work, -# though. (Nb: use 'c++filt' to demangle names like -# _ZN3JSC14ExecutablePool6createEj.) -# -# If that doesn't work, use grep. -#---------------------------------------------------------------------------- - -if [ -z $1 ] ; then - echo "usage: find_vanilla_new_calls " - exit 1 -fi - -file=$1 - -if [ ! -f $file ] ; then - echo "TEST-UNEXPECTED-FAIL | find_vanilla_new_calls | file '$file' not found" - exit 1 -fi - -tmpfile1=`mktemp` -tmpfile2=`mktemp` -nm -C $file > $tmpfile1 - -# Need to double-escape '[' and ']' to stop grep from interpreting them -# specially. -grep '^operator new(unsigned int)' $tmpfile1 >> $tmpfile2 -grep '^operator new(unsigned long)' $tmpfile1 >> $tmpfile2 -grep '^operator new\\[\\](unsigned int)' $tmpfile1 >> $tmpfile2 -grep '^operator new\\[\\](unsigned long)' $tmpfile1 >> $tmpfile2 -rm -f $tmpfile1 - -if [ -s $tmpfile2 ] ; then - echo "TEST-UNEXPECTED-FAIL | find_vanilla_new_calls | found calls are listed below" - cat $tmpfile2 - echo - rm -f $tmpfile2 - exit 1 -fi - -echo "TEST-PASS | find_vanilla_new_calls | ok" -echo - -exit 0 diff --git a/configure.in b/configure.in index b5f39812ad6f..6e983105fa3d 100644 --- a/configure.in +++ b/configure.in @@ -3942,6 +3942,7 @@ MOZ_OPUS=1 MOZ_WEBM=1 MOZ_DIRECTSHOW= MOZ_WMF= +MOZ_FMP4= MOZ_WEBRTC=1 MOZ_PEERCONNECTION= MOZ_SRTP= @@ -5275,6 +5276,26 @@ if test -n "$MOZ_WMF"; then MOZ_CUBEB=1 fi; +dnl ======================================================== +dnl = Built-in fragmented MP4 support. +dnl ======================================================== +if test -n "$MOZ_WMF"; then + dnl Enable fragmented MP4 parser on Windows by default. + dnl We will also need to enable it on other platforms as we implement + dnl platform decoder support there too. + MOZ_FMP4=1 +fi + +MOZ_ARG_DISABLE_BOOL(fmp4, +[ --disable-fmp4 Disable support for in built Fragmented MP4 parsing], + MOZ_FMP4=, + MOZ_FMP4=1) + +if test -n "$MOZ_FMP4"; then + AC_DEFINE(MOZ_FMP4) +fi; + + dnl ======================================================== dnl = Enable media plugin support dnl ======================================================== @@ -8627,6 +8648,7 @@ AC_SUBST(MOZ_TREMOR) AC_SUBST(MOZ_OPUS) AC_SUBST(MOZ_WEBM) AC_SUBST(MOZ_WMF) +AC_SUBST(MOZ_FMP4) AC_SUBST(MOZ_DIRECTSHOW) AC_SUBST(MOZ_MEDIA_PLUGINS) AC_SUBST(MOZ_APPLEMEDIA) diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index 1e8b5825fe0f..09275fb98ac4 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -1886,6 +1886,15 @@ public: */ static bool IsSubDocumentTabbable(nsIContent* aContent); + /** + * Returns if aNode ignores user focus. + * + * @param aNode node to test + * + * @return Whether the node ignores user focus. + */ + static bool IsUserFocusIgnored(nsINode* aNode); + /** * Flushes the layout tree (recursively) * diff --git a/content/base/public/nsIContent.h b/content/base/public/nsIContent.h index a81dd3f656e4..a9a668e1ed57 100644 --- a/content/base/public/nsIContent.h +++ b/content/base/public/nsIContent.h @@ -34,8 +34,8 @@ enum nsLinkState { // IID for the nsIContent interface #define NS_ICONTENT_IID \ -{ 0x976f4cd1, 0xbdfc, 0x4a1e, \ - { 0x82, 0x46, 0x1c, 0x13, 0x9c, 0xd3, 0x73, 0x7f } } +{ 0x34117ca3, 0x45d0, 0x479e, \ + { 0x91, 0x30, 0x54, 0x49, 0xa9, 0x5f, 0x25, 0x99 } } /** * A node of content in a document's content model. This interface @@ -557,12 +557,8 @@ public: * > 0 can be tabbed to in the order specified by this value * @return whether the content is focusable via mouse, kbd or script. */ - virtual bool IsFocusable(int32_t *aTabIndex = nullptr, bool aWithMouse = false) - { - if (aTabIndex) - *aTabIndex = -1; // Default, not tabbable - return false; - } + bool IsFocusable(int32_t* aTabIndex = nullptr, bool aWithMouse = false); + virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse); /** * The method focuses (or activates) element that accesskey is bound to. It is diff --git a/content/base/public/nsIDocument.h b/content/base/public/nsIDocument.h index 1e94bb1fea7b..3dd94806355b 100644 --- a/content/base/public/nsIDocument.h +++ b/content/base/public/nsIDocument.h @@ -20,6 +20,7 @@ #include "nsPropertyTable.h" // for member #include "nsTHashtable.h" // for member #include "mozilla/dom/DocumentBinding.h" +#include "mozilla/WeakPtr.h" #include "Units.h" #include "nsExpirationTracker.h" #include "nsClassHashtable.h" @@ -28,6 +29,8 @@ class imgIRequest; class nsAString; class nsBindingManager; class nsCSSStyleSheet; +class nsIDocShell; +class nsDocShell; class nsDOMNavigationTiming; class nsDOMTouchList; class nsEventStates; @@ -1150,22 +1153,23 @@ public: * Set the container (docshell) for this document. Virtual so that * docshell can call it. */ - virtual void SetContainer(nsISupports *aContainer); + virtual void SetContainer(nsDocShell* aContainer); /** * Get the container (docshell) for this document. */ - already_AddRefed GetContainer() const - { - nsCOMPtr container = do_QueryReferent(mDocumentContainer); - return container.forget(); - } + virtual nsISupports* GetContainer() const; /** * Get the container's load context for this document. */ nsILoadContext* GetLoadContext() const; + /** + * Get docshell the for this document. + */ + nsIDocShell* GetDocShell() const; + /** * Set and get XML declaration. If aVersion is null there is no declaration. * aStandalone takes values -1, 0 and 1 indicating respectively that there @@ -1526,7 +1530,7 @@ public: void SetDisplayDocument(nsIDocument* aDisplayDocument) { NS_PRECONDITION(!GetShell() && - !nsCOMPtr(GetContainer()) && + !GetContainer() && !GetWindow(), "Shouldn't set mDisplayDocument on documents that already " "have a presentation or a docshell or a window"); @@ -1696,7 +1700,7 @@ public: * @param aCloneContainer The container for the clone document. */ virtual already_AddRefed - CreateStaticClone(nsISupports* aCloneContainer); + CreateStaticClone(nsIDocShell* aCloneContainer); /** * If this document is a static clone, this returns the original @@ -2227,7 +2231,7 @@ protected: nsWeakPtr mDocumentLoadGroup; - nsWeakPtr mDocumentContainer; + mozilla::WeakPtr mDocumentContainer; nsCString mCharacterSet; int32_t mCharacterSetSource; @@ -2390,6 +2394,14 @@ protected: // caches. bool mDidDocumentOpen; +#ifdef DEBUG + /** + * This is true while FlushPendingLinkUpdates executes. Calls to + * [Un]RegisterPendingLinkUpdate will assert when this is true. + */ + bool mIsLinkUpdateRegistrationsForbidden; +#endif + // The document's script global object, the object from which the // document can get its script context and scope. This is the // *inner* window object. diff --git a/content/base/public/nsIXMLHttpRequest.idl b/content/base/public/nsIXMLHttpRequest.idl index 5ea99210820e..9cab424bc3c7 100644 --- a/content/base/public/nsIXMLHttpRequest.idl +++ b/content/base/public/nsIXMLHttpRequest.idl @@ -16,7 +16,7 @@ interface nsIGlobalObject; interface nsIInputStream; interface nsIDOMBlob; -[scriptable, builtinclass, uuid(ac97e161-9f1d-4163-adc9-e9a59e18682c)] +[scriptable, builtinclass, uuid(5ced7e7a-e2c3-4563-a57d-31b97ce64dc5)] interface nsIXMLHttpRequestEventTarget : nsIDOMEventTarget { // event handler attributes }; diff --git a/content/base/src/FragmentOrElement.cpp b/content/base/src/FragmentOrElement.cpp index 5530891fa00b..702f62232600 100644 --- a/content/base/src/FragmentOrElement.cpp +++ b/content/base/src/FragmentOrElement.cpp @@ -838,6 +838,33 @@ nsIContent::AttrValueIs(int32_t aNameSpaceID, AsElement()->AttrValueIs(aNameSpaceID, aName, aValue, aCaseSensitive); } +bool +nsIContent::IsFocusable(int32_t* aTabIndex, bool aWithMouse) +{ + bool focusable = IsFocusableInternal(aTabIndex, aWithMouse); + // Ensure that the return value and aTabIndex are consistent in the case + // we're in userfocusignored context. + if (focusable || (aTabIndex && *aTabIndex != -1)) { + if (nsContentUtils::IsUserFocusIgnored(this)) { + if (aTabIndex) { + *aTabIndex = -1; + } + return false; + } + return focusable; + } + return false; +} + +bool +nsIContent::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) +{ + if (aTabIndex) { + *aTabIndex = -1; // Default, not tabbable + } + return false; +} + const nsAttrValue* FragmentOrElement::DoGetClasses() const { diff --git a/content/base/src/moz.build b/content/base/src/moz.build index 1de7ffd911ec..d17d7e434edf 100644 --- a/content/base/src/moz.build +++ b/content/base/src/moz.build @@ -201,6 +201,7 @@ LOCAL_INCLUDES += [ '/content/xslt/src/xpath', '/content/xul/content/src', '/content/xul/document/src', + '/docshell/base', '/dom/base', '/dom/ipc', '/dom/workers', diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index 787a1d5fd2c7..34f3a9d8fc35 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -74,6 +74,7 @@ #include "nsEventStateManager.h" #include "nsFocusManager.h" #include "nsGenericHTMLElement.h" +#include "nsGenericHTMLFrameElement.h" #include "nsGkAtoms.h" #include "nsHostObjectProtocolHandler.h" #include "nsHtml5Module.h" @@ -378,6 +379,9 @@ nsContentUtils::Init() return NS_ERROR_FAILURE; NS_ADDREF(sSecurityManager); + // Getting the first context can trigger GC, so do this non-lazily. + sXPConnect->InitSafeJSContext(); + rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService); if (NS_FAILED(rv)) { // This makes life easier, but we can live without it. @@ -387,7 +391,7 @@ nsContentUtils::Init() rv = CallGetService(NS_LBRK_CONTRACTID, &sLineBreaker); NS_ENSURE_SUCCESS(rv, rv); - + rv = CallGetService(NS_WBRK_CONTRACTID, &sWordBreaker); NS_ENSURE_SUCCESS(rv, rv); @@ -2622,10 +2626,7 @@ nsContentUtils::CanLoadImage(nsIURI* aURI, nsISupports* aContext, uint32_t appType = nsIDocShell::APP_TYPE_UNKNOWN; { - nsCOMPtr container = aLoadingDocument->GetContainer(); - nsCOMPtr docShellTreeItem = - do_QueryInterface(container); - + nsCOMPtr docShellTreeItem = aLoadingDocument->GetDocShell(); if (docShellTreeItem) { nsCOMPtr root; docShellTreeItem->GetRootTreeItem(getter_AddRefs(root)); @@ -3153,8 +3154,7 @@ nsContentUtils::IsChromeDoc(nsIDocument *aDocument) bool nsContentUtils::IsChildOfSameType(nsIDocument* aDoc) { - nsCOMPtr container = aDoc->GetContainer(); - nsCOMPtr docShellAsItem(do_QueryInterface(container)); + nsCOMPtr docShellAsItem(aDoc->GetDocShell()); nsCOMPtr sameTypeParent; if (docShellAsItem) { docShellAsItem->GetSameTypeParent(getter_AddRefs(sameTypeParent)); @@ -3235,8 +3235,7 @@ nsContentUtils::IsInChromeDocshell(nsIDocument *aDocument) return IsInChromeDocshell(aDocument->GetDisplayDocument()); } - nsCOMPtr docContainer = aDocument->GetContainer(); - nsCOMPtr docShell(do_QueryInterface(docContainer)); + nsCOMPtr docShell(aDocument->GetDocShell()); int32_t itemType = nsIDocShellTreeItem::typeContent; if (docShell) { docShell->GetItemType(&itemType); @@ -4976,8 +4975,7 @@ nsContentUtils::HidePopupsInDocument(nsIDocument* aDocument) #ifdef MOZ_XUL nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); if (pm && aDocument) { - nsCOMPtr container = aDocument->GetContainer(); - nsCOMPtr docShellToHide = do_QueryInterface(container); + nsCOMPtr docShellToHide = aDocument->GetDocShell(); if (docShellToHide) pm->HidePopupsInDocShell(docShellToHide); } @@ -5877,8 +5875,7 @@ nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent) return false; } - nsCOMPtr container = subDoc->GetContainer(); - nsCOMPtr docShell = do_QueryInterface(container); + nsCOMPtr docShell = subDoc->GetDocShell(); if (!docShell) { return false; } @@ -5898,6 +5895,30 @@ nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent) return !zombieViewer; } +bool +nsContentUtils::IsUserFocusIgnored(nsINode* aNode) +{ + if (!nsGenericHTMLFrameElement::BrowserFramesEnabled()) { + return false; + } + + // Check if our mozbrowser iframe ancestors has ignoreuserfocus attribute. + while (aNode) { + nsCOMPtr browserFrame = do_QueryInterface(aNode); + if (browserFrame && + aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::ignoreuserfocus) && + browserFrame->GetReallyIsBrowserOrApp()) { + return true; + } + nsPIDOMWindow* win = aNode->OwnerDoc()->GetWindow(); + if (win) { + aNode = win->GetFrameElementInternal(); + } + } + + return false; +} + void nsContentUtils::FlushLayoutForTree(nsIDOMWindow* aWindow) { @@ -5988,8 +6009,7 @@ nsContentUtils::FindPresShellForDocument(const nsIDocument* aDoc) return shell; } - nsCOMPtr container = doc->GetContainer(); - nsCOMPtr docShellTreeItem = do_QueryInterface(container); + nsCOMPtr docShellTreeItem = doc->GetDocShell(); while (docShellTreeItem) { // We may be in a display:none subdocument, or we may not have a presshell // created yet. diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 89fbdb9c40c5..8a44a60dbe16 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -10,6 +10,7 @@ #include "nsDocument.h" +#include "mozilla/AutoRestore.h" #include "mozilla/DebugOnly.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Util.h" @@ -33,7 +34,7 @@ #include "nsIBaseWindow.h" #include "mozilla/css/Loader.h" #include "mozilla/css/ImageLoader.h" -#include "nsIDocShell.h" +#include "nsDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsCOMArray.h" #include "nsDOMClassInfo.h" @@ -2203,7 +2204,7 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup, nsIScriptSecurityManager *securityManager = nsContentUtils::GetSecurityManager(); if (securityManager) { - nsCOMPtr docShell = do_QueryReferent(mDocumentContainer); + nsCOMPtr docShell(mDocumentContainer); if (!docShell && aLoadGroup) { nsCOMPtr cbs; @@ -2699,7 +2700,7 @@ nsDocument::InitCSP(nsIChannel* aChannel) } // ----- Enforce frame-ancestor policy on any applied policies - nsCOMPtr docShell = do_QueryReferent(mDocumentContainer); + nsCOMPtr docShell(mDocumentContainer); if (docShell) { bool safeAncestry = false; @@ -2930,7 +2931,7 @@ nsresult nsDocument::GetAllowPlugins(bool * aAllowPlugins) { // First, we ask our docshell if it allows plugins. - nsCOMPtr docShell = do_QueryReferent(mDocumentContainer); + nsCOMPtr docShell(mDocumentContainer); if (docShell) { docShell->GetAllowPlugins(aAllowPlugins); @@ -3398,7 +3399,7 @@ nsDocument::SetHeaderData(nsIAtom* aHeaderField, const nsAString& aData) if (aHeaderField == nsGkAtoms::refresh) { // We get into this code before we have a script global yet, so get to // our container via mDocumentContainer. - nsCOMPtr refresher = do_QueryReferent(mDocumentContainer); + nsCOMPtr refresher(mDocumentContainer); if (refresher) { // Note: using mDocumentURI instead of mBaseURI here, for consistency // (used to just use the current URI of our webnavigation, but that @@ -3479,7 +3480,7 @@ nsDocument::doCreateShell(nsPresContext* aContext, mPresShell = shell; // Make sure to never paint if we belong to an invisible DocShell. - nsCOMPtr docShell = do_QueryReferent(mDocumentContainer); + nsCOMPtr docShell(mDocumentContainer); if (docShell && docShell->IsInvisible()) shell->SetNeverPainting(true); @@ -4212,29 +4213,41 @@ NotifyActivityChanged(nsIContent *aContent, void *aUnused) } void -nsIDocument::SetContainer(nsISupports* aContainer) +nsIDocument::SetContainer(nsDocShell* aContainer) { - mDocumentContainer = do_GetWeakReference(aContainer); - EnumerateFreezableElements(NotifyActivityChanged, nullptr); - // Get the Docshell - nsCOMPtr docShell = do_QueryInterface(aContainer); - if (docShell) { - int32_t itemType; - docShell->GetItemType(&itemType); - // check itemtype - if (itemType == nsIDocShellTreeItem::typeContent) { - // check if same type root - nsCOMPtr sameTypeRoot; - docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); - NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!"); + if (aContainer) { + mDocumentContainer = aContainer->asWeakPtr(); + } else { + mDocumentContainer = WeakPtr(); + } - if (sameTypeRoot == docShell) { - static_cast(this)->SetIsTopLevelContentDocument(true); - } + EnumerateFreezableElements(NotifyActivityChanged, nullptr); + if (!aContainer) { + return; + } + + // Get the Docshell + int32_t itemType; + aContainer->GetItemType(&itemType); + // check itemtype + if (itemType == nsIDocShellTreeItem::typeContent) { + // check if same type root + nsCOMPtr sameTypeRoot; + aContainer->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); + NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!"); + + if (sameTypeRoot == aContainer) { + static_cast(this)->SetIsTopLevelContentDocument(true); } } } +nsISupports* +nsIDocument::GetContainer() const +{ + return static_cast(mDocumentContainer); +} + void nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject) { @@ -4292,7 +4305,7 @@ nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject) #endif if (mAllowDNSPrefetch) { - nsCOMPtr docShell = do_QueryReferent(mDocumentContainer); + nsCOMPtr docShell(mDocumentContainer); if (docShell) { #ifdef DEBUG nsCOMPtr webNav = @@ -4391,8 +4404,7 @@ nsDocument::GetWindowInternal() const // the docshell, the outer window might be still obtainable from the it. nsCOMPtr win; if (mRemovedFromDocShell) { - nsCOMPtr requestor = - do_QueryReferent(mDocumentContainer); + nsCOMPtr requestor(mDocumentContainer); if (requestor) { // The docshell returns the outer window we are done. win = do_GetInterface(requestor); @@ -7819,7 +7831,7 @@ nsDocument::GetLayoutHistoryState() const if (!mScriptGlobalObject) { state = mLayoutHistoryState; } else { - nsCOMPtr docShell(do_QueryReferent(mDocumentContainer)); + nsCOMPtr docShell(mDocumentContainer); if (docShell) { docShell->GetLayoutHistoryState(getter_AddRefs(state)); } @@ -8340,7 +8352,7 @@ nsDocument::CloneDocHelper(nsDocument* clone) const // |mDocumentContainer| is the container of the document that is being // created and not the original container. See CreateStaticClone function(). - nsCOMPtr docLoader = do_QueryReferent(mDocumentContainer); + nsCOMPtr docLoader(mDocumentContainer); if (docLoader) { docLoader->GetLoadGroup(getter_AddRefs(loadGroup)); } @@ -8353,8 +8365,7 @@ nsDocument::CloneDocHelper(nsDocument* clone) const clone->ResetToURI(uri, loadGroup, NodePrincipal()); } } - nsCOMPtr container = GetContainer(); - clone->SetContainer(container); + clone->SetContainer(mDocumentContainer); } // Set scripting object @@ -8832,6 +8843,7 @@ nsIDocument::EnumerateFreezableElements(FreezableElementEnumerator aEnumerator, void nsIDocument::RegisterPendingLinkUpdate(Link* aLink) { + MOZ_ASSERT(!mIsLinkUpdateRegistrationsForbidden); mLinksToUpdate.PutEntry(aLink); mHasLinksToUpdate = true; } @@ -8839,6 +8851,7 @@ nsIDocument::RegisterPendingLinkUpdate(Link* aLink) void nsIDocument::UnregisterPendingLinkUpdate(Link* aLink) { + MOZ_ASSERT(!mIsLinkUpdateRegistrationsForbidden); if (!mHasLinksToUpdate) return; @@ -8855,28 +8868,32 @@ EnumeratePendingLinkUpdates(nsPtrHashKey* aEntry, void* aData) void nsIDocument::FlushPendingLinkUpdates() { + MOZ_ASSERT(!mIsLinkUpdateRegistrationsForbidden); if (!mHasLinksToUpdate) return; - nsAutoScriptBlocker scriptBlocker; +#ifdef DEBUG + AutoRestore saved(mIsLinkUpdateRegistrationsForbidden); + mIsLinkUpdateRegistrationsForbidden = true; +#endif mLinksToUpdate.EnumerateEntries(EnumeratePendingLinkUpdates, nullptr); mLinksToUpdate.Clear(); mHasLinksToUpdate = false; } already_AddRefed -nsIDocument::CreateStaticClone(nsISupports* aCloneContainer) +nsIDocument::CreateStaticClone(nsIDocShell* aCloneContainer) { nsCOMPtr domDoc = do_QueryInterface(this); NS_ENSURE_TRUE(domDoc, nullptr); mCreatingStaticClone = true; // Make document use different container during cloning. - nsCOMPtr originalContainer = GetContainer(); - SetContainer(aCloneContainer); + nsRefPtr originalShell = mDocumentContainer.get(); + SetContainer(static_cast(aCloneContainer)); nsCOMPtr clonedNode; nsresult rv = domDoc->CloneNode(true, 1, getter_AddRefs(clonedNode)); - SetContainer(originalContainer); + SetContainer(originalShell); nsCOMPtr clonedDoc; if (NS_SUCCEEDED(rv)) { @@ -10182,8 +10199,7 @@ nsDocument::FullScreenStackTop() static bool IsInActiveTab(nsIDocument* aDoc) { - nsCOMPtr container = aDoc->GetContainer(); - nsCOMPtr docshell = do_QueryInterface(container); + nsCOMPtr docshell = aDoc->GetDocShell(); if (!docshell) { return false; } @@ -10194,12 +10210,8 @@ IsInActiveTab(nsIDocument* aDoc) return false; } - nsCOMPtr dsti = do_QueryInterface(container); - if (!dsti) { - return false; - } nsCOMPtr rootItem; - dsti->GetRootTreeItem(getter_AddRefs(rootItem)); + docshell->GetRootTreeItem(getter_AddRefs(rootItem)); if (!rootItem) { return false; } @@ -10544,7 +10556,7 @@ nsDocument::IsFullScreenEnabled(bool aCallerIsChrome, bool aLogFailure) // Ensure that all ancestor + + diff --git a/content/html/content/test/forms/test_stepup_stepdown.html b/content/html/content/test/forms/test_stepup_stepdown.html index aa14e23a901f..055fbd5b6a29 100644 --- a/content/html/content/test/forms/test_stepup_stepdown.html +++ b/content/html/content/test/forms/test_stepup_stepdown.html @@ -169,7 +169,7 @@ function checkStepDown() [ '10', '2', '0', '4', '10', '0', false ], [ '10', '2', '0', '4', '5', '0', false ], // value = "" (NaN). - [ '', null, null, null, null, '', false ], + [ '', null, null, null, null, '-1', false ], // With step = 'any'. [ '0', 'any', null, null, 1, null, true ], [ '0', 'ANY', null, null, 1, null, true ], @@ -304,7 +304,7 @@ function checkStepDown() [ '1970-03-08', '3', '1970-02-01', '1970-02-07', 15, '1970-02-01', false ], [ '1970-01-10', '3', '1970-01-01', '1970-01-06', 2, '1970-01-04', false ], // value = "" (NaN). - [ '', null, null, null, null, '', false ], + [ '', null, null, null, null, '1969-12-31', false ], // With step = 'any'. [ '2012-01-01', 'any', null, null, 1, null, true ], [ '2012-01-01', 'ANY', null, null, 1, null, true ], @@ -368,7 +368,7 @@ function checkStepDown() [ '17:22', '180', '17:00', '17:20', 15, '17:00', false ], [ '17:22', '180', '17:10', '17:20', 2, '17:16', false ], // value = "" (NaN). - [ '', null, null, null, null, '', false ], + [ '', null, null, null, null, '23:59', false ], // With step = 'any'. [ '17:26', 'any', null, null, 1, null, true ], [ '17:26', 'ANY', null, null, 1, null, true ], @@ -484,7 +484,7 @@ function checkStepUp() [ '-3', '2', '-6', '-2', null, '-2', false ], [ '-3', '2', '-6', '-1', null, '-2', false ], // value = "" (NaN). - [ '', null, null, null, null, '', false ], + [ '', null, null, null, null, '1', false ], // With step = 'any'. [ '0', 'any', null, null, 1, null, true ], [ '0', 'ANY', null, null, 1, null, true ], @@ -619,7 +619,7 @@ function checkStepUp() [ '1970-01-01', '3', '1970-02-01', '1970-02-07', 15, '1970-02-07', false ], [ '1970-01-01', '3', '1970-01-01', '1970-01-06', 2, '1970-01-04', false ], // value = "" (NaN). - [ '', null, null, null, null, '', false ], + [ '', null, null, null, null, '1970-01-02', false ], // With step = 'any'. [ '2012-01-01', 'any', null, null, 1, null, true ], [ '2012-01-01', 'ANY', null, null, 1, null, true ], @@ -683,7 +683,7 @@ function checkStepUp() [ '17:22', '180', '17:00', '17:20', 15, '17:22', false ], [ '17:22', '180', '17:10', '17:20', 2, '17:22', false ], // value = "" (NaN). - [ '', null, null, null, null, '', false ], + [ '', null, null, null, null, '00:01', false ], // With step = 'any'. [ '17:26', 'any', null, null, 1, null, true ], [ '17:26', 'ANY', null, null, 1, null, true ], diff --git a/content/html/content/test/mochitest.ini b/content/html/content/test/mochitest.ini index f4246a1ff634..fd8ffc6366fe 100644 --- a/content/html/content/test/mochitest.ini +++ b/content/html/content/test/mochitest.ini @@ -154,6 +154,7 @@ support-files = reflect.js wakelock.ogg wakelock.ogv + file_ignoreuserfocus.html [test_a_text.html] [test_anchor_href_cache_invalidation.html] @@ -416,3 +417,4 @@ support-files = [test_undoManager.html] [test_video_wakelock.html] [test_input_files_not_nsIFile.html] +[test_ignoreuserfocus.html] diff --git a/content/html/content/test/test_ignoreuserfocus.html b/content/html/content/test/test_ignoreuserfocus.html new file mode 100644 index 000000000000..64ce2c0c1466 --- /dev/null +++ b/content/html/content/test/test_ignoreuserfocus.html @@ -0,0 +1,146 @@ + + + + + + + + + + + diff --git a/content/html/document/reftests/bug917595-1-ref.html b/content/html/document/reftests/bug917595-1-ref.html new file mode 100644 index 000000000000..6bb9e2dc92e4 --- /dev/null +++ b/content/html/document/reftests/bug917595-1-ref.html @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/content/html/document/reftests/bug917595-exif-rotated.jpg b/content/html/document/reftests/bug917595-exif-rotated.jpg new file mode 100644 index 000000000000..e7b0c22f3510 Binary files /dev/null and b/content/html/document/reftests/bug917595-exif-rotated.jpg differ diff --git a/content/html/document/reftests/bug917595-iframe-1.html b/content/html/document/reftests/bug917595-iframe-1.html new file mode 100644 index 000000000000..032c4f4ff58c --- /dev/null +++ b/content/html/document/reftests/bug917595-iframe-1.html @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/content/html/document/reftests/bug917595-pixel-rotated.jpg b/content/html/document/reftests/bug917595-pixel-rotated.jpg new file mode 100644 index 000000000000..ac39faadadd9 Binary files /dev/null and b/content/html/document/reftests/bug917595-pixel-rotated.jpg differ diff --git a/content/html/document/reftests/bug917595-unrotated.jpg b/content/html/document/reftests/bug917595-unrotated.jpg new file mode 100644 index 000000000000..a787797c5e78 Binary files /dev/null and b/content/html/document/reftests/bug917595-unrotated.jpg differ diff --git a/content/html/document/reftests/reftests.list b/content/html/document/reftests/reftests.list index 50ee465ffe0f..530d55867fda 100644 --- a/content/html/document/reftests/reftests.list +++ b/content/html/document/reftests/reftests.list @@ -3,3 +3,10 @@ == bug448564-4a.html bug448564-4b.html == bug502168-1_malformed.html bug502168-1_well-formed.html + +# Test that image documents taken into account CSS properties like +# image-orientation when determining the size of the image. +# (Fuzzy necessary due to pixel-wise comparison of different JPEGs. +# The vast majority of the fuzziness comes from Linux and WinXP.) +fuzzy(1,149) == bug917595-iframe-1.html bug917595-1-ref.html +fuzzy(2,446) == bug917595-exif-rotated.jpg bug917595-pixel-rotated.jpg diff --git a/content/html/document/src/ImageDocument.cpp b/content/html/document/src/ImageDocument.cpp index ebe217d0ccc6..bd242f2df174 100644 --- a/content/html/document/src/ImageDocument.cpp +++ b/content/html/document/src/ImageDocument.cpp @@ -8,6 +8,7 @@ #include "nsRect.h" #include "nsIImageLoadingContent.h" #include "nsGenericHTMLElement.h" +#include "nsDocShell.h" #include "nsIDocumentInlines.h" #include "nsDOMTokenList.h" #include "nsIDOMHTMLImageElement.h" @@ -15,6 +16,7 @@ #include "nsIDOMKeyEvent.h" #include "nsIDOMMouseEvent.h" #include "nsIDOMEventListener.h" +#include "nsIFrame.h" #include "nsGkAtoms.h" #include "imgIRequest.h" #include "imgILoader.h" @@ -209,6 +211,7 @@ ImageDocument::Destroy() if (mImageContent) { // Remove our event listener from the image content. nsCOMPtr target = do_QueryInterface(mImageContent); + target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false); target->RemoveEventListener(NS_LITERAL_STRING("click"), this, false); // Break reference cycle with mImageContent, if we have one @@ -253,6 +256,7 @@ ImageDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject) NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create synthetic document"); target = do_QueryInterface(mImageContent); + target->AddEventListener(NS_LITERAL_STRING("load"), this, false); target->AddEventListener(NS_LITERAL_STRING("click"), this, false); } @@ -509,8 +513,11 @@ ImageDocument::SetModeClass(eModeClasses mode) nsresult ImageDocument::OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage) { + // Styles have not yet been applied, so we don't know the final size. For now, + // default to the image's intrinsic size. aImage->GetWidth(&mImageWidth); aImage->GetHeight(&mImageHeight); + nsCOMPtr runnable = NS_NewRunnableMethod(this, &ImageDocument::DefaultCheckOverflowing); nsContentUtils::AddScriptRunner(runnable); @@ -573,11 +580,44 @@ ImageDocument::HandleEvent(nsIDOMEvent* aEvent) else if (mImageIsOverflowing) { ShrinkToFit(); } + } else if (eventType.EqualsLiteral("load")) { + UpdateSizeFromLayout(); } return NS_OK; } +void +ImageDocument::UpdateSizeFromLayout() +{ + // Pull an updated size from the content frame to account for any size + // change due to CSS properties like |image-orientation|. + Element* contentElement = mImageContent->AsElement(); + if (!contentElement) { + return; + } + + nsIFrame* contentFrame = contentElement->GetPrimaryFrame(Flush_Frames); + if (!contentFrame) { + return; + } + + nsIntSize oldSize(mImageWidth, mImageHeight); + IntrinsicSize newSize = contentFrame->GetIntrinsicSize(); + + if (newSize.width.GetUnit() == eStyleUnit_Coord) { + mImageWidth = nsPresContext::AppUnitsToFloatCSSPixels(newSize.width.GetCoordValue()); + } + if (newSize.height.GetUnit() == eStyleUnit_Coord) { + mImageHeight = nsPresContext::AppUnitsToFloatCSSPixels(newSize.height.GetCoordValue()); + } + + // Ensure that our information about overflow is up-to-date if needed. + if (mImageWidth != oldSize.width || mImageHeight != oldSize.height) { + CheckOverflowing(false); + } +} + nsresult ImageDocument::CreateSyntheticDocument() { @@ -724,7 +764,7 @@ ImageDocument::UpdateTitleAndCharset() void ImageDocument::ResetZoomLevel() { - nsCOMPtr docShell = do_QueryReferent(mDocumentContainer); + nsCOMPtr docShell(mDocumentContainer); if (docShell) { if (nsContentUtils::IsChildOfSameType(this)) { return; @@ -743,7 +783,7 @@ float ImageDocument::GetZoomLevel() { float zoomLevel = mOriginalZoomLevel; - nsCOMPtr docShell = do_QueryReferent(mDocumentContainer); + nsCOMPtr docShell(mDocumentContainer); if (docShell) { nsCOMPtr cv; docShell->GetContentViewer(getter_AddRefs(cv)); diff --git a/content/html/document/src/ImageDocument.h b/content/html/document/src/ImageDocument.h index d431b29a4ff9..45de6062deb2 100644 --- a/content/html/document/src/ImageDocument.h +++ b/content/html/document/src/ImageDocument.h @@ -94,6 +94,8 @@ protected: void ResetZoomLevel(); float GetZoomLevel(); + void UpdateSizeFromLayout(); + enum eModeClasses { eNone, eShrinkToFit, diff --git a/content/html/document/src/moz.build b/content/html/document/src/moz.build index 07692ca68e24..d11996ce4c48 100644 --- a/content/html/document/src/moz.build +++ b/content/html/document/src/moz.build @@ -32,6 +32,7 @@ LOCAL_INCLUDES += [ '/caps/include', '/content/base/src', '/content/events/src', + '/docshell/base', '/dom/base', '/layout/style', '/xpcom/ds', diff --git a/content/html/document/src/nsHTMLDocument.cpp b/content/html/document/src/nsHTMLDocument.cpp index f68682f53e45..1e46d56a322f 100644 --- a/content/html/document/src/nsHTMLDocument.cpp +++ b/content/html/document/src/nsHTMLDocument.cpp @@ -32,7 +32,7 @@ #include "nsIContentViewerContainer.h" #include "nsIContentViewer.h" #include "nsIMarkupDocumentViewer.h" -#include "nsIDocShell.h" +#include "nsDocShell.h" #include "nsDocShellLoadTypes.h" #include "nsIWebNavigation.h" #include "nsIBaseWindow.h" @@ -1362,7 +1362,7 @@ nsHTMLDocument::Open(JSContext* cx, } // check whether we're in the middle of unload. If so, ignore this call. - nsCOMPtr shell = do_QueryReferent(mDocumentContainer); + nsCOMPtr shell(mDocumentContainer); if (!shell) { // We won't be able to create a parser anyway. nsCOMPtr ret = this; diff --git a/content/mathml/content/src/nsMathMLElement.cpp b/content/mathml/content/src/nsMathMLElement.cpp index d7408a18fbae..7ec1c5e14a36 100644 --- a/content/mathml/content/src/nsMathMLElement.cpp +++ b/content/mathml/content/src/nsMathMLElement.cpp @@ -815,7 +815,7 @@ nsMathMLElement::SetIncrementScriptLevel(bool aIncrementScriptLevel, } bool -nsMathMLElement::IsFocusable(int32_t *aTabIndex, bool aWithMouse) +nsMathMLElement::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) { nsCOMPtr uri; if (IsLink(getter_AddRefs(uri))) { diff --git a/content/mathml/content/src/nsMathMLElement.h b/content/mathml/content/src/nsMathMLElement.h index ffeffc2fa813..81240042979b 100644 --- a/content/mathml/content/src/nsMathMLElement.h +++ b/content/mathml/content/src/nsMathMLElement.h @@ -80,8 +80,7 @@ public: return mIncrementScriptLevel; } - virtual bool IsFocusable(int32_t *aTabIndex = nullptr, - bool aWithMouse = false) MOZ_OVERRIDE; + virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) MOZ_OVERRIDE; virtual bool IsLink(nsIURI** aURI) const MOZ_OVERRIDE; virtual void GetLinkTarget(nsAString& aTarget) MOZ_OVERRIDE; virtual already_AddRefed GetHrefURI() const MOZ_OVERRIDE; diff --git a/content/media/AudioNodeExternalInputStream.h b/content/media/AudioNodeExternalInputStream.h index fe574da0d65b..072587ba2912 100644 --- a/content/media/AudioNodeExternalInputStream.h +++ b/content/media/AudioNodeExternalInputStream.h @@ -9,12 +9,6 @@ #include "MediaStreamGraph.h" #include "AudioNodeStream.h" -#ifdef PR_LOGGING -#define LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg) -#else -#define LOG(type, msg) -#endif - // Forward declaration for mResamplerMap typedef struct SpeexResamplerState_ SpeexResamplerState; diff --git a/content/media/AudioNodeStream.h b/content/media/AudioNodeStream.h index 7b6439815484..e9b81bd50058 100644 --- a/content/media/AudioNodeStream.h +++ b/content/media/AudioNodeStream.h @@ -10,12 +10,6 @@ #include "mozilla/dom/AudioNodeBinding.h" #include "AudioSegment.h" -#ifdef PR_LOGGING -#define LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg) -#else -#define LOG(type, msg) -#endif - namespace mozilla { namespace dom { diff --git a/content/media/DecoderTraits.cpp b/content/media/DecoderTraits.cpp index ec1622f31f7b..44fe3ff6bee0 100644 --- a/content/media/DecoderTraits.cpp +++ b/content/media/DecoderTraits.cpp @@ -61,6 +61,10 @@ #include "AppleDecoder.h" #include "AppleMP3Reader.h" #endif +#ifdef MOZ_FMP4 +#include "MP4Reader.h" +#include "MP4Decoder.h" +#endif namespace mozilla { @@ -298,6 +302,15 @@ IsDirectShowSupportedType(const nsACString& aType) } #endif +#ifdef MOZ_FMP4 +static bool +IsMP4SupportedType(const nsACString& aType) +{ + return Preferences::GetBool("media.fragmented-mp4.exposed", false) && + MP4Decoder::GetSupportedCodecs(aType, nullptr); +} +#endif + #ifdef MOZ_APPLEMEDIA static const char * const gAppleMP3Types[] = { "audio/mp3", @@ -445,30 +458,35 @@ DecoderTraits::CanHandleMediaType(const char* aMIMEType, return CANPLAY_YES; } -/* static */ +// Instantiates but does not initialize decoder. +static already_AddRefed -DecoderTraits::CreateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner) +InstantiateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner) { nsRefPtr decoder; #ifdef MOZ_GSTREAMER if (IsGStreamerSupportedType(aType)) { decoder = new GStreamerDecoder(); + return decoder.forget(); } #endif #ifdef MOZ_RAW if (IsRawType(aType)) { decoder = new RawDecoder(); + return decoder.forget(); } #endif #ifdef MOZ_OGG if (IsOggType(aType)) { decoder = new OggDecoder(); + return decoder.forget(); } #endif #ifdef MOZ_WAVE if (IsWaveType(aType)) { decoder = new WaveDecoder(); + return decoder.forget(); } #endif #ifdef MOZ_OMX_DECODER @@ -489,22 +507,26 @@ DecoderTraits::CreateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner) } } decoder = new MediaOmxDecoder(); + return decoder.forget(); } #endif #ifdef NECKO_PROTOCOL_rtsp if (IsRtspSupportedType(aType)) { decoder = new RtspOmxDecoder(); + return decoder.forget(); } #endif #ifdef MOZ_MEDIA_PLUGINS if (MediaDecoder::IsMediaPluginsEnabled() && GetMediaPluginHost()->FindDecoder(aType, nullptr)) { decoder = new MediaPluginDecoder(aType); + return decoder.forget(); } #endif #ifdef MOZ_WEBM if (IsWebMType(aType)) { decoder = new WebMDecoder(); + return decoder.forget(); } #endif #ifdef MOZ_DIRECTSHOW @@ -512,21 +534,40 @@ DecoderTraits::CreateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner) // "media.directshow.preferred" won't be honored. if (IsDirectShowSupportedType(aType)) { decoder = new DirectShowDecoder(); + return decoder.forget(); + } +#endif +#ifdef MOZ_FMP4 + if (IsMP4SupportedType(aType)) { + decoder = new MP4Decoder(); + return decoder.forget(); } #endif #ifdef MOZ_WMF if (IsWMFSupportedType(aType)) { decoder = new WMFDecoder(); + return decoder.forget(); } #endif #ifdef MOZ_APPLEMEDIA if (IsAppleMediaSupportedType(aType)) { decoder = new AppleDecoder(); + return decoder.forget(); } #endif NS_ENSURE_TRUE(decoder != nullptr, nullptr); NS_ENSURE_TRUE(decoder->Init(aOwner), nullptr); + return nullptr; +} + +/* static */ +already_AddRefed +DecoderTraits::CreateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner) +{ + nsRefPtr decoder(InstantiateDecoder(aType, aOwner)); + NS_ENSURE_TRUE(decoder != nullptr, nullptr); + NS_ENSURE_TRUE(decoder->Init(aOwner), nullptr); return decoder.forget(); } @@ -579,6 +620,11 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac decoderReader = new DirectShowReader(aDecoder); } else #endif +#ifdef MOZ_FMP4 + if (IsMP4SupportedType(aType)) { + decoderReader = new MP4Reader(aDecoder); + } else +#endif #ifdef MOZ_WMF if (IsWMFSupportedType(aType)) { decoderReader = new WMFReader(aDecoder); @@ -615,6 +661,9 @@ bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType) #ifdef MOZ_MEDIA_PLUGINS (MediaDecoder::IsMediaPluginsEnabled() && IsMediaPluginsType(aType)) || #endif +#ifdef MOZ_FMP4 + IsMP4SupportedType(aType) || +#endif #ifdef MOZ_WMF (IsWMFSupportedType(aType) && Preferences::GetBool("media.windows-media-foundation.play-stand-alone", true)) || diff --git a/content/media/MediaCache.cpp b/content/media/MediaCache.cpp index 98f877a13d13..005d88cc7841 100644 --- a/content/media/MediaCache.cpp +++ b/content/media/MediaCache.cpp @@ -26,9 +26,9 @@ namespace mozilla { #ifdef PR_LOGGING PRLogModuleInfo* gMediaCacheLog; -#define LOG(type, msg) PR_LOG(gMediaCacheLog, type, msg) +#define CACHE_LOG(type, msg) PR_LOG(gMediaCacheLog, type, msg) #else -#define LOG(type, msg) +#define CACHE_LOG(type, msg) #endif // Readahead blocks for non-seekable streams will be limited to this @@ -935,7 +935,7 @@ MediaCache::FreeBlock(int32_t aBlock) return; } - LOG(PR_LOG_DEBUG, ("Released block %d", aBlock)); + CACHE_LOG(PR_LOG_DEBUG, ("Released block %d", aBlock)); for (uint32_t i = 0; i < block->mOwners.Length(); ++i) { BlockOwner* bo = &block->mOwners[i]; @@ -1101,20 +1101,20 @@ MediaCache::Update() if (NS_SUCCEEDED(rv)) { // We successfully copied the file data. - LOG(PR_LOG_DEBUG, ("Swapping blocks %d and %d (trimming cache)", - blockIndex, destinationBlockIndex)); + CACHE_LOG(PR_LOG_DEBUG, ("Swapping blocks %d and %d (trimming cache)", + blockIndex, destinationBlockIndex)); // Swapping the block metadata here lets us maintain the // correct positions in the linked lists SwapBlocks(blockIndex, destinationBlockIndex); //Free the overflowing block even if the copy failed. - LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)", blockIndex)); + CACHE_LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)", blockIndex)); FreeBlock(blockIndex); } } else { - LOG(PR_LOG_DEBUG, ("Could not trim cache block %d (destination %d, predicted next use %f, latest predicted use for overflow %f", - blockIndex, destinationBlockIndex, - PredictNextUse(now, destinationBlockIndex).ToSeconds(), - latestPredictedUseForOverflow.ToSeconds())); + CACHE_LOG(PR_LOG_DEBUG, ("Could not trim cache block %d (destination %d, predicted next use %f, latest predicted use for overflow %f", + blockIndex, destinationBlockIndex, + PredictNextUse(now, destinationBlockIndex).ToSeconds(), + latestPredictedUseForOverflow.ToSeconds())); } } // Try chopping back the array of cache entries and the cache file. @@ -1194,29 +1194,29 @@ MediaCache::Update() // advertised with Content-Length, and we may as well keep reading. // But we don't want to seek to the end of the stream if we're not // already there. - LOG(PR_LOG_DEBUG, ("Stream %p at end of stream", stream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p at end of stream", stream)); enableReading = !stream->mCacheSuspended && stream->mStreamLength == stream->mChannelOffset; } else if (desiredOffset < stream->mStreamOffset) { // We're reading to try to catch up to where the current stream // reader wants to be. Better not stop. - LOG(PR_LOG_DEBUG, ("Stream %p catching up", stream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p catching up", stream)); enableReading = true; } else if (desiredOffset < stream->mStreamOffset + BLOCK_SIZE) { // The stream reader is waiting for us, or nearly so. Better feed it. - LOG(PR_LOG_DEBUG, ("Stream %p feeding reader", stream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p feeding reader", stream)); enableReading = true; } else if (!stream->mIsTransportSeekable && nonSeekableReadaheadBlockCount >= maxBlocks*NONSEEKABLE_READAHEAD_MAX) { // This stream is not seekable and there are already too many blocks // being cached for readahead for nonseekable streams (which we can't // free). So stop reading ahead now. - LOG(PR_LOG_DEBUG, ("Stream %p throttling non-seekable readahead", stream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p throttling non-seekable readahead", stream)); enableReading = false; } else if (mIndex.Length() > uint32_t(maxBlocks)) { // We're in the process of bringing the cache size back to the // desired limit, so don't bring in more data yet - LOG(PR_LOG_DEBUG, ("Stream %p throttling to reduce cache size", stream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p throttling to reduce cache size", stream)); enableReading = false; } else { TimeDuration predictedNewDataUse = PredictNextUseForIncomingData(stream); @@ -1224,21 +1224,21 @@ MediaCache::Update() if (stream->mCacheSuspended && predictedNewDataUse.ToMilliseconds() > CACHE_POWERSAVE_WAKEUP_LOW_THRESHOLD_MS) { // Don't need data for a while, so don't bother waking up the stream - LOG(PR_LOG_DEBUG, ("Stream %p avoiding wakeup since more data is not needed", stream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p avoiding wakeup since more data is not needed", stream)); enableReading = false; } else if (freeBlockCount > 0) { // Free blocks in the cache, so keep reading - LOG(PR_LOG_DEBUG, ("Stream %p reading since there are free blocks", stream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p reading since there are free blocks", stream)); enableReading = true; } else if (latestNextUse <= TimeDuration(0)) { // No reusable blocks, so can't read anything - LOG(PR_LOG_DEBUG, ("Stream %p throttling due to no reusable blocks", stream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p throttling due to no reusable blocks", stream)); enableReading = false; } else { // Read ahead if the data we expect to read is more valuable than // the least valuable block in the main part of the cache - LOG(PR_LOG_DEBUG, ("Stream %p predict next data in %f, current worst block is %f", - stream, predictedNewDataUse.ToSeconds(), latestNextUse.ToSeconds())); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p predict next data in %f, current worst block is %f", + stream, predictedNewDataUse.ToSeconds(), latestNextUse.ToSeconds())); enableReading = predictedNewDataUse < latestNextUse; } } @@ -1252,8 +1252,8 @@ MediaCache::Update() // This block is already going to be read by the other stream. // So don't try to read it from this stream as well. enableReading = false; - LOG(PR_LOG_DEBUG, ("Stream %p waiting on same block (%lld) from stream %p", - stream, desiredOffset/BLOCK_SIZE, other)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p waiting on same block (%lld) from stream %p", + stream, desiredOffset/BLOCK_SIZE, other)); break; } } @@ -1316,17 +1316,17 @@ MediaCache::Update() switch (actions[i]) { case SEEK: case SEEK_AND_RESUME: - LOG(PR_LOG_DEBUG, ("Stream %p CacheSeek to %lld (resume=%d)", stream, - (long long)stream->mChannelOffset, actions[i] == SEEK_AND_RESUME)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p CacheSeek to %lld (resume=%d)", stream, + (long long)stream->mChannelOffset, actions[i] == SEEK_AND_RESUME)); rv = stream->mClient->CacheClientSeek(stream->mChannelOffset, actions[i] == SEEK_AND_RESUME); break; case RESUME: - LOG(PR_LOG_DEBUG, ("Stream %p Resumed", stream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p Resumed", stream)); rv = stream->mClient->CacheClientResume(); break; case SUSPEND: - LOG(PR_LOG_DEBUG, ("Stream %p Suspended", stream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p Suspended", stream)); rv = stream->mClient->CacheClientSuspend(); break; default: @@ -1447,8 +1447,8 @@ MediaCache::AllocateAndWriteBlock(MediaCacheStream* aStream, const void* aData, if (stream->mBlocks[streamBlockIndex] >= 0) { // We no longer want to own this block int32_t globalBlockIndex = stream->mBlocks[streamBlockIndex]; - LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)", - globalBlockIndex, stream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE)); + CACHE_LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)", + globalBlockIndex, stream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE)); RemoveBlockOwner(globalBlockIndex, stream); } } @@ -1461,8 +1461,8 @@ MediaCache::AllocateAndWriteBlock(MediaCacheStream* aStream, const void* aData, FreeBlock(blockIndex); Block* block = &mIndex[blockIndex]; - LOG(PR_LOG_DEBUG, ("Allocated block %d to stream %p block %d(%lld)", - blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE)); + CACHE_LOG(PR_LOG_DEBUG, ("Allocated block %d to stream %p block %d(%lld)", + blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE)); mFreeBlocks.RemoveBlock(blockIndex); @@ -1496,8 +1496,8 @@ MediaCache::AllocateAndWriteBlock(MediaCacheStream* aStream, const void* aData, nsresult rv = mFileCache->WriteBlock(blockIndex, reinterpret_cast(aData)); if (NS_FAILED(rv)) { - LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)", - blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE)); + CACHE_LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)", + blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE)); FreeBlock(blockIndex); } } @@ -1513,7 +1513,7 @@ MediaCache::OpenStream(MediaCacheStream* aStream) NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); ReentrantMonitorAutoEnter mon(mReentrantMonitor); - LOG(PR_LOG_DEBUG, ("Stream %p opened", aStream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p opened", aStream)); mStreams.AppendElement(aStream); aStream->mResourceID = AllocateResourceID(); @@ -1527,7 +1527,7 @@ MediaCache::ReleaseStream(MediaCacheStream* aStream) NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); ReentrantMonitorAutoEnter mon(mReentrantMonitor); - LOG(PR_LOG_DEBUG, ("Stream %p closed", aStream)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p closed", aStream)); mStreams.RemoveElement(aStream); } @@ -1543,8 +1543,8 @@ MediaCache::ReleaseStreamBlocks(MediaCacheStream* aStream) for (uint32_t i = 0; i < length; ++i) { int32_t blockIndex = aStream->mBlocks[i]; if (blockIndex >= 0) { - LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)", - blockIndex, aStream, i, (long long)i*BLOCK_SIZE)); + CACHE_LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)", + blockIndex, aStream, i, (long long)i*BLOCK_SIZE)); RemoveBlockOwner(blockIndex, aStream); } } @@ -1712,8 +1712,8 @@ MediaCacheStream::NotifyDataReceived(int64_t aSize, const char* aData, int64_t size = aSize; const char* data = aData; - LOG(PR_LOG_DEBUG, ("Stream %p DataReceived at %lld count=%lld", - this, (long long)mChannelOffset, (long long)aSize)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p DataReceived at %lld count=%lld", + this, (long long)mChannelOffset, (long long)aSize)); // We process the data one block (or part of a block) at a time while (size > 0) { @@ -1780,12 +1780,12 @@ MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll) int32_t blockOffset = int32_t(mChannelOffset%BLOCK_SIZE); if (blockOffset > 0) { - LOG(PR_LOG_DEBUG, - ("Stream %p writing partial block: [%d] bytes; " - "mStreamOffset [%lld] mChannelOffset[%lld] mStreamLength [%lld] " - "notifying: [%s]", - this, blockOffset, mStreamOffset, mChannelOffset, mStreamLength, - aNotifyAll ? "yes" : "no")); + CACHE_LOG(PR_LOG_DEBUG, + ("Stream %p writing partial block: [%d] bytes; " + "mStreamOffset [%lld] mChannelOffset[%lld] mStreamLength [%lld] " + "notifying: [%s]", + this, blockOffset, mStreamOffset, mChannelOffset, mStreamLength, + aNotifyAll ? "yes" : "no")); // Write back the partial block memset(reinterpret_cast(mPartialBlockBuffer) + blockOffset, 0, @@ -2107,7 +2107,7 @@ MediaCacheStream::Seek(int32_t aWhence, int64_t aOffset) return NS_ERROR_FAILURE; } - LOG(PR_LOG_DEBUG, ("Stream %p Seek to %lld", this, (long long)mStreamOffset)); + CACHE_LOG(PR_LOG_DEBUG, ("Stream %p Seek to %lld", this, (long long)mStreamOffset)); gMediaCache->NoteSeek(this, oldOffset); gMediaCache->QueueUpdate(); @@ -2221,8 +2221,8 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) // have changed gMediaCache->QueueUpdate(); } - LOG(PR_LOG_DEBUG, - ("Stream %p Read at %lld count=%d", this, (long long)(mStreamOffset-count), count)); + CACHE_LOG(PR_LOG_DEBUG, + ("Stream %p Read at %lld count=%d", this, (long long)(mStreamOffset-count), count)); *aBytes = count; return NS_OK; } diff --git a/content/media/MediaDecoder.cpp b/content/media/MediaDecoder.cpp index 81dc7cdf0b02..f63f499750c5 100644 --- a/content/media/MediaDecoder.cpp +++ b/content/media/MediaDecoder.cpp @@ -48,9 +48,9 @@ static const int64_t CAN_PLAY_THROUGH_MARGIN = 1; #ifdef PR_LOGGING PRLogModuleInfo* gMediaDecoderLog; -#define LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) +#define DECODER_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) #else -#define LOG(type, msg) +#define DECODER_LOG(type, msg) #endif class MediaMemoryTracker @@ -251,8 +251,8 @@ void MediaDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs) { MOZ_ASSERT(NS_IsMainThread()); GetReentrantMonitor().AssertCurrentThreadIn(); - LOG(PR_LOG_DEBUG, ("MediaDecoder::RecreateDecodedStream this=%p aStartTimeUSecs=%lld!", - this, (long long)aStartTimeUSecs)); + DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoder::RecreateDecodedStream this=%p aStartTimeUSecs=%lld!", + this, (long long)aStartTimeUSecs)); DestroyDecodedStream(); @@ -296,8 +296,8 @@ void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded) { MOZ_ASSERT(NS_IsMainThread()); - LOG(PR_LOG_DEBUG, ("MediaDecoder::AddOutputStream this=%p aStream=%p!", - this, aStream)); + DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoder::AddOutputStream this=%p aStream=%p!", + this, aStream)); { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); @@ -459,7 +459,7 @@ nsresult MediaDecoder::OpenResource(nsIStreamListener** aStreamListener) nsresult rv = mResource->Open(aStreamListener); if (NS_FAILED(rv)) { - LOG(PR_LOG_DEBUG, ("%p Failed to open stream!", this)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Failed to open stream!", this)); return rv; } } @@ -476,7 +476,7 @@ nsresult MediaDecoder::Load(nsIStreamListener** aStreamListener, mDecoderStateMachine = CreateStateMachine(); if (!mDecoderStateMachine) { - LOG(PR_LOG_DEBUG, ("%p Failed to create state machine!", this)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Failed to create state machine!", this)); return NS_ERROR_FAILURE; } @@ -491,7 +491,7 @@ nsresult MediaDecoder::InitializeStateMachine(MediaDecoder* aCloneDonor) MediaDecoder* cloneDonor = static_cast(aCloneDonor); if (NS_FAILED(mDecoderStateMachine->Init(cloneDonor ? cloneDonor->mDecoderStateMachine : nullptr))) { - LOG(PR_LOG_DEBUG, ("%p Failed to init state machine!", this)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Failed to init state machine!", this)); return NS_ERROR_FAILURE; } { @@ -814,6 +814,23 @@ void MediaDecoder::ResourceLoaded() } } +void MediaDecoder::ResetConnectionState() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (mShuttingDown) + return; + + if (mOwner) { + // Notify the media element that connection gets lost. + mOwner->ResetConnectionState(); + } + + // Since we have notified the media element the connection + // lost event, the decoder will be reloaded when user tries + // to play the Rtsp streaming next time. + Shutdown(); +} + void MediaDecoder::NetworkError() { MOZ_ASSERT(NS_IsMainThread()); @@ -1252,7 +1269,7 @@ void MediaDecoder::DurationChanged() UpdatePlaybackRate(); if (mOwner && oldDuration != mDuration && !IsInfinite()) { - LOG(PR_LOG_DEBUG, ("%p duration changed to %lld", this, mDuration)); + DECODER_LOG(PR_LOG_DEBUG, ("%p duration changed to %lld", this, mDuration)); mOwner->DispatchEvent(NS_LITERAL_STRING("durationchange")); } } diff --git a/content/media/MediaDecoder.h b/content/media/MediaDecoder.h index 27a66180f142..651be6c73ec9 100644 --- a/content/media/MediaDecoder.h +++ b/content/media/MediaDecoder.h @@ -247,6 +247,9 @@ public: MediaDecoder(); virtual ~MediaDecoder(); + // Reset the decoder and notify the media element that + // server connection is closed. + virtual void ResetConnectionState(); // Create a new decoder of the same type as this one. // Subclasses must implement this. virtual MediaDecoder* Clone() = 0; diff --git a/content/media/MediaDecoderOwner.h b/content/media/MediaDecoderOwner.h index a12f3ff0fb0d..b075ef309351 100644 --- a/content/media/MediaDecoderOwner.h +++ b/content/media/MediaDecoderOwner.h @@ -139,6 +139,10 @@ public: // Called by the media decoder and the video frame to get the // ImageContainer containing the video data. virtual VideoFrameContainer* GetVideoFrameContainer() = 0; + + // Called by the media decoder object, on the main thread, + // when the connection between Rtsp server and client gets lost. + virtual void ResetConnectionState() = 0; }; } diff --git a/content/media/MediaDecoderReader.cpp b/content/media/MediaDecoderReader.cpp index e5f92b886ad2..8baeefd4587e 100644 --- a/content/media/MediaDecoderReader.cpp +++ b/content/media/MediaDecoderReader.cpp @@ -36,14 +36,14 @@ static_assert(PlanarYCbCrImage::MAX_DIMENSION < UINT32_MAX / PlanarYCbCrImage::M #ifdef PR_LOGGING extern PRLogModuleInfo* gMediaDecoderLog; -#define LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) +#define DECODER_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) #ifdef SEEK_LOGGING #define SEEK_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) #else #define SEEK_LOG(type, msg) #endif #else -#define LOG(type, msg) +#define DECODER_LOG(type, msg) #define SEEK_LOG(type, msg) #endif @@ -466,12 +466,14 @@ VideoData* MediaDecoderReader::FindStartTime(int64_t& aOutStartTime) videoData = DecodeToFirstVideoData(); if (videoData) { videoStartTime = videoData->mTime; + DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::FindStartTime() video=%lld", videoStartTime)); } } if (HasAudio()) { AudioData* audioData = DecodeToFirstAudioData(); if (audioData) { audioStartTime = audioData->mTime; + DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::FindStartTime() audio=%lld", audioStartTime)); } } @@ -485,7 +487,7 @@ VideoData* MediaDecoderReader::FindStartTime(int64_t& aOutStartTime) nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget) { - LOG(PR_LOG_DEBUG, ("MediaDecoderReader::DecodeToTarget(%lld) Begin", aTarget)); + DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::DecodeToTarget(%lld) Begin", aTarget)); // Decode forward to the target frame. Start with video, if we have it. if (HasVideo()) { @@ -529,7 +531,7 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget) return NS_ERROR_FAILURE; } } - LOG(PR_LOG_DEBUG, ("First video frame after decode is %lld", startTime)); + DECODER_LOG(PR_LOG_DEBUG, ("First video frame after decode is %lld", startTime)); } if (HasAudio()) { @@ -609,7 +611,7 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget) } } - LOG(PR_LOG_DEBUG, ("MediaDecoderReader::DecodeToTarget(%lld) End", aTarget)); + DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::DecodeToTarget(%lld) End", aTarget)); return NS_OK; } diff --git a/content/media/MediaDecoderStateMachine.cpp b/content/media/MediaDecoderStateMachine.cpp index 086649722581..dca55817d62e 100644 --- a/content/media/MediaDecoderStateMachine.cpp +++ b/content/media/MediaDecoderStateMachine.cpp @@ -38,9 +38,9 @@ using namespace mozilla::dom; #ifdef PR_LOGGING extern PRLogModuleInfo* gMediaDecoderLog; -#define LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) +#define DECODER_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) #else -#define LOG(type, msg) +#define DECODER_LOG(type, msg) #endif // Wait this number of seconds when buffering, then leave and play @@ -287,7 +287,7 @@ void StateMachineTracker::CleanupGlobalStateMachine() "State machine ref count must be > 0"); mStateMachineCount--; if (mStateMachineCount == 0) { - LOG(PR_LOG_DEBUG, ("Destroying media state machine thread")); + DECODER_LOG(PR_LOG_DEBUG, ("Destroying media state machine thread")); NS_ASSERTION(mPending.GetSize() == 0, "Shouldn't all requests be handled by now?"); { ReentrantMonitorAutoEnter mon(mMonitor); @@ -506,7 +506,7 @@ void MediaDecoderStateMachine::DecodeThreadRun() NS_FAILED(DecodeMetadata())) { NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN, "Should be in shutdown state if metadata loading fails."); - LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread")); + DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread")); } while (mState != DECODER_STATE_SHUTDOWN && @@ -522,7 +522,7 @@ void MediaDecoderStateMachine::DecodeThreadRun() if (NS_FAILED(DecodeMetadata())) { NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN, "Should be in shutdown state if metadata loading fails."); - LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread")); + DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread")); } } else if (mState == DECODER_STATE_WAIT_FOR_RESOURCES) { mDecoder->GetReentrantMonitor().Wait(); @@ -537,7 +537,7 @@ void MediaDecoderStateMachine::DecodeThreadRun() } mDecodeThreadIdle = true; - LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder.get())); } mReader->OnDecodeThreadFinish(); @@ -567,8 +567,8 @@ void MediaDecoderStateMachine::SendStreamAudio(AudioData* aAudio, return; if (audioWrittenOffset.value() < frameOffset.value()) { // Write silence to catch up - LOG(PR_LOG_DEBUG, ("%p Decoder writing %d frames of silence to MediaStream", - mDecoder.get(), int32_t(frameOffset.value() - audioWrittenOffset.value()))); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing %d frames of silence to MediaStream", + mDecoder.get(), int32_t(frameOffset.value() - audioWrittenOffset.value()))); AudioSegment silence; silence.InsertNullDataAtStart(frameOffset.value() - audioWrittenOffset.value()); aStream->mAudioFramesWritten += silence.GetDuration(); @@ -597,8 +597,8 @@ void MediaDecoderStateMachine::SendStreamAudio(AudioData* aAudio, channels.AppendElement(bufferData + i*aAudio->mFrames + offset); } aOutput->AppendFrames(buffer.forget(), channels, aAudio->mFrames); - LOG(PR_LOG_DEBUG, ("%p Decoder writing %d frames of data to MediaStream for AudioData at %lld", - mDecoder.get(), aAudio->mFrames - int32_t(offset), aAudio->mTime)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing %d frames of data to MediaStream for AudioData at %lld", + mDecoder.get(), aAudio->mFrames - int32_t(offset), aAudio->mTime)); aStream->mAudioFramesWritten += aAudio->mFrames - int32_t(offset); } @@ -684,9 +684,9 @@ void MediaDecoderStateMachine::SendStreamData() for (uint32_t i = 0; i < video.Length(); ++i) { VideoData* v = video[i]; if (stream->mNextVideoTime + mStartTime < v->mTime) { - LOG(PR_LOG_DEBUG, ("%p Decoder writing last video to MediaStream %p for %lld ms", - mDecoder.get(), mediaStream, - v->mTime - (stream->mNextVideoTime + mStartTime))); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing last video to MediaStream %p for %lld ms", + mDecoder.get(), mediaStream, + v->mTime - (stream->mNextVideoTime + mStartTime))); // Write last video frame to catch up. mLastVideoImage can be null here // which is fine, it just means there's no video. WriteVideoToMediaStream(stream->mLastVideoImage, @@ -695,9 +695,9 @@ void MediaDecoderStateMachine::SendStreamData() stream->mNextVideoTime = v->mTime - mStartTime; } if (stream->mNextVideoTime + mStartTime < v->GetEndTime()) { - LOG(PR_LOG_DEBUG, ("%p Decoder writing video frame %lld to MediaStream %p for %lld ms", - mDecoder.get(), v->mTime, mediaStream, - v->GetEndTime() - (stream->mNextVideoTime + mStartTime))); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing video frame %lld to MediaStream %p for %lld ms", + mDecoder.get(), v->mTime, mediaStream, + v->GetEndTime() - (stream->mNextVideoTime + mStartTime))); WriteVideoToMediaStream(v->mImage, v->GetEndTime() - (stream->mNextVideoTime + mStartTime), v->mDisplay, &output); @@ -705,8 +705,8 @@ void MediaDecoderStateMachine::SendStreamData() stream->mLastVideoImage = v->mImage; stream->mLastVideoImageDisplaySize = v->mDisplay; } else { - LOG(PR_LOG_DEBUG, ("%p Decoder skipping writing video frame %lld to MediaStream", - mDecoder.get(), v->mTime)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder skipping writing video frame %lld to MediaStream", + mDecoder.get(), v->mTime)); } } if (output.GetDuration() > 0) { @@ -814,7 +814,7 @@ bool MediaDecoderStateMachine::HaveEnoughDecodedVideo() void MediaDecoderStateMachine::DecodeLoop() { - LOG(PR_LOG_DEBUG, ("%p Start DecodeLoop()", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Start DecodeLoop()", mDecoder.get())); AssertCurrentThreadInMonitor(); NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); @@ -889,7 +889,7 @@ void MediaDecoderStateMachine::DecodeLoop() !HasLowUndecodedData()) { skipToNextKeyframe = true; - LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder.get())); } // Video decode. @@ -922,9 +922,9 @@ void MediaDecoderStateMachine::DecodeLoop() std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), AMPLE_AUDIO_USECS); ampleAudioThreshold = std::max(THRESHOLD_FACTOR * lowAudioThreshold, ampleAudioThreshold); - LOG(PR_LOG_DEBUG, - ("Slow video decode, set lowAudioThreshold=%lld ampleAudioThreshold=%lld", - lowAudioThreshold, ampleAudioThreshold)); + DECODER_LOG(PR_LOG_DEBUG, + ("Slow video decode, set lowAudioThreshold=%lld ampleAudioThreshold=%lld", + lowAudioThreshold, ampleAudioThreshold)); } } @@ -990,7 +990,7 @@ void MediaDecoderStateMachine::DecodeLoop() ScheduleStateMachine(); } - LOG(PR_LOG_DEBUG, ("%p Exiting DecodeLoop", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Exiting DecodeLoop", mDecoder.get())); } bool MediaDecoderStateMachine::IsPlaying() @@ -1026,7 +1026,7 @@ static void WriteSilence(AudioStream* aStream, uint32_t aFrames) void MediaDecoderStateMachine::AudioLoop() { NS_ASSERTION(OnAudioThread(), "Should be on audio thread."); - LOG(PR_LOG_DEBUG, ("%p Begun audio thread/loop", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Begun audio thread/loop", mDecoder.get())); int64_t audioDuration = 0; int64_t audioStartTime = -1; uint32_t channels, rate; @@ -1164,8 +1164,8 @@ void MediaDecoderStateMachine::AudioLoop() // hardware so that the next audio chunk begins playback at the correct // time. missingFrames = std::min(UINT32_MAX, missingFrames.value()); - LOG(PR_LOG_DEBUG, ("%p Decoder playing %d frames of silence", - mDecoder.get(), int32_t(missingFrames.value()))); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder playing %d frames of silence", + mDecoder.get(), int32_t(missingFrames.value()))); framesWritten = PlaySilence(static_cast(missingFrames.value()), channels, playedFrames.value()); } else { @@ -1220,7 +1220,7 @@ void MediaDecoderStateMachine::AudioLoop() } } } - LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder.get())); { // Must hold lock while shutting down and anulling the audio stream to prevent // state machine thread trying to use it while we're destroying it. @@ -1236,7 +1236,7 @@ void MediaDecoderStateMachine::AudioLoop() } } - LOG(PR_LOG_DEBUG, ("%p Audio stream finished playing, audio thread exit", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Audio stream finished playing, audio thread exit", mDecoder.get())); } uint32_t MediaDecoderStateMachine::PlaySilence(uint32_t aFrames, @@ -1271,8 +1271,8 @@ uint32_t MediaDecoderStateMachine::PlayFromAudioQueue(uint64_t aFrameOffset, int64_t offset = -1; uint32_t frames = 0; if (!PR_GetEnv("MOZ_QUIET")) { - LOG(PR_LOG_DEBUG, ("%p Decoder playing %d frames of data to stream for AudioData at %lld", - mDecoder.get(), audio->mFrames, audio->mTime)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder playing %d frames of data to stream for AudioData at %lld", + mDecoder.get(), audio->mFrames, audio->mTime)); } mAudioStream->Write(audio->mAudioData, audio->mFrames); @@ -1303,7 +1303,7 @@ nsresult MediaDecoderStateMachine::Init(MediaDecoderStateMachine* aCloneDonor) void MediaDecoderStateMachine::StopPlayback() { - LOG(PR_LOG_DEBUG, ("%p StopPlayback()", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p StopPlayback()", mDecoder.get())); AssertCurrentThreadInMonitor(); @@ -1321,7 +1321,7 @@ void MediaDecoderStateMachine::StopPlayback() void MediaDecoderStateMachine::StartPlayback() { - LOG(PR_LOG_DEBUG, ("%p StartPlayback()", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p StartPlayback()", mDecoder.get())); NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called"); AssertCurrentThreadInMonitor(); @@ -1535,7 +1535,7 @@ void MediaDecoderStateMachine::Shutdown() // Change state before issuing shutdown request to threads so those // threads can start exiting cleanly during the Shutdown call. - LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder.get())); ScheduleStateMachine(); mState = DECODER_STATE_SHUTDOWN; mDecoder->GetReentrantMonitor().NotifyAll(); @@ -1577,7 +1577,7 @@ void MediaDecoderStateMachine::Play() // when the state machine notices the decoder's state change to PLAYING. ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); if (mState == DECODER_STATE_BUFFERING) { - LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder.get())); mState = DECODER_STATE_DECODING; mDecodeStartTime = TimeStamp::Now(); } @@ -1652,7 +1652,7 @@ void MediaDecoderStateMachine::Seek(double aTime) mSeekTime = std::min(mSeekTime, mEndTime); mSeekTime = std::max(mStartTime, mSeekTime); mBasePosition = mSeekTime - mStartTime; - LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder.get(), aTime)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder.get(), aTime)); mState = DECODER_STATE_SEEKING; if (mDecoder->GetDecodedStream()) { mDecoder->RecreateDecodedStream(mSeekTime - mStartTime); @@ -1675,7 +1675,7 @@ void MediaDecoderStateMachine::StopDecodeThread() mStopDecodeThread = true; mDecoder->GetReentrantMonitor().NotifyAll(); if (mDecodeThread) { - LOG(PR_LOG_DEBUG, ("%p Shutdown decode thread", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Shutdown decode thread", mDecoder.get())); { ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); mDecodeThread->Shutdown(); @@ -1704,7 +1704,7 @@ void MediaDecoderStateMachine::StopAudioThread() mStopAudioThread = true; mDecoder->GetReentrantMonitor().NotifyAll(); if (mAudioThread) { - LOG(PR_LOG_DEBUG, ("%p Shutdown audio thread", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Shutdown audio thread", mDecoder.get())); { ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); mAudioThread->Shutdown(); @@ -1801,7 +1801,7 @@ MediaDecoderStateMachine::StartAudioThread() nullptr, MEDIA_THREAD_STACK_SIZE); if (NS_FAILED(rv)) { - LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN because failed to create audio thread", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN because failed to create audio thread", mDecoder.get())); mState = DECODER_STATE_SHUTDOWN; return rv; } @@ -1884,7 +1884,7 @@ nsresult MediaDecoderStateMachine::DecodeMetadata() NS_ASSERTION(mState == DECODER_STATE_DECODING_METADATA, "Only call when in metadata decoding state"); - LOG(PR_LOG_DEBUG, ("%p Decoding Media Headers", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decoding Media Headers", mDecoder.get())); nsresult res; MediaInfo info; MetadataTags* tags; @@ -1935,10 +1935,10 @@ nsresult MediaDecoderStateMachine::DecodeMetadata() "Active seekable media should have end time"); MOZ_ASSERT(!(mMediaSeekable && mTransportSeekable) || GetDuration() != -1, "Seekable media should have duration"); - LOG(PR_LOG_DEBUG, ("%p Media goes from %lld to %lld (duration %lld)" - " transportSeekable=%d, mediaSeekable=%d", - mDecoder.get(), mStartTime, mEndTime, GetDuration(), - mTransportSeekable, mMediaSeekable)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Media goes from %lld to %lld (duration %lld)" + " transportSeekable=%d, mediaSeekable=%d", + mDecoder.get(), mStartTime, mEndTime, GetDuration(), + mTransportSeekable, mMediaSeekable)); // Inform the element that we've loaded the metadata and the first frame, // setting the default framebuffer size for audioavailable events. Also, @@ -1963,7 +1963,7 @@ nsresult MediaDecoderStateMachine::DecodeMetadata() NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL); if (mState == DECODER_STATE_DECODING_METADATA) { - LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder.get())); StartDecoding(); } @@ -2068,8 +2068,8 @@ void MediaDecoderStateMachine::DecodeSeek() } // Try to decode another frame to detect if we're at the end... - LOG(PR_LOG_DEBUG, ("%p Seek completed, mCurrentFrameTime=%lld\n", - mDecoder.get(), mCurrentFrameTime)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Seek completed, mCurrentFrameTime=%lld\n", + mDecoder.get(), mCurrentFrameTime)); // Change state to DECODING or COMPLETED now. SeekingStopped will // call MediaDecoderStateMachine::Seek to reset our state to SEEKING @@ -2081,13 +2081,13 @@ void MediaDecoderStateMachine::DecodeSeek() // Seeked to end of media, move to COMPLETED state. Note we don't do // this if we're playing a live stream, since the end of media will advance // once we download more data! - LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to COMPLETED", - mDecoder.get(), seekTime)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to COMPLETED", + mDecoder.get(), seekTime)); stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStoppedAtEnd); mState = DECODER_STATE_COMPLETED; } else { - LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to DECODING", - mDecoder.get(), seekTime)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to DECODING", + mDecoder.get(), seekTime)); stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStopped); StartDecoding(); } @@ -2278,19 +2278,19 @@ nsresult MediaDecoderStateMachine::RunStateMachine() !mDecoder->IsDataCachedToEndOfResource() && !resource->IsSuspended()) { - LOG(PR_LOG_DEBUG, - ("%p Buffering: wait %ds, timeout in %.3lfs %s", - mDecoder.get(), - mBufferingWait, - mBufferingWait - elapsed.ToSeconds(), - (mQuickBuffering ? "(quick exit)" : ""))); + DECODER_LOG(PR_LOG_DEBUG, + ("%p Buffering: wait %ds, timeout in %.3lfs %s", + mDecoder.get(), + mBufferingWait, + mBufferingWait - elapsed.ToSeconds(), + (mQuickBuffering ? "(quick exit)" : ""))); ScheduleStateMachine(USECS_PER_S); return NS_OK; } else { - LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder.get())); - LOG(PR_LOG_DEBUG, ("%p Buffered for %.3lfs", - mDecoder.get(), - (now - mBufferingStart).ToSeconds())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder.get())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Buffered for %.3lfs", + mDecoder.get(), + (now - mBufferingStart).ToSeconds())); StartDecoding(); } @@ -2377,8 +2377,8 @@ void MediaDecoderStateMachine::RenderVideoFrame(VideoData* aData, } if (!PR_GetEnv("MOZ_QUIET")) { - LOG(PR_LOG_DEBUG, ("%p Decoder playing video frame %lld", - mDecoder.get(), aData->mTime)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder playing video frame %lld", + mDecoder.get(), aData->mTime)); } VideoFrameContainer* container = mDecoder->GetVideoFrameContainer(); @@ -2487,10 +2487,10 @@ void MediaDecoderStateMachine::AdvanceFrame() currentFrame = frame; #ifdef PR_LOGGING if (!PR_GetEnv("MOZ_QUIET")) { - LOG(PR_LOG_DEBUG, ("%p Decoder discarding video frame %lld", mDecoder.get(), frame->mTime)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder discarding video frame %lld", mDecoder.get(), frame->mTime)); if (droppedFrames++) { - LOG(PR_LOG_DEBUG, ("%p Decoder discarding video frame %lld (%d so far)", - mDecoder.get(), frame->mTime, droppedFrames - 1)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder discarding video frame %lld (%d so far)", + mDecoder.get(), frame->mTime, droppedFrames - 1)); } } #endif @@ -2632,7 +2632,7 @@ VideoData* MediaDecoderStateMachine::FindStartTime() // first actual audio frame we have, we'll inject silence during playback // to ensure the audio starts at the correct time. mAudioStartTime = mStartTime; - LOG(PR_LOG_DEBUG, ("%p Media start time is %lld", mDecoder.get(), mStartTime)); + DECODER_LOG(PR_LOG_DEBUG, ("%p Media start time is %lld", mDecoder.get(), mStartTime)); return v; } @@ -2693,15 +2693,15 @@ void MediaDecoderStateMachine::StartBuffering() // the element we're buffering or not. UpdateReadyState(); mState = DECODER_STATE_BUFFERING; - LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING to BUFFERING, decoded for %.3lfs", - mDecoder.get(), decodeDuration.ToSeconds())); + DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING to BUFFERING, decoded for %.3lfs", + mDecoder.get(), decodeDuration.ToSeconds())); #ifdef PR_LOGGING MediaDecoder::Statistics stats = mDecoder->GetStatistics(); #endif - LOG(PR_LOG_DEBUG, ("%p Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s", - mDecoder.get(), - stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)", - stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)")); + DECODER_LOG(PR_LOG_DEBUG, ("%p Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s", + mDecoder.get(), + stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)", + stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)")); } nsresult MediaDecoderStateMachine::GetBuffered(dom::TimeRanges* aBuffered) { diff --git a/content/media/MediaResource.cpp b/content/media/MediaResource.cpp index f5806b659e73..8e150cb16ffb 100644 --- a/content/media/MediaResource.cpp +++ b/content/media/MediaResource.cpp @@ -33,13 +33,13 @@ #ifdef PR_LOGGING PRLogModuleInfo* gMediaResourceLog; -#define LOG(msg, ...) PR_LOG(gMediaResourceLog, PR_LOG_DEBUG, \ - (msg, ##__VA_ARGS__)) +#define RESOURCE_LOG(msg, ...) PR_LOG(gMediaResourceLog, PR_LOG_DEBUG, \ + (msg, ##__VA_ARGS__)) // Debug logging macro with object pointer and class name. #define CMLOG(msg, ...) \ - LOG("%p [ChannelMediaResource]: " msg, this, ##__VA_ARGS__) + RESOURCE_LOG("%p [ChannelMediaResource]: " msg, this, ##__VA_ARGS__) #else -#define LOG(msg, ...) +#define RESOURCE_LOG(msg, ...) #define CMLOG(msg, ...) #endif @@ -475,10 +475,10 @@ ChannelMediaResource::CopySegmentToCache(nsIInputStream *aInStream, closure->mResource->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mResource->mOffset); // Keep track of where we're up to. - LOG("%p [ChannelMediaResource]: CopySegmentToCache at mOffset [%lld] add " - "[%d] bytes for decoder[%p]", - closure->mResource, closure->mResource->mOffset, aCount, - closure->mResource->mDecoder); + RESOURCE_LOG("%p [ChannelMediaResource]: CopySegmentToCache at mOffset [%lld] add " + "[%d] bytes for decoder[%p]", + closure->mResource, closure->mResource->mOffset, aCount, + closure->mResource->mDecoder); closure->mResource->mOffset += aCount; closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment, diff --git a/content/media/MediaStreamGraph.cpp b/content/media/MediaStreamGraph.cpp index 7b3c7aa4627d..ec4c21ad30e8 100644 --- a/content/media/MediaStreamGraph.cpp +++ b/content/media/MediaStreamGraph.cpp @@ -32,6 +32,9 @@ namespace mozilla { #ifdef PR_LOGGING PRLogModuleInfo* gMediaStreamGraphLog; +#define STREAM_LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg) +#else +#define STREAM_LOG(type, msg) #endif /** @@ -39,6 +42,14 @@ PRLogModuleInfo* gMediaStreamGraphLog; */ static MediaStreamGraphImpl* gGraph; +MediaStreamGraphImpl::~MediaStreamGraphImpl() +{ + NS_ASSERTION(IsEmpty(), + "All streams should have been destroyed by messages from the main thread"); + STREAM_LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p destroyed", this)); +} + + StreamTime MediaStreamGraphImpl::GetDesiredBufferEnd(MediaStream* aStream) { @@ -52,7 +63,7 @@ MediaStreamGraphImpl::FinishStream(MediaStream* aStream) { if (aStream->mFinished) return; - LOG(PR_LOG_DEBUG, ("MediaStream %p will finish", aStream)); + STREAM_LOG(PR_LOG_DEBUG, ("MediaStream %p will finish", aStream)); aStream->mFinished = true; // Force at least one more iteration of the control loop, since we rely // on UpdateCurrentTime to notify our listeners once the stream end @@ -65,7 +76,7 @@ MediaStreamGraphImpl::AddStream(MediaStream* aStream) { aStream->mBufferStartTime = mCurrentTime; *mStreams.AppendElement() = already_AddRefed(aStream); - LOG(PR_LOG_DEBUG, ("Adding media stream %p to the graph", aStream)); + STREAM_LOG(PR_LOG_DEBUG, ("Adding media stream %p to the graph", aStream)); } void @@ -86,7 +97,7 @@ MediaStreamGraphImpl::RemoveStream(MediaStream* aStream) // This unrefs the stream, probably destroying it mStreams.RemoveElement(aStream); - LOG(PR_LOG_DEBUG, ("Removing media stream %p from the graph", aStream)); + STREAM_LOG(PR_LOG_DEBUG, ("Removing media stream %p from the graph", aStream)); } void @@ -120,16 +131,16 @@ MediaStreamGraphImpl::ExtractPendingInput(SourceMediaStream* aStream, StreamTime t = GraphTimeToStreamTime(aStream, mStateComputedTime) + (aDesiredUpToTime - mStateComputedTime); - LOG(PR_LOG_DEBUG+1, ("Calling NotifyPull aStream=%p t=%f current end=%f", aStream, - MediaTimeToSeconds(t), - MediaTimeToSeconds(aStream->mBuffer.GetEnd()))); + STREAM_LOG(PR_LOG_DEBUG+1, ("Calling NotifyPull aStream=%p t=%f current end=%f", aStream, + MediaTimeToSeconds(t), + MediaTimeToSeconds(aStream->mBuffer.GetEnd()))); if (t > aStream->mBuffer.GetEnd()) { *aEnsureNextIteration = true; #ifdef DEBUG if (aStream->mListeners.Length() == 0) { - LOG(PR_LOG_ERROR, ("No listeners in NotifyPull aStream=%p desired=%f current end=%f", - aStream, MediaTimeToSeconds(t), - MediaTimeToSeconds(aStream->mBuffer.GetEnd()))); + STREAM_LOG(PR_LOG_ERROR, ("No listeners in NotifyPull aStream=%p desired=%f current end=%f", + aStream, MediaTimeToSeconds(t), + MediaTimeToSeconds(aStream->mBuffer.GetEnd()))); aStream->DumpTrackInfo(); } #endif @@ -155,9 +166,9 @@ MediaStreamGraphImpl::ExtractPendingInput(SourceMediaStream* aStream, } if (data->mCommands & SourceMediaStream::TRACK_CREATE) { MediaSegment* segment = data->mData.forget(); - LOG(PR_LOG_DEBUG, ("SourceMediaStream %p creating track %d, rate %d, start %lld, initial end %lld", - aStream, data->mID, data->mRate, int64_t(data->mStart), - int64_t(segment->GetDuration()))); + STREAM_LOG(PR_LOG_DEBUG, ("SourceMediaStream %p creating track %d, rate %d, start %lld, initial end %lld", + aStream, data->mID, data->mRate, int64_t(data->mStart), + int64_t(segment->GetDuration()))); aStream->mBuffer.AddTrack(data->mID, data->mRate, data->mStart, segment); // The track has taken ownership of data->mData, so let's replace // data->mData with an empty clone. @@ -165,10 +176,10 @@ MediaStreamGraphImpl::ExtractPendingInput(SourceMediaStream* aStream, data->mCommands &= ~SourceMediaStream::TRACK_CREATE; } else if (data->mData->GetDuration() > 0) { MediaSegment* dest = aStream->mBuffer.FindTrack(data->mID)->GetSegment(); - LOG(PR_LOG_DEBUG+1, ("SourceMediaStream %p track %d, advancing end from %lld to %lld", - aStream, data->mID, - int64_t(dest->GetDuration()), - int64_t(dest->GetDuration() + data->mData->GetDuration()))); + STREAM_LOG(PR_LOG_DEBUG+1, ("SourceMediaStream %p track %d, advancing end from %lld to %lld", + aStream, data->mID, + int64_t(dest->GetDuration()), + int64_t(dest->GetDuration() + data->mData->GetDuration()))); dest->AppendFrom(data->mData); } if (data->mCommands & SourceMediaStream::TRACK_END) { @@ -322,27 +333,27 @@ MediaStreamGraphImpl::UpdateCurrentTime() SecondsToMediaTime((now - mCurrentTimeStamp).ToSeconds()) + mCurrentTime; mCurrentTimeStamp = now; - LOG(PR_LOG_DEBUG+1, ("Updating current time to %f (real %f, mStateComputedTime %f)", - MediaTimeToSeconds(nextCurrentTime), - (now - mInitialTimeStamp).ToSeconds(), - MediaTimeToSeconds(mStateComputedTime))); + STREAM_LOG(PR_LOG_DEBUG+1, ("Updating current time to %f (real %f, mStateComputedTime %f)", + MediaTimeToSeconds(nextCurrentTime), + (now - mInitialTimeStamp).ToSeconds(), + MediaTimeToSeconds(mStateComputedTime))); } else { prevCurrentTime = mCurrentTime; nextCurrentTime = mCurrentTime + MEDIA_GRAPH_TARGET_PERIOD_MS; - LOG(PR_LOG_DEBUG+1, ("Updating offline current time to %f (mStateComputedTime %f)", - MediaTimeToSeconds(nextCurrentTime), - MediaTimeToSeconds(mStateComputedTime))); + STREAM_LOG(PR_LOG_DEBUG+1, ("Updating offline current time to %f (mStateComputedTime %f)", + MediaTimeToSeconds(nextCurrentTime), + MediaTimeToSeconds(mStateComputedTime))); } if (mStateComputedTime < nextCurrentTime) { - LOG(PR_LOG_WARNING, ("Media graph global underrun detected")); + STREAM_LOG(PR_LOG_WARNING, ("Media graph global underrun detected")); nextCurrentTime = mStateComputedTime; } if (prevCurrentTime >= nextCurrentTime) { NS_ASSERTION(prevCurrentTime == nextCurrentTime, "Time can't go backwards!"); // This could happen due to low clock resolution, maybe? - LOG(PR_LOG_DEBUG, ("Time did not advance")); + STREAM_LOG(PR_LOG_DEBUG, ("Time did not advance")); // There's not much left to do here, but the code below that notifies // listeners that streams have ended still needs to run. } @@ -386,9 +397,9 @@ MediaStreamGraphImpl::UpdateCurrentTime() if (stream->mFinished && !stream->mNotifiedFinished) { streamsReadyToFinish.AppendElement(stream); } - LOG(PR_LOG_DEBUG+1, ("MediaStream %p bufferStartTime=%f blockedTime=%f", - stream, MediaTimeToSeconds(stream->mBufferStartTime), - MediaTimeToSeconds(blockedTime))); + STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p bufferStartTime=%f blockedTime=%f", + stream, MediaTimeToSeconds(stream->mBufferStartTime), + MediaTimeToSeconds(blockedTime))); } mCurrentTime = nextCurrentTime; @@ -422,19 +433,19 @@ MediaStreamGraphImpl::WillUnderrun(MediaStream* aStream, GraphTime aTime, INCLUDE_TRAILING_BLOCKED_INTERVAL); #ifdef DEBUG if (bufferEnd < mCurrentTime) { - LOG(PR_LOG_ERROR, ("MediaStream %p underrun, " - "bufferEnd %f < mCurrentTime %f (%lld < %lld), Streamtime %lld", - aStream, MediaTimeToSeconds(bufferEnd), MediaTimeToSeconds(mCurrentTime), - bufferEnd, mCurrentTime, aStream->GetBufferEnd())); + STREAM_LOG(PR_LOG_ERROR, ("MediaStream %p underrun, " + "bufferEnd %f < mCurrentTime %f (%lld < %lld), Streamtime %lld", + aStream, MediaTimeToSeconds(bufferEnd), MediaTimeToSeconds(mCurrentTime), + bufferEnd, mCurrentTime, aStream->GetBufferEnd())); aStream->DumpTrackInfo(); NS_ASSERTION(bufferEnd >= mCurrentTime, "Buffer underran"); } #endif // We should block after bufferEnd. if (bufferEnd <= aTime) { - LOG(PR_LOG_DEBUG+1, ("MediaStream %p will block due to data underrun, " - "bufferEnd %f", - aStream, MediaTimeToSeconds(bufferEnd))); + STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p will block due to data underrun, " + "bufferEnd %f", + aStream, MediaTimeToSeconds(bufferEnd))); return true; } // We should keep blocking if we're currently blocked and we don't have @@ -443,9 +454,9 @@ MediaStreamGraphImpl::WillUnderrun(MediaStream* aStream, GraphTime aTime, // but we might as well remain unblocked and play the data we've got while // we can. if (bufferEnd <= aEndBlockingDecisions && aStream->mBlocked.GetBefore(aTime)) { - LOG(PR_LOG_DEBUG+1, ("MediaStream %p will block due to speculative data underrun, " - "bufferEnd %f", - aStream, MediaTimeToSeconds(bufferEnd))); + STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p will block due to speculative data underrun, " + "bufferEnd %f", + aStream, MediaTimeToSeconds(bufferEnd))); return true; } // Reconsider decisions at bufferEnd @@ -573,8 +584,8 @@ MediaStreamGraphImpl::RecomputeBlocking(GraphTime aEndBlockingDecisions) { bool blockingDecisionsWillChange = false; - LOG(PR_LOG_DEBUG+1, ("Media graph %p computing blocking for time %f", - this, MediaTimeToSeconds(mStateComputedTime))); + STREAM_LOG(PR_LOG_DEBUG+1, ("Media graph %p computing blocking for time %f", + this, MediaTimeToSeconds(mStateComputedTime))); for (uint32_t i = 0; i < mStreams.Length(); ++i) { MediaStream* stream = mStreams[i]; if (!stream->mInBlockingSet) { @@ -600,9 +611,9 @@ MediaStreamGraphImpl::RecomputeBlocking(GraphTime aEndBlockingDecisions) blockingDecisionsWillChange = true; } } - LOG(PR_LOG_DEBUG+1, ("Media graph %p computed blocking for interval %f to %f", - this, MediaTimeToSeconds(mStateComputedTime), - MediaTimeToSeconds(aEndBlockingDecisions))); + STREAM_LOG(PR_LOG_DEBUG+1, ("Media graph %p computed blocking for interval %f to %f", + this, MediaTimeToSeconds(mStateComputedTime), + MediaTimeToSeconds(aEndBlockingDecisions))); mStateComputedTime = aEndBlockingDecisions; if (blockingDecisionsWillChange) { @@ -676,15 +687,15 @@ MediaStreamGraphImpl::RecomputeBlockingAt(const nsTArray& aStreams if (stream->mFinished) { GraphTime endTime = StreamTimeToGraphTime(stream, stream->GetBufferEnd()); if (endTime <= aTime) { - LOG(PR_LOG_DEBUG+1, ("MediaStream %p is blocked due to being finished", stream)); + STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p is blocked due to being finished", stream)); // We'll block indefinitely MarkStreamBlocking(stream); *aEnd = aEndBlockingDecisions; continue; } else { - LOG(PR_LOG_DEBUG+1, ("MediaStream %p is finished, but not blocked yet (end at %f, with blocking at %f)", - stream, MediaTimeToSeconds(stream->GetBufferEnd()), - MediaTimeToSeconds(endTime))); + STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p is finished, but not blocked yet (end at %f, with blocking at %f)", + stream, MediaTimeToSeconds(stream->GetBufferEnd()), + MediaTimeToSeconds(endTime))); *aEnd = std::min(*aEnd, endTime); } } @@ -693,7 +704,7 @@ MediaStreamGraphImpl::RecomputeBlockingAt(const nsTArray& aStreams bool explicitBlock = stream->mExplicitBlockerCount.GetAt(aTime, &end) > 0; *aEnd = std::min(*aEnd, end); if (explicitBlock) { - LOG(PR_LOG_DEBUG+1, ("MediaStream %p is blocked due to explicit blocker", stream)); + STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p is blocked due to explicit blocker", stream)); MarkStreamBlocking(stream); continue; } @@ -833,8 +844,8 @@ MediaStreamGraphImpl::PlayAudio(MediaStream* aStream, TimeToTicksRoundDown(track->GetRate(), audioOutput.mBlockedAudioTime); output.InsertNullDataAtStart(endTicks - startTicks); - LOG(PR_LOG_DEBUG+1, ("MediaStream %p writing blocking-silence samples for %f to %f", - aStream, MediaTimeToSeconds(t), MediaTimeToSeconds(end))); + STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p writing blocking-silence samples for %f to %f", + aStream, MediaTimeToSeconds(t), MediaTimeToSeconds(end))); } else { TrackTicks startTicks = track->TimeToTicksRoundDown(GraphTimeToStreamTime(aStream, t)); @@ -854,9 +865,9 @@ MediaStreamGraphImpl::PlayAudio(MediaStream* aStream, NS_ASSERTION(endTicks == sliceEnd || track->IsEnded(), "Ran out of data but track not ended?"); output.ApplyVolume(volume); - LOG(PR_LOG_DEBUG+1, ("MediaStream %p writing samples for %f to %f (samples %lld to %lld)", - aStream, MediaTimeToSeconds(t), MediaTimeToSeconds(end), - startTicks, endTicks)); + STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p writing samples for %f to %f (samples %lld to %lld)", + aStream, MediaTimeToSeconds(t), MediaTimeToSeconds(end), + startTicks, endTicks)); } // Need unique id for stream & track - and we want it to match the inserter output.WriteTo(LATENCY_STREAM_ID(aStream, track->GetID()), @@ -912,9 +923,9 @@ MediaStreamGraphImpl::PlayVideo(MediaStream* aStream) if (!frame || *frame == aStream->mLastPlayedVideoFrame) return; - LOG(PR_LOG_DEBUG+1, ("MediaStream %p writing video frame %p (%dx%d)", - aStream, frame->GetImage(), frame->GetIntrinsicSize().width, - frame->GetIntrinsicSize().height)); + STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p writing video frame %p (%dx%d)", + aStream, frame->GetImage(), frame->GetIntrinsicSize().width, + frame->GetIntrinsicSize().height)); GraphTime startTime = StreamTimeToGraphTime(aStream, track->TicksToTimeRoundDown(start), INCLUDE_TRAILING_BLOCKED_INTERVAL); TimeStamp targetTime = mCurrentTimeStamp + @@ -1220,7 +1231,7 @@ MediaStreamGraphImpl::RunThread() if (finalUpdate) { // Enter shutdown mode. The stable-state handler will detect this // and complete shutdown. Destroy any streams immediately. - LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p waiting for main thread cleanup", this)); + STREAM_LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p waiting for main thread cleanup", this)); // Commit to shutting down this graph object. mLifecycleState = LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP; // No need to Destroy streams here. The main-thread owner of each @@ -1240,17 +1251,17 @@ MediaStreamGraphImpl::RunThread() // least once a minute, if we need to wake up at all timeoutMS = std::max(0, std::min(timeoutMS, 60*1000)); timeout = PR_MillisecondsToInterval(uint32_t(timeoutMS)); - LOG(PR_LOG_DEBUG+1, ("Waiting for next iteration; at %f, timeout=%f", - (now - mInitialTimeStamp).ToSeconds(), timeoutMS/1000.0)); + STREAM_LOG(PR_LOG_DEBUG+1, ("Waiting for next iteration; at %f, timeout=%f", + (now - mInitialTimeStamp).ToSeconds(), timeoutMS/1000.0)); mWaitState = WAITSTATE_WAITING_FOR_NEXT_ITERATION; } else { mWaitState = WAITSTATE_WAITING_INDEFINITELY; } if (timeout > 0) { mMonitor.Wait(timeout); - LOG(PR_LOG_DEBUG+1, ("Resuming after timeout; at %f, elapsed=%f", - (TimeStamp::Now() - mInitialTimeStamp).ToSeconds(), - (TimeStamp::Now() - now).ToSeconds())); + STREAM_LOG(PR_LOG_DEBUG+1, ("Resuming after timeout; at %f, elapsed=%f", + (TimeStamp::Now() - mInitialTimeStamp).ToSeconds(), + (TimeStamp::Now() - now).ToSeconds())); } } mWaitState = WAITSTATE_RUNNING; @@ -1289,7 +1300,7 @@ MediaStreamGraphImpl::ShutdownThreads() { NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread"); // mGraph's thread is not running so it's OK to do whatever here - LOG(PR_LOG_DEBUG, ("Stopping threads for MediaStreamGraph %p", this)); + STREAM_LOG(PR_LOG_DEBUG, ("Stopping threads for MediaStreamGraph %p", this)); if (mThread) { mThread->Shutdown(); @@ -1301,7 +1312,7 @@ void MediaStreamGraphImpl::ForceShutDown() { NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread"); - LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p ForceShutdown", this)); + STREAM_LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p ForceShutdown", this)); { MonitorAutoLock lock(mMonitor); mForceShutDown = true; @@ -1475,7 +1486,7 @@ MediaStreamGraphImpl::RunInStableState() if (mLifecycleState == LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP && IsEmpty()) { // Complete shutdown. First, ensure that this graph is no longer used. // A new graph graph will be created if one is needed. - LOG(PR_LOG_DEBUG, ("Disconnecting MediaStreamGraph %p", this)); + STREAM_LOG(PR_LOG_DEBUG, ("Disconnecting MediaStreamGraph %p", this)); if (this == gGraph) { // null out gGraph if that's the graph being shut down gGraph = nullptr; @@ -2196,8 +2207,8 @@ SourceMediaStream::GetBufferedTicks(TrackID aID) void MediaInputPort::Init() { - LOG(PR_LOG_DEBUG, ("Adding MediaInputPort %p (from %p to %p) to the graph", - this, mSource, mDest)); + STREAM_LOG(PR_LOG_DEBUG, ("Adding MediaInputPort %p (from %p to %p) to the graph", + this, mSource, mDest)); mSource->AddConsumer(this); mDest->AddInput(this); // mPortCount decremented via MediaInputPort::Destroy's message @@ -2415,7 +2426,7 @@ MediaStreamGraph::GetInstance() } gGraph = new MediaStreamGraphImpl(true); - LOG(PR_LOG_DEBUG, ("Starting up MediaStreamGraph %p", gGraph)); + STREAM_LOG(PR_LOG_DEBUG, ("Starting up MediaStreamGraph %p", gGraph)); } return gGraph; diff --git a/content/media/MediaStreamGraph.h b/content/media/MediaStreamGraph.h index cf1e4a5d6f43..7a35db6cb52c 100644 --- a/content/media/MediaStreamGraph.h +++ b/content/media/MediaStreamGraph.h @@ -1057,7 +1057,7 @@ protected: { MOZ_COUNT_CTOR(MediaStreamGraph); } - ~MediaStreamGraph() + virtual ~MediaStreamGraph() { MOZ_COUNT_DTOR(MediaStreamGraph); } diff --git a/content/media/MediaStreamGraphImpl.h b/content/media/MediaStreamGraphImpl.h index c7d2e8721b21..c8cd27a8b1be 100644 --- a/content/media/MediaStreamGraphImpl.h +++ b/content/media/MediaStreamGraphImpl.h @@ -19,13 +19,6 @@ namespace mozilla { template class LinkedList; -#ifdef PR_LOGGING -extern PRLogModuleInfo* gMediaStreamGraphLog; -#define LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg) -#else -#define LOG(type, msg) -#endif - /** * Assume we can run an iteration of the MediaStreamGraph loop in this much time * or less. @@ -120,12 +113,7 @@ public: * implement OfflineAudioContext. They do not support MediaStream inputs. */ explicit MediaStreamGraphImpl(bool aRealtime); - ~MediaStreamGraphImpl() - { - NS_ASSERTION(IsEmpty(), - "All streams should have been destroyed by messages from the main thread"); - LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p destroyed", this)); - } + virtual ~MediaStreamGraphImpl(); // Main thread only. /** diff --git a/content/media/RtspMediaResource.cpp b/content/media/RtspMediaResource.cpp index 20f78594e002..d1a143cd7fc7 100644 --- a/content/media/RtspMediaResource.cpp +++ b/content/media/RtspMediaResource.cpp @@ -18,13 +18,13 @@ #ifdef PR_LOGGING PRLogModuleInfo* gRtspMediaResourceLog; -#define LOG(msg, ...) PR_LOG(gRtspMediaResourceLog, PR_LOG_DEBUG, \ - (msg, ##__VA_ARGS__)) +#define RTSP_LOG(msg, ...) PR_LOG(gRtspMediaResourceLog, PR_LOG_DEBUG, \ + (msg, ##__VA_ARGS__)) // Debug logging macro with object pointer and class name. #define RTSPMLOG(msg, ...) \ - LOG("%p [RtspMediaResource]: " msg, this, ##__VA_ARGS__) + RTSP_LOG("%p [RtspMediaResource]: " msg, this, ##__VA_ARGS__) #else -#define LOG(msg, ...) +#define RTSP_LOG(msg, ...) #define RTSPMLOG(msg, ...) #endif @@ -72,6 +72,7 @@ public: void Start() { MonitorAutoLock monitor(mMonitor); mIsStarted = true; + mFrameType = 0; } void Stop() { MonitorAutoLock monitor(mMonitor); @@ -444,6 +445,9 @@ RtspMediaResource::OnConnected(uint8_t aTrackIdx, nsIStreamingProtocolMetaData *meta) { if (mIsConnected) { + for (uint32_t i = 0 ; i < mTrackBuffer.Length(); ++i) { + mTrackBuffer[i]->Start(); + } return NS_OK; } @@ -523,9 +527,19 @@ RtspMediaResource::OnDisconnected(uint8_t aTrackIdx, nsresult aReason) mTrackBuffer[i]->Reset(); } - if (aReason == NS_ERROR_CONNECTION_REFUSED) { + if (aReason == NS_ERROR_NOT_INITIALIZED || + aReason == NS_ERROR_CONNECTION_REFUSED || + aReason == NS_ERROR_NOT_CONNECTED) { + + RTSPMLOG("Error in OnDisconnected 0x%x", aReason); + mDecoder->NetworkError(); + return NS_OK; } + + // Resetting the decoder and media element when the connection + // between Rtsp client and server goes down. + mDecoder->ResetConnectionState(); return NS_OK; } diff --git a/content/media/StreamBuffer.cpp b/content/media/StreamBuffer.cpp index 21f250c6deac..51d5f0d2ffd4 100644 --- a/content/media/StreamBuffer.cpp +++ b/content/media/StreamBuffer.cpp @@ -11,23 +11,23 @@ namespace mozilla { #ifdef PR_LOGGING extern PRLogModuleInfo* gMediaStreamGraphLog; -#define LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg) +#define STREAM_LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg) #else -#define LOG(type, msg) +#define STREAM_LOG(type, msg) #endif #ifdef DEBUG void StreamBuffer::DumpTrackInfo() const { - LOG(PR_LOG_ALWAYS, ("DumpTracks: mTracksKnownTime %lld", mTracksKnownTime)); + STREAM_LOG(PR_LOG_ALWAYS, ("DumpTracks: mTracksKnownTime %lld", mTracksKnownTime)); for (uint32_t i = 0; i < mTracks.Length(); ++i) { Track* track = mTracks[i]; if (track->IsEnded()) { - LOG(PR_LOG_ALWAYS, ("Track[%d] %d: ended", i, track->GetID())); + STREAM_LOG(PR_LOG_ALWAYS, ("Track[%d] %d: ended", i, track->GetID())); } else { - LOG(PR_LOG_ALWAYS, ("Track[%d] %d: %lld", i, track->GetID(), - track->GetEndTimeRoundDown())); + STREAM_LOG(PR_LOG_ALWAYS, ("Track[%d] %d: %lld", i, track->GetID(), + track->GetEndTimeRoundDown())); } } } diff --git a/content/media/TrackUnionStream.h b/content/media/TrackUnionStream.h index ff0881fc3ecb..800480d84483 100644 --- a/content/media/TrackUnionStream.h +++ b/content/media/TrackUnionStream.h @@ -12,9 +12,9 @@ namespace mozilla { #ifdef PR_LOGGING -#define LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg) +#define STREAM_LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg) #else -#define LOG(type, msg) +#define STREAM_LOG(type, msg) #endif /** @@ -178,9 +178,9 @@ protected: segment->AppendNullData(outputStart); StreamBuffer::Track* track = &mBuffer.AddTrack(id, rate, outputStart, segment.forget()); - LOG(PR_LOG_DEBUG, ("TrackUnionStream %p adding track %d for input stream %p track %d, start ticks %lld", - this, id, aPort->GetSource(), aTrack->GetID(), - (long long)outputStart)); + STREAM_LOG(PR_LOG_DEBUG, ("TrackUnionStream %p adding track %d for input stream %p track %d, start ticks %lld", + this, id, aPort->GetSource(), aTrack->GetID(), + (long long)outputStart)); TrackMapEntry* map = mTrackMap.AppendElement(); map->mEndOfConsumedInputTicks = 0; @@ -253,8 +253,8 @@ protected: if (interval.mInputIsBlocked) { // Maybe the input track ended? segment->AppendNullData(ticks); - LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of null data to track %d", - this, (long long)ticks, outputTrack->GetID())); + STREAM_LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of null data to track %d", + this, (long long)ticks, outputTrack->GetID())); } else { // Figuring out which samples to use from the input stream is tricky // because its start time and our start time may differ by a fraction @@ -322,9 +322,9 @@ protected: std::min(inputTrackEndPoint, inputStartTicks), std::min(inputTrackEndPoint, inputEndTicks)); } - LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of input data to track %d", - this, (long long)(std::min(inputTrackEndPoint, inputEndTicks) - std::min(inputTrackEndPoint, inputStartTicks)), - outputTrack->GetID())); + STREAM_LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of input data to track %d", + this, (long long)(std::min(inputTrackEndPoint, inputEndTicks) - std::min(inputTrackEndPoint, inputStartTicks)), + outputTrack->GetID())); } ApplyTrackDisabling(outputTrack->GetID(), segment); for (uint32_t j = 0; j < mListeners.Length(); ++j) { diff --git a/content/media/WebVTTListener.cpp b/content/media/WebVTTListener.cpp index 28c933527894..6cff168ea803 100644 --- a/content/media/WebVTTListener.cpp +++ b/content/media/WebVTTListener.cpp @@ -30,9 +30,9 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(WebVTTListener) #ifdef PR_LOGGING PRLogModuleInfo* gTextTrackLog; -# define LOG(...) PR_LOG(gTextTrackLog, PR_LOG_DEBUG, (__VA_ARGS__)) +# define VTT_LOG(...) PR_LOG(gTextTrackLog, PR_LOG_DEBUG, (__VA_ARGS__)) #else -# define LOG(msg) +# define VTT_LOG(msg) #endif WebVTTListener::WebVTTListener(HTMLTrackElement* aElement) @@ -44,12 +44,12 @@ WebVTTListener::WebVTTListener(HTMLTrackElement* aElement) gTextTrackLog = PR_NewLogModule("TextTrack"); } #endif - LOG("WebVTTListener created."); + VTT_LOG("WebVTTListener created."); } WebVTTListener::~WebVTTListener() { - LOG("WebVTTListener destroyed."); + VTT_LOG("WebVTTListener destroyed."); } NS_IMETHODIMP @@ -124,7 +124,7 @@ WebVTTListener::ParseChunk(nsIInputStream* aInStream, void* aClosure, WebVTTListener* listener = static_cast(aClosure); if (NS_FAILED(listener->mParserWrapper->Parse(buffer))) { - LOG("Unable to parse chunk of WEBVTT text. Aborting."); + VTT_LOG("Unable to parse chunk of WEBVTT text. Aborting."); *aWriteCount = 0; return NS_ERROR_FAILURE; } diff --git a/content/media/encoder/OpusTrackEncoder.cpp b/content/media/encoder/OpusTrackEncoder.cpp index d89b15e9ed24..735dc22d9520 100644 --- a/content/media/encoder/OpusTrackEncoder.cpp +++ b/content/media/encoder/OpusTrackEncoder.cpp @@ -127,6 +127,10 @@ OpusTrackEncoder::~OpusTrackEncoder() if (mEncoder) { opus_encoder_destroy(mEncoder); } + if (mResampler) { + speex_resampler_destroy(mResampler); + } + } nsresult @@ -324,6 +328,7 @@ OpusTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData) mDoneEncoding = true; if (mResampler) { speex_resampler_destroy(mResampler); + mResampler = nullptr; } LOG("[Opus] Done encoding."); } diff --git a/content/media/fmp4/BlankDecoderModule.cpp b/content/media/fmp4/BlankDecoderModule.cpp new file mode 100644 index 000000000000..bef6244072be --- /dev/null +++ b/content/media/fmp4/BlankDecoderModule.cpp @@ -0,0 +1,225 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MediaDecoderReader.h" +#include "PlatformDecoderModule.h" +#include "nsRect.h" +#include "mozilla/RefPtr.h" +#include "mozilla/CheckedInt.h" +#include "VideoUtils.h" +#include "ImageContainer.h" + +namespace mozilla { + +// Decoder that uses a passed in object's Create function to create blank +// MediaData objects. +template +class BlankMediaDataDecoder : public MediaDataDecoder { +public: + + BlankMediaDataDecoder(BlankMediaDataCreator* aCreator) + : mCreator(aCreator), + mNextDTS(-1), + mNextOffset(-1) + { + } + + virtual nsresult Shutdown() MOZ_OVERRIDE { + return NS_OK; + } + + virtual DecoderStatus Input(const uint8_t* aData, + uint32_t aLength, + Microseconds aDTS, + Microseconds aPTS, + int64_t aOffsetInStream) MOZ_OVERRIDE + { + // Accepts input, and outputs on the second input, using the difference + // in DTS as the duration. + if (mOutput) { + return DECODE_STATUS_NOT_ACCEPTING; + } + if (mNextDTS != -1 && mNextOffset != -1) { + Microseconds duration = aDTS - mNextDTS; + mOutput = mCreator->Create(mNextDTS, duration, mNextOffset); + } + + mNextDTS = aDTS; + mNextOffset = aOffsetInStream; + return DECODE_STATUS_OK; + } + + virtual DecoderStatus Output(nsAutoPtr& aOutData) MOZ_OVERRIDE + { + if (!mOutput) { + return DECODE_STATUS_NEED_MORE_INPUT; + } + aOutData = mOutput.forget(); + return DECODE_STATUS_OK; + } + + virtual DecoderStatus Flush() MOZ_OVERRIDE { + return DECODE_STATUS_OK; + } +private: + nsAutoPtr mCreator; + Microseconds mNextDTS; + int64_t mNextOffset; + nsAutoPtr mOutput; + bool mHasInput; +}; + +static const uint32_t sFrameWidth = 320; +static const uint32_t sFrameHeight = 240; + +class BlankVideoDataCreator { +public: + BlankVideoDataCreator(layers::ImageContainer* aImageContainer) + : mImageContainer(aImageContainer) + { + mInfo.mDisplay = nsIntSize(sFrameWidth, sFrameHeight); + mPicture = nsIntRect(0, 0, sFrameWidth, sFrameHeight); + } + + MediaData* Create(Microseconds aDTS, + Microseconds aDuration, + int64_t aOffsetInStream) + { + // Create a fake YUV buffer in a 420 format. That is, an 8bpp Y plane, + // with a U and V plane that are half the size of the Y plane, i.e 8 bit, + // 2x2 subsampled. Have the data pointers of each frame point to the + // first plane, they'll always be zero'd memory anyway. + uint8_t* frame = new uint8_t[sFrameWidth * sFrameHeight]; + memset(frame, 0, sFrameWidth * sFrameHeight); + VideoData::YCbCrBuffer buffer; + + // Y plane. + buffer.mPlanes[0].mData = frame; + buffer.mPlanes[0].mStride = sFrameWidth; + buffer.mPlanes[0].mHeight = sFrameHeight; + buffer.mPlanes[0].mWidth = sFrameWidth; + buffer.mPlanes[0].mOffset = 0; + buffer.mPlanes[0].mSkip = 0; + + // Cb plane. + buffer.mPlanes[1].mData = frame; + buffer.mPlanes[1].mStride = sFrameWidth / 2; + buffer.mPlanes[1].mHeight = sFrameHeight / 2; + buffer.mPlanes[1].mWidth = sFrameWidth / 2; + buffer.mPlanes[1].mOffset = 0; + buffer.mPlanes[1].mSkip = 0; + + // Cr plane. + buffer.mPlanes[2].mData = frame; + buffer.mPlanes[2].mStride = sFrameWidth / 2; + buffer.mPlanes[2].mHeight = sFrameHeight / 2; + buffer.mPlanes[2].mWidth = sFrameWidth / 2; + buffer.mPlanes[2].mOffset = 0; + buffer.mPlanes[2].mSkip = 0; + + return VideoData::Create(mInfo, + mImageContainer, + nullptr, + aOffsetInStream, + aDTS, + aDuration, + buffer, + true, + aDTS, + mPicture); + } +private: + VideoInfo mInfo; + nsIntRect mPicture; + RefPtr mImageContainer; +}; + + +class BlankAudioDataCreator { +public: + BlankAudioDataCreator(uint32_t aChannelCount, + uint32_t aSampleRate, + uint16_t aBitsPerSample) + : mFrameSum(0), + mChannelCount(aChannelCount), + mSampleRate(aSampleRate), + mBitsPerSample(aBitsPerSample) + { + } + + MediaData* Create(Microseconds aDTS, + Microseconds aDuration, + int64_t aOffsetInStream) + { + // Convert duration to frames. We add 1 to duration to account for + // rounding errors, so we get a consistent tone. + CheckedInt64 frames = UsecsToFrames(aDuration+1, mSampleRate); + if (!frames.isValid() || + !mChannelCount || + !mSampleRate || + frames.value() > (UINT32_MAX / mChannelCount)) { + return nullptr; + } + AudioDataValue* samples = new AudioDataValue[frames.value() * mChannelCount]; + // Fill the sound buffer with an A4 tone. + static const float pi = 3.14159265f; + static const float noteHz = 440.0f; + for (int i = 0; i < frames.value(); i++) { + float f = sin(2 * pi * noteHz * mFrameSum / mSampleRate); + for (unsigned c = 0; c < mChannelCount; c++) { + samples[i * mChannelCount + c] = AudioDataValue(f); + } + mFrameSum++; + } + return new AudioData(aOffsetInStream, + aDTS, + aDuration, + uint32_t(frames.value()), + samples, + mChannelCount); + } + +private: + int64_t mFrameSum; + uint32_t mChannelCount; + uint32_t mSampleRate; + uint16_t mBitsPerSample; +}; + +class BlankDecoderModule : public PlatformDecoderModule { +public: + + // Called when the decoders have shutdown. Main thread only. + virtual nsresult Shutdown() MOZ_OVERRIDE { + return NS_OK; + } + + // Decode thread. + virtual MediaDataDecoder* CreateVideoDecoder(layers::LayersBackend aLayersBackend, + layers::ImageContainer* aImageContainer) MOZ_OVERRIDE { + BlankVideoDataCreator* decoder = new BlankVideoDataCreator(aImageContainer); + return new BlankMediaDataDecoder(decoder); + } + + // Decode thread. + virtual MediaDataDecoder* CreateAudioDecoder(uint32_t aChannelCount, + uint32_t aSampleRate, + uint16_t aBitsPerSample, + const uint8_t* aUserData, + uint32_t aUserDataLength) MOZ_OVERRIDE { + BlankAudioDataCreator* decoder = new BlankAudioDataCreator(aChannelCount, + aSampleRate, + aBitsPerSample); + return new BlankMediaDataDecoder(decoder); + } +}; + +PlatformDecoderModule* CreateBlankDecoderModule() +{ + return new BlankDecoderModule(); +} + +} // namespace mozilla diff --git a/content/media/fmp4/MP4Decoder.cpp b/content/media/fmp4/MP4Decoder.cpp new file mode 100644 index 000000000000..1d912f0f1d3d --- /dev/null +++ b/content/media/fmp4/MP4Decoder.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MP4Decoder.h" +#include "MP4Reader.h" +#include "MediaDecoderStateMachine.h" +#include "mozilla/Preferences.h" + +#ifdef XP_WIN +#include "WinUtils.h" +using namespace mozilla::widget; +#endif + +namespace mozilla { + +MediaDecoderStateMachine* MP4Decoder::CreateStateMachine() +{ + return new MediaDecoderStateMachine(this, new MP4Reader(this)); +} + +bool +MP4Decoder::GetSupportedCodecs(const nsACString& aType, + char const *const ** aCodecList) +{ + if (!IsEnabled()) { + return false; + } + + // AAC in M4A. + static char const *const aacAudioCodecs[] = { + "mp4a.40.2", // AAC-LC + // TODO: AAC-HE ? + nullptr + }; + if (aType.EqualsASCII("audio/mp4") || + aType.EqualsASCII("audio/x-m4a")) { + if (aCodecList) { + *aCodecList = aacAudioCodecs; + } + return true; + } + + // H.264 + AAC in MP4. + static char const *const h264Codecs[] = { + "avc1.42E01E", // H.264 Constrained Baseline Profile Level 3.0 + "avc1.42001E", // H.264 Baseline Profile Level 3.0 + "avc1.58A01E", // H.264 Extended Profile Level 3.0 + "avc1.4D401E", // H.264 Main Profile Level 3.0 + "avc1.64001E", // H.264 High Profile Level 3.0 + "avc1.64001F", // H.264 High Profile Level 3.1 + "mp4a.40.2", // AAC-LC + // TODO: There must be more profiles here? + nullptr + }; + if (aType.EqualsASCII("video/mp4")) { + if (aCodecList) { + *aCodecList = h264Codecs; + } + return true; + } + + return false; +} + +static bool +HavePlatformMPEGDecoders() +{ + return + Preferences::GetBool("media.fragmented-mp4.use-blank-decoder") || +#ifdef XP_WIN + // We have H.264/AAC platform decoders on Windows Vista and up. + WinUtils::GetWindowsVersion() >= WinUtils::VISTA_VERSION || +#endif + // TODO: Other platforms... + false; +} + +/* static */ +bool +MP4Decoder::IsEnabled() +{ + return HavePlatformMPEGDecoders() && + Preferences::GetBool("media.fragmented-mp4.enabled"); +} + +} // namespace mozilla + diff --git a/content/media/fmp4/MP4Decoder.h b/content/media/fmp4/MP4Decoder.h new file mode 100644 index 000000000000..323381f0b50b --- /dev/null +++ b/content/media/fmp4/MP4Decoder.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(MP4Decoder_h_) +#define MP4Decoder_h_ + +#include "MediaDecoder.h" + +namespace mozilla { + +// Decoder that uses a bundled MP4 demuxer and platform decoders to play MP4. +class MP4Decoder : public MediaDecoder +{ +public: + + virtual MediaDecoder* Clone() { + if (!IsEnabled()) { + return nullptr; + } + return new MP4Decoder(); + } + + virtual MediaDecoderStateMachine* CreateStateMachine(); + + // Returns true if aType is a MIME type that we can render with the + // a MP4 platform decoder backend. If aCodecList is non null, + // it is filled with a (static const) null-terminated list of strings + // denoting the codecs we'll playback. + static bool GetSupportedCodecs(const nsACString& aType, + char const *const ** aCodecList); + + // Returns true if the MP4 backend is preffed on, and we're running on a + // platform that is likely to have decoders for the contained formats. + static bool IsEnabled(); +}; + +} // namespace mozilla + +#endif diff --git a/content/media/fmp4/MP4Reader.cpp b/content/media/fmp4/MP4Reader.cpp new file mode 100644 index 000000000000..0a3bf7948ed3 --- /dev/null +++ b/content/media/fmp4/MP4Reader.cpp @@ -0,0 +1,393 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MP4Reader.h" +#include "MediaResource.h" +#include "mp4_demuxer/mp4_demuxer.h" +#include "mp4_demuxer/Streams.h" +#include "nsSize.h" +#include "VideoUtils.h" +#include "mozilla/dom/HTMLMediaElement.h" +#include "ImageContainer.h" +#include "Layers.h" + +using mozilla::layers::Image; +using mozilla::layers::LayerManager; +using mozilla::layers::LayersBackend; + +#ifdef PR_LOGGING +PRLogModuleInfo* GetDemuxerLog() { + static PRLogModuleInfo* log = nullptr; + if (!log) { + log = PR_NewLogModule("MP4Demuxer"); + } + return log; +} +#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__)) +#else +#define LOG(...) +#endif + +using namespace mp4_demuxer; + +namespace mozilla { + +// Uncomment to enable verbose per-sample logging. +//#define LOG_SAMPLE_DECODE 1 + +class MP4Stream : public mp4_demuxer::Stream { +public: + + MP4Stream(MediaResource* aResource) + : mResource(aResource) + { + MOZ_COUNT_CTOR(MP4Stream); + MOZ_ASSERT(aResource); + } + ~MP4Stream() { + MOZ_COUNT_DTOR(MP4Stream); + } + + virtual bool ReadAt(int64_t aOffset, + uint8_t* aBuffer, + uint32_t aCount, + uint32_t* aBytesRead) MOZ_OVERRIDE { + uint32_t sum = 0; + do { + uint32_t offset = aOffset + sum; + char* buffer = reinterpret_cast(aBuffer + sum); + uint32_t toRead = aCount - sum; + uint32_t bytesRead = 0; + nsresult rv = mResource->ReadAt(offset, buffer, toRead, &bytesRead); + if (NS_FAILED(rv)) { + return false; + } + sum += bytesRead; + } while (sum < aCount); + *aBytesRead = sum; + return true; + } + + virtual int64_t Length() const MOZ_OVERRIDE { + return mResource->GetLength(); + } + +private: + RefPtr mResource; +}; + +MP4Reader::MP4Reader(AbstractMediaDecoder* aDecoder) + : MediaDecoderReader(aDecoder), + mLayersBackendType(layers::LAYERS_NONE), + mHasAudio(false), + mHasVideo(false) +{ + MOZ_COUNT_CTOR(MP4Reader); +} + +MP4Reader::~MP4Reader() +{ + MOZ_COUNT_DTOR(MP4Reader); +} + +nsresult +MP4Reader::Init(MediaDecoderReader* aCloneDonor) +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + mMP4Stream = new MP4Stream(mDecoder->GetResource()); + mDemuxer = new MP4Demuxer(mMP4Stream); + + mPlatform = PlatformDecoderModule::Create(); + NS_ENSURE_TRUE(mPlatform, NS_ERROR_FAILURE); + + if (IsVideoContentType(mDecoder->GetResource()->GetContentType())) { + // Extract the layer manager backend type so that platform decoders + // can determine whether it's worthwhile using hardware accelerated + // video decoding. + MediaDecoderOwner* owner = mDecoder->GetOwner(); + NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE); + + dom::HTMLMediaElement* element = owner->GetMediaElement(); + NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); + + nsRefPtr layerManager = + nsContentUtils::LayerManagerForDocument(element->OwnerDoc()); + NS_ENSURE_TRUE(layerManager, NS_ERROR_FAILURE); + + mLayersBackendType = layerManager->GetBackendType(); + } + + return NS_OK; +} + +nsresult +MP4Reader::ReadMetadata(MediaInfo* aInfo, + MetadataTags** aTags) +{ + bool ok = mDemuxer->Init(); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + mInfo.mAudio.mHasAudio = mHasAudio = mDemuxer->HasAudio(); + if (mHasAudio) { + const AudioDecoderConfig& config = mDemuxer->AudioConfig(); + mInfo.mAudio.mRate = config.samples_per_second(); + mInfo.mAudio.mChannels = ChannelLayoutToChannelCount(config.channel_layout()); + mAudioDecoder = mPlatform->CreateAudioDecoder(mInfo.mAudio.mChannels, + mInfo.mAudio.mRate, + config.bits_per_channel(), + config.extra_data(), + config.extra_data_size()); + NS_ENSURE_TRUE(mAudioDecoder != nullptr, NS_ERROR_FAILURE); + } + + mInfo.mVideo.mHasVideo = mHasVideo = mDemuxer->HasVideo(); + if (mHasVideo) { + const VideoDecoderConfig& config = mDemuxer->VideoConfig(); + IntSize sz = config.natural_size(); + mInfo.mVideo.mDisplay = nsIntSize(sz.width(), sz.height()); + + mVideoDecoder = mPlatform->CreateVideoDecoder(mLayersBackendType, + mDecoder->GetImageContainer()); + NS_ENSURE_TRUE(mVideoDecoder != nullptr, NS_ERROR_FAILURE); + } + + // Get the duration, and report it to the decoder if we have it. + Microseconds duration = mDemuxer->Duration(); + if (duration != -1) { + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + mDecoder->SetMediaDuration(duration); + } + // We can seek if we get a duration *and* the reader reports that it's + // seekable. + if (!mDemuxer->CanSeek()) { + mDecoder->SetMediaSeekable(false); + } + + *aInfo = mInfo; + *aTags = nullptr; + + return NS_OK; +} + +bool +MP4Reader::HasAudio() +{ + return mHasAudio; +} + +bool +MP4Reader::HasVideo() +{ + return mHasVideo; +} + +MP4SampleQueue& +MP4Reader::SampleQueue(TrackType aTrack) +{ + MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo); + return (aTrack == kAudio) ? mCompressedAudioQueue + : mCompressedVideoQueue; +} + +MediaDataDecoder* +MP4Reader::Decoder(mp4_demuxer::TrackType aTrack) +{ + MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo); + return (aTrack == kAudio) ? mAudioDecoder + : mVideoDecoder; +} + +MP4Sample* +MP4Reader::PopSample(TrackType aTrack) +{ + // Unfortunately the demuxer outputs in the order samples appear in the + // media, not on a per stream basis. We cache the samples we get from + // streams other than the one we want. + MP4SampleQueue& sampleQueue = SampleQueue(aTrack); + while (sampleQueue.empty()) { + nsAutoPtr sample; + bool eos = false; + bool ok = mDemuxer->Demux(&sample, &eos); + if (!ok || eos) { + MOZ_ASSERT(!sample); + return nullptr; + } + MOZ_ASSERT(sample); + MP4Sample* s = sample.forget(); + SampleQueue(s->type).push_back(s); + } + MOZ_ASSERT(!sampleQueue.empty()); + MP4Sample* sample = sampleQueue.front(); + sampleQueue.pop_front(); + return sample; +} + +bool +MP4Reader::Decode(TrackType aTrack, nsAutoPtr& aOutData) +{ + MP4SampleQueue& sampleQueue = SampleQueue(aTrack); + MediaDataDecoder* decoder = Decoder(aTrack); + + MOZ_ASSERT(decoder); + + // Loop until we hit a return condition; we produce samples, or hit an error. + while (true) { + DecoderStatus status = decoder->Output(aOutData); + if (status == DECODE_STATUS_OK) { + MOZ_ASSERT(aOutData); + return true; + } + // |aOutData| should only be non-null in success case. + MOZ_ASSERT(!aOutData); + + if (status == DECODE_STATUS_ERROR) { + return false; + } + + if (status == DECODE_STATUS_NEED_MORE_INPUT) { + // We need to push more data from the demuxer into the decoder. + // Now loop back and try to extract output again. + nsAutoPtr compressed; + do { + compressed = PopSample(aTrack); + if (!compressed) { + // EOS, or error. Let the state machine know there are no more + // frames coming. + return false; + } + const std::vector* data = compressed->data; + status = decoder->Input(&data->front(), + data->size(), + compressed->decode_timestamp, + compressed->composition_timestamp, + compressed->byte_offset); + } while (status == DECODE_STATUS_OK); + if (status == DECODE_STATUS_NOT_ACCEPTING) { + // Decoder should now be able to produce an output. + SampleQueue(aTrack).push_front(compressed.forget()); + continue; + } + LOG("MP4Reader decode failure. track=%d status=%d\n", aTrack, status); + return false; + } else { + LOG("MP4Reader unexpected error. track=%d status=%d\n", aTrack, status); + return false; + } + } +} + +bool +MP4Reader::DecodeAudioData() +{ + MOZ_ASSERT(mHasAudio && mPlatform && mAudioDecoder); + nsAutoPtr audio; + bool ok = Decode(kAudio, audio); + if (ok && audio && audio->mType == MediaData::AUDIO_SAMPLES) { +#ifdef LOG_SAMPLE_DECODE + LOG("DecodeAudioData time=%lld dur=%lld", audio->mTime, audio->mDuration); +#endif + mAudioQueue.Push(static_cast(audio.forget())); + } + return ok; +} + +bool +MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed) +{ + MOZ_ASSERT(mVideoDecoder); + + // Purge the current decoder's state. + mVideoDecoder->Flush(); + + // Loop until we reach the next keyframe after the threshold. + while (true) { + nsAutoPtr compressed(PopSample(kVideo)); + if (!compressed) { + // EOS, or error. Let the state machine know. + return false; + } + parsed++; + if (!compressed->is_sync_point || + compressed->composition_timestamp < aTimeThreshold) { + continue; + } + mCompressedVideoQueue.push_front(compressed.forget()); + break; + } + + return true; +} + +bool +MP4Reader::DecodeVideoFrame(bool &aKeyframeSkip, + int64_t aTimeThreshold) +{ + // Record number of frames decoded and parsed. Automatically update the + // stats counters using the AutoNotifyDecoded stack-based class. + uint32_t parsed = 0, decoded = 0; + AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded); + + MOZ_ASSERT(mHasVideo && mPlatform && mVideoDecoder); + + if (aKeyframeSkip) { + bool ok = SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed); + if (!ok) { + NS_WARNING("Failed to skip demux up to next keyframe"); + return false; + } + aKeyframeSkip = false; + } + + nsAutoPtr data; + bool ok = Decode(kVideo, data); + MOZ_ASSERT(!data || data->mType == MediaData::VIDEO_FRAME); + if (ok && data) { + parsed++; + if (data->mTime < aTimeThreshold) { + // Skip frame, it's too late to be displayed. + return true; + } + decoded++; + VideoData* video = static_cast(data.forget()); +#ifdef LOG_SAMPLE_DECODE + LOG("DecodeVideoData time=%lld dur=%lld", video->mTime, video->mDuration); +#endif + mVideoQueue.Push(video); + } + return ok; +} + +nsresult +MP4Reader::Seek(int64_t aTime, + int64_t aStartTime, + int64_t aEndTime, + int64_t aCurrentTime) +{ + if (!mDemuxer->CanSeek()) { + return NS_ERROR_FAILURE; + } + return NS_ERROR_NOT_IMPLEMENTED; +} + +void +MP4Reader::OnDecodeThreadStart() +{ + MOZ_ASSERT(!NS_IsMainThread(), "Must not be on main thread."); + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); + MOZ_ASSERT(mPlatform); + mPlatform->OnDecodeThreadStart(); +} + +void +MP4Reader::OnDecodeThreadFinish() +{ + MOZ_ASSERT(!NS_IsMainThread(), "Must not be on main thread."); + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); + MOZ_ASSERT(mPlatform); + mPlatform->OnDecodeThreadFinish(); +} + +} // namespace mozilla diff --git a/content/media/fmp4/MP4Reader.h b/content/media/fmp4/MP4Reader.h new file mode 100644 index 000000000000..d7d1e23597c8 --- /dev/null +++ b/content/media/fmp4/MP4Reader.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(MP4Reader_h_) +#define MP4Reader_h_ + +#include "MediaDecoderReader.h" +#include "nsAutoPtr.h" +#include "PlatformDecoderModule.h" +#include "mp4_demuxer/mp4_demuxer.h" +#include "mp4_demuxer/box_definitions.h" + +#include + +namespace mozilla { + +namespace dom { +class TimeRanges; +} + +typedef std::deque MP4SampleQueue; + +class MP4Stream; + +class MP4Reader : public MediaDecoderReader +{ +public: + MP4Reader(AbstractMediaDecoder* aDecoder); + + virtual ~MP4Reader(); + + virtual nsresult Init(MediaDecoderReader* aCloneDonor) MOZ_OVERRIDE; + + virtual bool DecodeAudioData() MOZ_OVERRIDE; + virtual bool DecodeVideoFrame(bool &aKeyframeSkip, + int64_t aTimeThreshold) MOZ_OVERRIDE; + + virtual bool HasAudio() MOZ_OVERRIDE; + virtual bool HasVideo() MOZ_OVERRIDE; + + virtual nsresult ReadMetadata(MediaInfo* aInfo, + MetadataTags** aTags) MOZ_OVERRIDE; + + virtual nsresult Seek(int64_t aTime, + int64_t aStartTime, + int64_t aEndTime, + int64_t aCurrentTime) MOZ_OVERRIDE; + + virtual void OnDecodeThreadStart() MOZ_OVERRIDE; + virtual void OnDecodeThreadFinish() MOZ_OVERRIDE; + +private: + + MP4SampleQueue& SampleQueue(mp4_demuxer::TrackType aTrack); + + // Blocks until the demuxer produces an sample of specified type. + // Returns nullptr on error on EOS. Caller must delete sample. + mp4_demuxer::MP4Sample* PopSample(mp4_demuxer::TrackType aTrack); + + bool Decode(mp4_demuxer::TrackType aTrack, + nsAutoPtr& aOutData); + + MediaDataDecoder* Decoder(mp4_demuxer::TrackType aTrack); + + bool SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed); + + nsAutoPtr mDemuxer; + nsAutoPtr mMP4Stream; + nsAutoPtr mPlatform; + nsAutoPtr mVideoDecoder; + nsAutoPtr mAudioDecoder; + + MP4SampleQueue mCompressedAudioQueue; + MP4SampleQueue mCompressedVideoQueue; + + layers::LayersBackend mLayersBackendType; + + bool mHasAudio; + bool mHasVideo; + +}; + +} // namespace mozilla + +#endif diff --git a/content/media/fmp4/Makefile.in b/content/media/fmp4/Makefile.in new file mode 100644 index 000000000000..642256452251 --- /dev/null +++ b/content/media/fmp4/Makefile.in @@ -0,0 +1,7 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +ifeq ($(OS_ARCH),WINNT) +OS_CXXFLAGS += -DNOMINMAX +endif diff --git a/content/media/fmp4/PlatformDecoderModule.cpp b/content/media/fmp4/PlatformDecoderModule.cpp new file mode 100644 index 000000000000..fdb37aedb0dd --- /dev/null +++ b/content/media/fmp4/PlatformDecoderModule.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PlatformDecoderModule.h" +#ifdef XP_WIN +#include "WMFDecoderModule.h" +#endif +#include "mozilla/Preferences.h" + +namespace mozilla { + +extern PlatformDecoderModule* CreateBlankDecoderModule(); + +/* static */ +PlatformDecoderModule* +PlatformDecoderModule::Create() +{ + if (Preferences::GetBool("media.fragmented-mp4.use-blank-decoder")) { + return CreateBlankDecoderModule(); + } +#ifdef XP_WIN + nsAutoPtr m(new WMFDecoderModule()); + if (NS_SUCCEEDED(m->Init())) { + return m.forget(); + } +#endif + return nullptr; +} + +void +PlatformDecoderModule::OnDecodeThreadStart() +{ +} + +void +PlatformDecoderModule::OnDecodeThreadFinish() +{ +} + +} // namespace mozilla diff --git a/content/media/fmp4/PlatformDecoderModule.h b/content/media/fmp4/PlatformDecoderModule.h new file mode 100644 index 000000000000..182c3ce3ebb0 --- /dev/null +++ b/content/media/fmp4/PlatformDecoderModule.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(PlatformDecoderModule_h_) +#define PlatformDecoderModule_h_ + +#include "MediaDecoderReader.h" +#include "mozilla/layers/LayersTypes.h" + +namespace mozilla { + +namespace layers { +class ImageContainer; +} + +typedef int64_t Microseconds; + +enum DecoderStatus { + DECODE_STATUS_NOT_ACCEPTING, // Can't accept input at this time. + DECODE_STATUS_NEED_MORE_INPUT, // Nothing in pipeline to produce output with at this time. + DECODE_STATUS_OK, + DECODE_STATUS_ERROR +}; + +class MediaDataDecoder { +public: + virtual ~MediaDataDecoder() {}; + + virtual nsresult Shutdown() = 0; + + // Returns true if future decodes may produce output, or false + // on end of segment. + // Inserts data into the decoder's pipeline. + virtual DecoderStatus Input(const uint8_t* aData, + uint32_t aLength, + Microseconds aDTS, + Microseconds aPTS, + int64_t aOffsetInStream) = 0; + + // Blocks until decoded sample is produced by the deoder. + virtual DecoderStatus Output(nsAutoPtr& aOutData) = 0; + + virtual DecoderStatus Flush() = 0; +}; + +class PlatformDecoderModule { +public: + + // Creates the appropriate PlatformDecoderModule for this platform. + // Caller is responsible for deleting this instance. It's safe to have + // multiple PlatformDecoderModules alive at the same time. + // There is one PlatformDecoderModule's created per media decoder. + static PlatformDecoderModule* Create(); + + // Called when the decoders have shutdown. Main thread only. + virtual nsresult Shutdown() = 0; + + // TODO: Parameters for codec type. + // Decode thread. + virtual MediaDataDecoder* CreateVideoDecoder(layers::LayersBackend aLayersBackend, + layers::ImageContainer* aImageContainer) = 0; + + // Decode thread. + virtual MediaDataDecoder* CreateAudioDecoder(uint32_t aChannelCount, + uint32_t aSampleRate, + uint16_t aBitsPerSample, + const uint8_t* aUserData, + uint32_t aUserDataLength) = 0; + + // Platform decoders can override these. Base implementation does nothing. + virtual void OnDecodeThreadStart(); + virtual void OnDecodeThreadFinish(); + + virtual ~PlatformDecoderModule() {} +protected: + PlatformDecoderModule() {} +}; + +} // namespace mozilla + +#endif diff --git a/content/media/fmp4/demuxer/LICENSE b/content/media/fmp4/demuxer/LICENSE new file mode 100644 index 000000000000..3d0f7d3edfd8 --- /dev/null +++ b/content/media/fmp4/demuxer/LICENSE @@ -0,0 +1,27 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/content/media/fmp4/demuxer/Streams.h b/content/media/fmp4/demuxer/Streams.h new file mode 100644 index 000000000000..0274433cc09e --- /dev/null +++ b/content/media/fmp4/demuxer/Streams.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace mp4_demuxer { + +class Stream { +public: + + // Returns true on success, false on failure. + // Writes number of bytes read into out_bytes_read, or 0 on EOS. + // Returns true on EOS. + virtual bool ReadAt(int64_t offset, + uint8_t* buffer, + uint32_t count, + uint32_t* out_bytes_read) = 0; + + virtual int64_t Length() const = 0; +}; + +} // namespace mp4_demuxer \ No newline at end of file diff --git a/content/media/fmp4/demuxer/aac.cc b/content/media/fmp4/demuxer/aac.cc new file mode 100644 index 000000000000..f0b6203ca739 --- /dev/null +++ b/content/media/fmp4/demuxer/aac.cc @@ -0,0 +1,273 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/aac.h" + +#include + +#include "mp4_demuxer/bit_reader.h" + +namespace mp4_demuxer { + +// The following conversion table is extracted from ISO 14496 Part 3 - +// Table 1.16 - Sampling Frequency Index. +static const int kFrequencyMap[] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, + 22050, 16000, 12000, 11025, 8000, 7350 +}; + +static ChannelLayout ConvertChannelConfigToLayout(uint8_t channel_config) { + switch (channel_config) { + case 1: + return CHANNEL_LAYOUT_MONO; + case 2: + return CHANNEL_LAYOUT_STEREO; + case 3: + return CHANNEL_LAYOUT_SURROUND; + case 4: + return CHANNEL_LAYOUT_4_0; + case 5: + return CHANNEL_LAYOUT_5_0; + case 6: + return CHANNEL_LAYOUT_5_1; + case 8: + return CHANNEL_LAYOUT_7_1; + default: + break; + } + + return CHANNEL_LAYOUT_UNSUPPORTED; +} + +AAC::AAC() + : profile_(0), frequency_index_(0), channel_config_(0), frequency_(0), + extension_frequency_(0), channel_layout_(CHANNEL_LAYOUT_UNSUPPORTED) { +} + +AAC::~AAC() { +} + +bool AAC::Parse(const std::vector& data) { + if (data.empty()) + return false; + + BitReader reader(&data[0], data.size()); + uint8_t extension_type = 0; + bool ps_present = false; + uint8_t extension_frequency_index = 0xff; + + frequency_ = 0; + extension_frequency_ = 0; + + // The following code is written according to ISO 14496 Part 3 Table 1.13 - + // Syntax of AudioSpecificConfig. + + // Read base configuration + RCHECK(reader.ReadBits(5, &profile_)); + RCHECK(reader.ReadBits(4, &frequency_index_)); + if (frequency_index_ == 0xf) + RCHECK(reader.ReadBits(24, &frequency_)); + RCHECK(reader.ReadBits(4, &channel_config_)); + + // Read extension configuration. + if (profile_ == 5 || profile_ == 29) { + ps_present = (profile_ == 29); + extension_type = 5; + RCHECK(reader.ReadBits(4, &extension_frequency_index)); + if (extension_frequency_index == 0xf) + RCHECK(reader.ReadBits(24, &extension_frequency_)); + RCHECK(reader.ReadBits(5, &profile_)); + } + + RCHECK(SkipDecoderGASpecificConfig(&reader)); + RCHECK(SkipErrorSpecificConfig()); + + // Read extension configuration again + // Note: The check for 16 available bits comes from the AAC spec. + if (extension_type != 5 && reader.bits_available() >= 16) { + uint16_t sync_extension_type; + uint8_t sbr_present_flag; + uint8_t ps_present_flag; + + if (reader.ReadBits(11, &sync_extension_type) && + sync_extension_type == 0x2b7) { + if (reader.ReadBits(5, &extension_type) && extension_type == 5) { + RCHECK(reader.ReadBits(1, &sbr_present_flag)); + + if (sbr_present_flag) { + RCHECK(reader.ReadBits(4, &extension_frequency_index)); + + if (extension_frequency_index == 0xf) + RCHECK(reader.ReadBits(24, &extension_frequency_)); + + // Note: The check for 12 available bits comes from the AAC spec. + if (reader.bits_available() >= 12) { + RCHECK(reader.ReadBits(11, &sync_extension_type)); + if (sync_extension_type == 0x548) { + RCHECK(reader.ReadBits(1, &ps_present_flag)); + ps_present = ps_present_flag != 0; + } + } + } + } + } + } + + if (frequency_ == 0) { + RCHECK(frequency_index_ < arraysize(kFrequencyMap)); + frequency_ = kFrequencyMap[frequency_index_]; + } + + if (extension_frequency_ == 0 && extension_frequency_index != 0xff) { + RCHECK(extension_frequency_index < arraysize(kFrequencyMap)); + extension_frequency_ = kFrequencyMap[extension_frequency_index]; + } + + // When Parametric Stereo is on, mono will be played as stereo. + if (ps_present && channel_config_ == 1) + channel_layout_ = CHANNEL_LAYOUT_STEREO; + else + channel_layout_ = ConvertChannelConfigToLayout(channel_config_); + + audio_specific_config_.insert(audio_specific_config_.begin(), data.begin(), data.end()); + + return frequency_ != 0 && channel_layout_ != CHANNEL_LAYOUT_UNSUPPORTED && + profile_ >= 1 && profile_ <= 4 && frequency_index_ != 0xf && + channel_config_ <= 7; +} + +const std::vector& AAC::AudioSpecificConfig() const +{ + return audio_specific_config_; +} + +int AAC::GetOutputSamplesPerSecond(bool sbr_in_mimetype) const { + if (extension_frequency_ > 0) + return extension_frequency_; + + if (!sbr_in_mimetype) + return frequency_; + + // The following code is written according to ISO 14496 Part 3 Table 1.11 and + // Table 1.22. (Table 1.11 refers to the capping to 48000, Table 1.22 refers + // to SBR doubling the AAC sample rate.) + // TODO(acolwell) : Extend sample rate cap to 96kHz for Level 5 content. + DCHECK_GT(frequency_, 0); + return std::min(2 * frequency_, 48000); +} + +ChannelLayout AAC::GetChannelLayout(bool sbr_in_mimetype) const { + // Check for implicit signalling of HE-AAC and indicate stereo output + // if the mono channel configuration is signalled. + // See ISO-14496-3 Section 1.6.6.1.2 for details about this special casing. + if (sbr_in_mimetype && channel_config_ == 1) + return CHANNEL_LAYOUT_STEREO; + + return channel_layout_; +} + +bool AAC::ConvertEsdsToADTS(std::vector* buffer) const { + size_t size = buffer->size() + kADTSHeaderSize; + + DCHECK(profile_ >= 1 && profile_ <= 4 && frequency_index_ != 0xf && + channel_config_ <= 7); + + // ADTS header uses 13 bits for packet size. + if (size >= (1 << 13)) + return false; + + std::vector& adts = *buffer; + + adts.insert(buffer->begin(), kADTSHeaderSize, 0); + adts[0] = 0xff; + adts[1] = 0xf1; + adts[2] = ((profile_ - 1) << 6) + (frequency_index_ << 2) + + (channel_config_ >> 2); + adts[3] = ((channel_config_ & 0x3) << 6) + (size >> 11); + adts[4] = (size & 0x7ff) >> 3; + adts[5] = ((size & 7) << 5) + 0x1f; + adts[6] = 0xfc; + + return true; +} + +// Currently this function only support GASpecificConfig defined in +// ISO 14496 Part 3 Table 4.1 - Syntax of GASpecificConfig() +bool AAC::SkipDecoderGASpecificConfig(BitReader* bit_reader) const { + switch (profile_) { + case 1: + case 2: + case 3: + case 4: + case 6: + case 7: + case 17: + case 19: + case 20: + case 21: + case 22: + case 23: + return SkipGASpecificConfig(bit_reader); + default: + break; + } + + return false; +} + +bool AAC::SkipErrorSpecificConfig() const { + switch (profile_) { + case 17: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + return false; + default: + break; + } + + return true; +} + +// The following code is written according to ISO 14496 part 3 Table 4.1 - +// GASpecificConfig. +bool AAC::SkipGASpecificConfig(BitReader* bit_reader) const { + uint8_t extension_flag = 0; + uint8_t depends_on_core_coder; + uint16_t dummy; + + RCHECK(bit_reader->ReadBits(1, &dummy)); // frameLengthFlag + RCHECK(bit_reader->ReadBits(1, &depends_on_core_coder)); + if (depends_on_core_coder == 1) + RCHECK(bit_reader->ReadBits(14, &dummy)); // coreCoderDelay + + RCHECK(bit_reader->ReadBits(1, &extension_flag)); + RCHECK(channel_config_ != 0); + + if (profile_ == 6 || profile_ == 20) + RCHECK(bit_reader->ReadBits(3, &dummy)); // layerNr + + if (extension_flag) { + if (profile_ == 22) { + RCHECK(bit_reader->ReadBits(5, &dummy)); // numOfSubFrame + RCHECK(bit_reader->ReadBits(11, &dummy)); // layer_length + } + + if (profile_ == 17 || profile_ == 19 || profile_ == 20 || profile_ == 23) { + RCHECK(bit_reader->ReadBits(3, &dummy)); // resilience flags + } + + RCHECK(bit_reader->ReadBits(1, &dummy)); // extensionFlag3 + } + + return true; +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/aac.h b/content/media/fmp4/demuxer/aac.h new file mode 100644 index 000000000000..e3f4b92f74e8 --- /dev/null +++ b/content/media/fmp4/demuxer/aac.h @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_AAC_H_ +#define MEDIA_MP4_AAC_H_ + +#include + +#include "mp4_demuxer/basictypes.h" +#include "mp4_demuxer/channel_layout.h" + +namespace mp4_demuxer { + +class BitReader; + +// This class parses the AAC information from decoder specific information +// embedded in the esds box in an ISO BMFF file. +// Please refer to ISO 14496 Part 3 Table 1.13 - Syntax of AudioSpecificConfig +// for more details. +class AAC { + public: + AAC(); + ~AAC(); + + // Parse the AAC config from the raw binary data embedded in esds box. + // The function will parse the data and get the ElementaryStreamDescriptor, + // then it will parse the ElementaryStreamDescriptor to get audio stream + // configurations. + bool Parse(const std::vector& data); + + // Gets the output sample rate for the AAC stream. + // |sbr_in_mimetype| should be set to true if the SBR mode is + // signalled in the mimetype. (ie mp4a.40.5 in the codecs parameter). + // Returns the samples_per_second value that should used in an + // AudioDecoderConfig. + int GetOutputSamplesPerSecond(bool sbr_in_mimetype) const; + + // Gets the channel layout for the AAC stream. + // |sbr_in_mimetype| should be set to true if the SBR mode is + // signalled in the mimetype. (ie mp4a.40.5 in the codecs parameter). + // Returns the channel_layout value that should used in an + // AudioDecoderConfig. + ChannelLayout GetChannelLayout(bool sbr_in_mimetype) const; + + // This function converts a raw AAC frame into an AAC frame with an ADTS + // header. On success, the function returns true and stores the converted data + // in the buffer. The function returns false on failure and leaves the buffer + // unchanged. + bool ConvertEsdsToADTS(std::vector* buffer) const; + + // Size in bytes of the ADTS header added by ConvertEsdsToADTS(). + static const size_t kADTSHeaderSize = 7; + + const std::vector& AudioSpecificConfig() const; + + private: + bool SkipDecoderGASpecificConfig(BitReader* bit_reader) const; + bool SkipErrorSpecificConfig() const; + bool SkipGASpecificConfig(BitReader* bit_reader) const; + + // The following variables store the AAC specific configuration information + // that are used to generate the ADTS header. + uint8_t profile_; + uint8_t frequency_index_; + uint8_t channel_config_; + + // The following variables store audio configuration information that + // can be used by Chromium. They are based on the AAC specific + // configuration but can be overridden by extensions in elementary + // stream descriptor. + int frequency_; + int extension_frequency_; + ChannelLayout channel_layout_; + + std::vector audio_specific_config_; +}; + +} // namespace mp4_demuxer + +#endif // MEDIA_MP4_AAC_H_ diff --git a/content/media/fmp4/demuxer/audio_decoder_config.cc b/content/media/fmp4/demuxer/audio_decoder_config.cc new file mode 100644 index 000000000000..f73b23e8bedc --- /dev/null +++ b/content/media/fmp4/demuxer/audio_decoder_config.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/audio_decoder_config.h" + +#include + +namespace mp4_demuxer { + +static int SampleFormatToBitsPerChannel(SampleFormat sample_format) { + switch (sample_format) { + case kUnknownSampleFormat: + return 0; + case kSampleFormatU8: + return 8; + case kSampleFormatS16: + case kSampleFormatPlanarS16: + return 16; + case kSampleFormatS32: + case kSampleFormatF32: + case kSampleFormatPlanarF32: + return 32; + case kSampleFormatMax: + break; + } + + //NOTREACHED() << "Invalid sample format provided: " << sample_format; + return 0; +} + +AudioDecoderConfig::AudioDecoderConfig() + : codec_(kUnknownAudioCodec), + sample_format_(kUnknownSampleFormat), + bits_per_channel_(0), + channel_layout_(CHANNEL_LAYOUT_UNSUPPORTED), + samples_per_second_(0), + bytes_per_frame_(0), + is_encrypted_(false) { +} + +AudioDecoderConfig::AudioDecoderConfig(AudioCodec codec, + SampleFormat sample_format, + ChannelLayout channel_layout, + int samples_per_second, + const uint8_t* extra_data, + size_t extra_data_size, + bool is_encrypted) { + Initialize(codec, sample_format, channel_layout, samples_per_second, + extra_data, extra_data_size, is_encrypted); +} + +void AudioDecoderConfig::Initialize(AudioCodec codec, + SampleFormat sample_format, + ChannelLayout channel_layout, + int samples_per_second, + const uint8_t* extra_data, + size_t extra_data_size, + bool is_encrypted) { + CHECK((extra_data_size != 0) == (extra_data != NULL)); + + codec_ = codec; + channel_layout_ = channel_layout; + samples_per_second_ = samples_per_second; + sample_format_ = sample_format; + bits_per_channel_ = SampleFormatToBitsPerChannel(sample_format); + extra_data_.assign(extra_data, extra_data + extra_data_size); + is_encrypted_ = is_encrypted; + + int channels = ChannelLayoutToChannelCount(channel_layout_); + bytes_per_frame_ = channels * bits_per_channel_ / 8; +} + +AudioDecoderConfig::~AudioDecoderConfig() {} + +bool AudioDecoderConfig::IsValidConfig() const { + return codec_ != kUnknownAudioCodec && + channel_layout_ != CHANNEL_LAYOUT_UNSUPPORTED && + bits_per_channel_ > 0 && + bits_per_channel_ <= kMaxBitsPerSample && + samples_per_second_ > 0 && + samples_per_second_ <= kMaxSampleRate && + sample_format_ != kUnknownSampleFormat; +} + +bool AudioDecoderConfig::Matches(const AudioDecoderConfig& config) const { + return ((codec() == config.codec()) && + (bits_per_channel() == config.bits_per_channel()) && + (channel_layout() == config.channel_layout()) && + (samples_per_second() == config.samples_per_second()) && + (extra_data_size() == config.extra_data_size()) && + (!extra_data() || !memcmp(extra_data(), config.extra_data(), + extra_data_size())) && + (is_encrypted() == config.is_encrypted()) && + (sample_format() == config.sample_format())); +} + +std::string AudioDecoderConfig::AsHumanReadableString() const { + std::ostringstream s; + s << "codec: " << codec() + << " bits/channel: " << bits_per_channel() + << " samples/s: " << samples_per_second() + << " has extra data? " << (extra_data() ? "true" : "false") + << " encrypted? " << (is_encrypted() ? "true" : "false"); + return s.str(); +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/audio_decoder_config.h b/content/media/fmp4/demuxer/audio_decoder_config.h new file mode 100644 index 000000000000..8318ecf899a3 --- /dev/null +++ b/content/media/fmp4/demuxer/audio_decoder_config.h @@ -0,0 +1,127 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_AUDIO_DECODER_CONFIG_H_ +#define MEDIA_BASE_AUDIO_DECODER_CONFIG_H_ + +#include + +#include "mp4_demuxer/basictypes.h" +#include "mp4_demuxer/channel_layout.h" + +namespace mp4_demuxer { + +enum AudioCodec { + // These values are histogrammed over time; do not change their ordinal + // values. When deleting a codec replace it with a dummy value; when adding a + // codec, do so at the bottom before kAudioCodecMax. + kUnknownAudioCodec = 0, + kCodecAAC, + kCodecMP3, + kCodecPCM, + kCodecVorbis, + kCodecFLAC, + kCodecAMR_NB, + kCodecAMR_WB, + kCodecPCM_MULAW, + kCodecGSM_MS, + kCodecPCM_S16BE, + kCodecPCM_S24BE, + kCodecOpus, + // DO NOT ADD RANDOM AUDIO CODECS! + // + // The only acceptable time to add a new codec is if there is production code + // that uses said codec in the same CL. + + // Must always be last! + kAudioCodecMax +}; + +enum SampleFormat { + // These values are histogrammed over time; do not change their ordinal + // values. When deleting a sample format replace it with a dummy value; when + // adding a sample format, do so at the bottom before kSampleFormatMax. + kUnknownSampleFormat = 0, + kSampleFormatU8, // Unsigned 8-bit w/ bias of 128. + kSampleFormatS16, // Signed 16-bit. + kSampleFormatS32, // Signed 32-bit. + kSampleFormatF32, // Float 32-bit. + kSampleFormatPlanarS16, // Signed 16-bit planar. + kSampleFormatPlanarF32, // Float 32-bit planar. + + // Must always be last! + kSampleFormatMax +}; + +// TODO(dalecurtis): FFmpeg API uses |bytes_per_channel| instead of +// |bits_per_channel|, we should switch over since bits are generally confusing +// to work with. +class AudioDecoderConfig { + public: + // Constructs an uninitialized object. Clients should call Initialize() with + // appropriate values before using. + AudioDecoderConfig(); + + // Constructs an initialized object. It is acceptable to pass in NULL for + // |extra_data|, otherwise the memory is copied. + AudioDecoderConfig(AudioCodec codec, SampleFormat sample_format, + ChannelLayout channel_layout, int samples_per_second, + const uint8_t* extra_data, size_t extra_data_size, + bool is_encrypted); + + ~AudioDecoderConfig(); + + // Resets the internal state of this object. + void Initialize(AudioCodec codec, SampleFormat sample_format, + ChannelLayout channel_layout, int samples_per_second, + const uint8_t* extra_data, size_t extra_data_size, + bool is_encrypted); + + // Returns true if this object has appropriate configuration values, false + // otherwise. + bool IsValidConfig() const; + + // Returns true if all fields in |config| match this config. + // Note: The contents of |extra_data_| are compared not the raw pointers. + bool Matches(const AudioDecoderConfig& config) const; + + AudioCodec codec() const { return codec_; } + int bits_per_channel() const { return bits_per_channel_; } + ChannelLayout channel_layout() const { return channel_layout_; } + int samples_per_second() const { return samples_per_second_; } + SampleFormat sample_format() const { return sample_format_; } + int bytes_per_frame() const { return bytes_per_frame_; } + + // Optional byte data required to initialize audio decoders such as Vorbis + // codebooks. + const uint8_t* extra_data() const { + return extra_data_.empty() ? NULL : &extra_data_[0]; + } + size_t extra_data_size() const { return extra_data_.size(); } + + // Whether the audio stream is potentially encrypted. + // Note that in a potentially encrypted audio stream, individual buffers + // can be encrypted or not encrypted. + bool is_encrypted() const { return is_encrypted_; } + + std::string AsHumanReadableString() const; + + private: + AudioCodec codec_; + SampleFormat sample_format_; + int bits_per_channel_; + ChannelLayout channel_layout_; + int samples_per_second_; + int bytes_per_frame_; + std::vector extra_data_; + bool is_encrypted_; + + // Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler + // generated copy constructor and assignment operator. Since the extra data is + // typically small, the performance impact is minimal. +}; + +} // namespace mp4_demuxer + +#endif // MEDIA_BASE_AUDIO_DECODER_CONFIG_H_ diff --git a/content/media/fmp4/demuxer/avc.cc b/content/media/fmp4/demuxer/avc.cc new file mode 100644 index 000000000000..d03790860860 --- /dev/null +++ b/content/media/fmp4/demuxer/avc.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/avc.h" + +#include +#include + +#include "mp4_demuxer/box_definitions.h" +#include "mp4_demuxer/box_reader.h" + +namespace mp4_demuxer { + +static const uint8_t kAnnexBStartCode[] = {0, 0, 0, 1}; +static const int kAnnexBStartCodeSize = 4; + +static bool ConvertAVCToAnnexBInPlaceForLengthSize4(std::vector* buf) { + const int kLengthSize = 4; + size_t pos = 0; + while (pos + kLengthSize < buf->size()) { + int nal_size = (*buf)[pos]; + nal_size = (nal_size << 8) + (*buf)[pos+1]; + nal_size = (nal_size << 8) + (*buf)[pos+2]; + nal_size = (nal_size << 8) + (*buf)[pos+3]; + std::copy(kAnnexBStartCode, kAnnexBStartCode + kAnnexBStartCodeSize, + buf->begin() + pos); + pos += kLengthSize + nal_size; + } + return pos == buf->size(); +} + +// static +bool AVC::ConvertFrameToAnnexB(int length_size, std::vector* buffer) { + RCHECK(length_size == 1 || length_size == 2 || length_size == 4); + + if (length_size == 4) + return ConvertAVCToAnnexBInPlaceForLengthSize4(buffer); + + std::vector temp; + temp.swap(*buffer); + buffer->reserve(temp.size() + 32); + + size_t pos = 0; + while (pos + length_size < temp.size()) { + int nal_size = temp[pos]; + if (length_size == 2) nal_size = (nal_size << 8) + temp[pos+1]; + pos += length_size; + + RCHECK(pos + nal_size <= temp.size()); + buffer->insert(buffer->end(), kAnnexBStartCode, + kAnnexBStartCode + kAnnexBStartCodeSize); + buffer->insert(buffer->end(), temp.begin() + pos, + temp.begin() + pos + nal_size); + pos += nal_size; + } + return pos == temp.size(); +} + +// static +bool AVC::ConvertConfigToAnnexB( + const AVCDecoderConfigurationRecord& avc_config, + std::vector* buffer) { + DCHECK(buffer->empty()); + buffer->clear(); + int total_size = 0; + for (size_t i = 0; i < avc_config.sps_list.size(); i++) + total_size += avc_config.sps_list[i].size() + kAnnexBStartCodeSize; + for (size_t i = 0; i < avc_config.pps_list.size(); i++) + total_size += avc_config.pps_list[i].size() + kAnnexBStartCodeSize; + buffer->reserve(total_size); + + for (size_t i = 0; i < avc_config.sps_list.size(); i++) { + buffer->insert(buffer->end(), kAnnexBStartCode, + kAnnexBStartCode + kAnnexBStartCodeSize); + buffer->insert(buffer->end(), avc_config.sps_list[i].begin(), + avc_config.sps_list[i].end()); + } + + for (size_t i = 0; i < avc_config.pps_list.size(); i++) { + buffer->insert(buffer->end(), kAnnexBStartCode, + kAnnexBStartCode + kAnnexBStartCodeSize); + buffer->insert(buffer->end(), avc_config.pps_list[i].begin(), + avc_config.pps_list[i].end()); + } + return true; +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/avc.h b/content/media/fmp4/demuxer/avc.h new file mode 100644 index 000000000000..150a4e10975d --- /dev/null +++ b/content/media/fmp4/demuxer/avc.h @@ -0,0 +1,27 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_AVC_H_ +#define MEDIA_MP4_AVC_H_ + +#include + +#include "mp4_demuxer/basictypes.h" + +namespace mp4_demuxer { + +struct AVCDecoderConfigurationRecord; + +class AVC { + public: + static bool ConvertFrameToAnnexB(int length_size, std::vector* buffer); + + static bool ConvertConfigToAnnexB( + const AVCDecoderConfigurationRecord& avc_config, + std::vector* buffer); +}; + +} // namespace mp4_demuxer + +#endif // MEDIA_MP4_AVC_H_ diff --git a/content/media/fmp4/demuxer/basictypes.h b/content/media/fmp4/demuxer/basictypes.h new file mode 100644 index 000000000000..00b463507bc4 --- /dev/null +++ b/content/media/fmp4/demuxer/basictypes.h @@ -0,0 +1,175 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_BASIC_TYPES_H_ +#define MEDIA_MP4_BASIC_TYPES_H_ + +#include +#include +#include "prlog.h" + +#ifdef PR_LOGGING +PRLogModuleInfo* GetDemuxerLog(); +#endif + +namespace mp4_demuxer { + +// Define to enable logging. +//#define LOG_DEMUXER + +#define kint32max std::numeric_limits::max() +#define kuint64max std::numeric_limits::max() +#define kint64max std::numeric_limits::max() + + + +#define OVERRIDE override +#define WARN_UNUSED_RESULT + +#define DCHECK(condition) \ +{ \ + if (!(condition)) {\ + DMX_LOG("DCHECK Failed (%s) %s:%d\n", #condition, __FILE__, __LINE__); \ + } \ +} + +#define CHECK(condition) { \ + if (!(condition)) {\ + DMX_LOG("CHECK Failed %s %s:%d\n", #condition, __FILE__, __LINE__); \ + } \ +} + +#define DCHECK_LE(variable, value) DCHECK(variable <= value) +#define DCHECK_LT(variable, value) DCHECK(variable < value) +#define DCHECK_EQ(variable, value) DCHECK(variable == value) +#define DCHECK_GT(variable, value) DCHECK(variable > value) +#define DCHECK_GE(variable, value) DCHECK(variable >= value) + +#define RCHECK(x) \ + do { \ + if (!(x)) { \ + DMX_LOG("Failure while parsing MP4: %s %s:%d\n", #x, __FILE__, __LINE__); \ + return false; \ + } \ + } while (0) + +#define arraysize(f) (sizeof(f) / sizeof(*f)) + +#ifdef LOG_DEMUXER +#ifdef PR_LOGGING +#define DMX_LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__)) +#else +#define DMX_LOG(...) 0 +#endif +#else +// define DMX_LOG as 0, so that if(condition){DMX_LOG(...)} branches don't elicit +// a warning-as-error. +#define DMX_LOG(...) 0 +#endif + +// A macro to disallow the evil copy constructor and operator= functions +// This should be used in the private: declarations for a class +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +typedef int64_t Microseconds; +typedef int64_t Milliseconds; + +#define MicrosecondsPerSecond (int64_t(1000000)) +#define InfiniteMicroseconds (int64_t(-1)) +#define InfiniteMilliseconds (int64_t(-1)) + +inline Microseconds MicrosecondsFromRational(int64_t numer, int64_t denom) { + DCHECK_LT((numer > 0 ? numer : -numer), + kint64max / MicrosecondsPerSecond); + return MicrosecondsPerSecond * numer / denom; +} + +inline Milliseconds ToMilliseconds(Microseconds us) { + return (us == InfiniteMicroseconds) ? InfiniteMilliseconds : us / 1000; +} + +class IntSize { +public: + IntSize() : w_(0), h_(0) {} + IntSize(const IntSize& i) : w_(i.w_), h_(i.h_) {} + IntSize(int32_t w, int32_t h) : w_(w), h_(h) {} + ~IntSize() {}; + int32_t width() const { return w_; } + int32_t height() const { return h_; } + int32_t GetArea() const { return w_ * h_; } + bool IsEmpty() const { return (w_ == 0) || (h_ == 0); } +private: + int32_t w_; + int32_t h_; +}; + +inline bool operator==(const IntSize& lhs, const IntSize& rhs) { + return lhs.width() == rhs.width() && + lhs.height() == rhs.height(); +} + +class IntRect { +public: + IntRect() : x_(0), y_(0), w_(0), h_(0) {} + IntRect(const IntRect& i) : x_(i.x_), y_(i.y_), w_(i.w_), h_(i.h_) {} + IntRect(int32_t x, int32_t y, int32_t w, int32_t h) : x_(x), y_(y), w_(w), h_(h) {} + ~IntRect() {}; + IntSize size() const { return IntSize(w_, h_); } + int32_t x() const { return x_; } + int32_t y() const { return y_; } + int32_t width() const { return w_; } + int32_t height() const { return h_; } + int32_t GetArea() const { return w_ * h_; } + bool IsEmpty() const { return (w_ == 0) || (h_ == 0); } + int32_t right() const { return x() + width(); } + int32_t bottom() const { return y() + height(); } +private: + int32_t x_; + int32_t y_; + int32_t w_; + int32_t h_; +}; + +inline bool operator==(const IntRect& lhs, const IntRect& rhs) { + return lhs.x() == rhs.x() && + lhs.y() == rhs.y() && + lhs.width() == rhs.width() && + lhs.height() == rhs.height(); +} + +enum { + // Maximum possible dimension (width or height) for any video. + kMaxDimension = (1 << 15) - 1, // 32767 + + // Maximum possible canvas size (width multiplied by height) for any video. + kMaxCanvas = (1 << (14 * 2)), // 16384 x 16384 + + // Total number of video frames which are populating in the pipeline. + kMaxVideoFrames = 4, + + // The following limits are used by AudioParameters::IsValid(). + // + // A few notes on sample rates of common formats: + // - AAC files are limited to 96 kHz. + // - MP3 files are limited to 48 kHz. + // - Vorbis used to be limited to 96 KHz, but no longer has that + // restriction. + // - Most PC audio hardware is limited to 192 KHz. + kMaxSampleRate = 192000, + kMinSampleRate = 3000, + kMaxChannels = 32, + kMaxBitsPerSample = 32, + kMaxSamplesPerPacket = kMaxSampleRate, + kMaxPacketSizeInBytes = + (kMaxBitsPerSample / 8) * kMaxChannels * kMaxSamplesPerPacket, + + // This limit is used by ParamTraits. + kMaxFramesPerSecond = 1000, +}; + +} // namespace mp4_demuxer + +#endif // MEDIA_MP4_BASIC_TYPES_H_ diff --git a/content/media/fmp4/demuxer/bit_reader.cc b/content/media/fmp4/demuxer/bit_reader.cc new file mode 100644 index 000000000000..3677021a589e --- /dev/null +++ b/content/media/fmp4/demuxer/bit_reader.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/bit_reader.h" + +namespace mp4_demuxer { + +BitReader::BitReader(const uint8_t* data, off_t size) + : data_(data), bytes_left_(size), num_remaining_bits_in_curr_byte_(0) { + DCHECK(data_ != nullptr && bytes_left_ > 0); + + UpdateCurrByte(); +} + +BitReader::~BitReader() {} + +int BitReader::bits_available() const { + return 8 * bytes_left_ + num_remaining_bits_in_curr_byte_; +} + +bool BitReader::ReadBitsInternal(int num_bits, uint64_t* out) { + DCHECK_LE(num_bits, 64); + + *out = 0; + + while (num_remaining_bits_in_curr_byte_ != 0 && num_bits != 0) { + int bits_to_take = std::min(num_remaining_bits_in_curr_byte_, num_bits); + + *out <<= bits_to_take; + *out += curr_byte_ >> (num_remaining_bits_in_curr_byte_ - bits_to_take); + num_bits -= bits_to_take; + num_remaining_bits_in_curr_byte_ -= bits_to_take; + curr_byte_ &= (1 << num_remaining_bits_in_curr_byte_) - 1; + + if (num_remaining_bits_in_curr_byte_ == 0) + UpdateCurrByte(); + } + + return num_bits == 0; +} + +void BitReader::UpdateCurrByte() { + DCHECK_EQ(num_remaining_bits_in_curr_byte_, 0); + + if (bytes_left_ == 0) + return; + + // Load a new byte and advance pointers. + curr_byte_ = *data_; + ++data_; + --bytes_left_; + num_remaining_bits_in_curr_byte_ = 8; +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/bit_reader.h b/content/media/fmp4/demuxer/bit_reader.h new file mode 100644 index 000000000000..0657c25397df --- /dev/null +++ b/content/media/fmp4/demuxer/bit_reader.h @@ -0,0 +1,69 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_BIT_READER_H_ +#define MEDIA_BASE_BIT_READER_H_ + +#include + +#include "mp4_demuxer/basictypes.h" + +namespace mp4_demuxer { + +// A class to read bit streams. +class BitReader { + public: + // Initialize the reader to start reading at |data|, |size| being size + // of |data| in bytes. + BitReader(const uint8_t* data, off_t size); + ~BitReader(); + + // Read |num_bits| next bits from stream and return in |*out|, first bit + // from the stream starting at |num_bits| position in |*out|. + // |num_bits| cannot be larger than the bits the type can hold. + // Return false if the given number of bits cannot be read (not enough + // bits in the stream), true otherwise. When return false, the stream will + // enter a state where further ReadBits/SkipBits operations will always + // return false unless |num_bits| is 0. The type |T| has to be a primitive + // integer type. + template bool ReadBits(int num_bits, T *out) { + DCHECK_LE(num_bits, static_cast(sizeof(T) * 8)); + uint64_t temp; + bool ret = ReadBitsInternal(num_bits, &temp); + *out = static_cast(temp); + return ret; + } + + // Returns the number of bits available for reading. + int bits_available() const; + + private: + // Help function used by ReadBits to avoid inlining the bit reading logic. + bool ReadBitsInternal(int num_bits, uint64_t* out); + + // Advance to the next byte, loading it into curr_byte_. + // If the num_remaining_bits_in_curr_byte_ is 0 after this function returns, + // the stream has reached the end. + void UpdateCurrByte(); + + // Pointer to the next unread (not in curr_byte_) byte in the stream. + const uint8_t* data_; + + // Bytes left in the stream (without the curr_byte_). + off_t bytes_left_; + + // Contents of the current byte; first unread bit starting at position + // 8 - num_remaining_bits_in_curr_byte_ from MSB. + uint8_t curr_byte_; + + // Number of bits remaining in curr_byte_ + int num_remaining_bits_in_curr_byte_; + + private: + DISALLOW_COPY_AND_ASSIGN(BitReader); +}; + +} // namespace mp4_demuxer + +#endif // MEDIA_BASE_BIT_READER_H_ diff --git a/content/media/fmp4/demuxer/box_definitions.cc b/content/media/fmp4/demuxer/box_definitions.cc new file mode 100644 index 000000000000..7dfb53fced65 --- /dev/null +++ b/content/media/fmp4/demuxer/box_definitions.cc @@ -0,0 +1,754 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/box_definitions.h" +#include "mp4_demuxer/es_descriptor.h" + +#include + +namespace mp4_demuxer { + +FileType::FileType() {} +FileType::~FileType() {} +FourCC FileType::BoxType() const { return FOURCC_FTYP; } + +bool FileType::Parse(BoxReader* reader) { + RCHECK(reader->ReadFourCC(&major_brand) && reader->Read4(&minor_version)); + size_t num_brands = (reader->size() - reader->pos()) / sizeof(FourCC); + return reader->SkipBytes(sizeof(FourCC) * num_brands); // compatible_brands +} + +ProtectionSystemSpecificHeader::ProtectionSystemSpecificHeader() {} +ProtectionSystemSpecificHeader::~ProtectionSystemSpecificHeader() {} +FourCC ProtectionSystemSpecificHeader::BoxType() const { return FOURCC_PSSH; } + +bool ProtectionSystemSpecificHeader::Parse(BoxReader* reader) { + // Validate the box's contents and hang on to the system ID. + uint32_t size; + RCHECK(reader->ReadFullBoxHeader() && + reader->ReadVec(&system_id, 16) && + reader->Read4(&size) && + reader->HasBytes(size)); + + // Copy the entire box, including the header, for passing to EME as initData. + DCHECK(raw_box.empty()); + reader->ReadVec(&raw_box, size); + //raw_box.assign(reader->data(), reader->data() + size); + return true; +} + +SampleAuxiliaryInformationOffset::SampleAuxiliaryInformationOffset() {} +SampleAuxiliaryInformationOffset::~SampleAuxiliaryInformationOffset() {} +FourCC SampleAuxiliaryInformationOffset::BoxType() const { return FOURCC_SAIO; } + +bool SampleAuxiliaryInformationOffset::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + if (reader->flags() & 1) + RCHECK(reader->SkipBytes(8)); + + uint32_t count; + RCHECK(reader->Read4(&count) && + reader->HasBytes(count * (reader->version() == 1 ? 8 : 4))); + offsets.resize(count); + + for (uint32_t i = 0; i < count; i++) { + if (reader->version() == 1) { + RCHECK(reader->Read8(&offsets[i])); + } else { + RCHECK(reader->Read4Into8(&offsets[i])); + } + } + return true; +} + +SampleAuxiliaryInformationSize::SampleAuxiliaryInformationSize() + : default_sample_info_size(0), sample_count(0) { +} +SampleAuxiliaryInformationSize::~SampleAuxiliaryInformationSize() {} +FourCC SampleAuxiliaryInformationSize::BoxType() const { return FOURCC_SAIZ; } + +bool SampleAuxiliaryInformationSize::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + if (reader->flags() & 1) + RCHECK(reader->SkipBytes(8)); + + RCHECK(reader->Read1(&default_sample_info_size) && + reader->Read4(&sample_count)); + if (default_sample_info_size == 0) + return reader->ReadVec(&sample_info_sizes, sample_count); + return true; +} + +OriginalFormat::OriginalFormat() : format(FOURCC_NULL) {} +OriginalFormat::~OriginalFormat() {} +FourCC OriginalFormat::BoxType() const { return FOURCC_FRMA; } + +bool OriginalFormat::Parse(BoxReader* reader) { + return reader->ReadFourCC(&format); +} + +SchemeType::SchemeType() : type(FOURCC_NULL), version(0) {} +SchemeType::~SchemeType() {} +FourCC SchemeType::BoxType() const { return FOURCC_SCHM; } + +bool SchemeType::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader() && + reader->ReadFourCC(&type) && + reader->Read4(&version)); + return true; +} + +TrackEncryption::TrackEncryption() + : is_encrypted(false), default_iv_size(0) { +} +TrackEncryption::~TrackEncryption() {} +FourCC TrackEncryption::BoxType() const { return FOURCC_TENC; } + +bool TrackEncryption::Parse(BoxReader* reader) { + uint8_t flag; + RCHECK(reader->ReadFullBoxHeader() && + reader->SkipBytes(2) && + reader->Read1(&flag) && + reader->Read1(&default_iv_size) && + reader->ReadVec(&default_kid, 16)); + is_encrypted = (flag != 0); + if (is_encrypted) { + RCHECK(default_iv_size == 8 || default_iv_size == 16); + } else { + RCHECK(default_iv_size == 0); + } + return true; +} + +SchemeInfo::SchemeInfo() {} +SchemeInfo::~SchemeInfo() {} +FourCC SchemeInfo::BoxType() const { return FOURCC_SCHI; } + +bool SchemeInfo::Parse(BoxReader* reader) { + return reader->ScanChildren() && reader->ReadChild(&track_encryption); +} + +ProtectionSchemeInfo::ProtectionSchemeInfo() {} +ProtectionSchemeInfo::~ProtectionSchemeInfo() {} +FourCC ProtectionSchemeInfo::BoxType() const { return FOURCC_SINF; } + +bool ProtectionSchemeInfo::Parse(BoxReader* reader) { + RCHECK(reader->ScanChildren() && + reader->ReadChild(&format) && + reader->ReadChild(&type)); + if (type.type == FOURCC_CENC) + RCHECK(reader->ReadChild(&info)); + // Other protection schemes are silently ignored. Since the protection scheme + // type can't be determined until this box is opened, we return 'true' for + // non-CENC protection scheme types. It is the parent box's responsibility to + // ensure that this scheme type is a supported one. + return true; +} + +MovieHeader::MovieHeader() + : creation_time(0), + modification_time(0), + timescale(0), + duration(0), + rate(-1), + volume(-1), + next_track_id(0) {} +MovieHeader::~MovieHeader() {} +FourCC MovieHeader::BoxType() const { return FOURCC_MVHD; } + +bool MovieHeader::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + + if (reader->version() == 1) { + RCHECK(reader->Read8(&creation_time) && + reader->Read8(&modification_time) && + reader->Read4(×cale) && + reader->Read8(&duration)); + } else { + RCHECK(reader->Read4Into8(&creation_time) && + reader->Read4Into8(&modification_time) && + reader->Read4(×cale) && + reader->Read4Into8(&duration)); + } + + RCHECK(reader->Read4s(&rate) && + reader->Read2s(&volume) && + reader->SkipBytes(10) && // reserved + reader->SkipBytes(36) && // matrix + reader->SkipBytes(24) && // predefined zero + reader->Read4(&next_track_id)); + return true; +} + +TrackHeader::TrackHeader() + : creation_time(0), + modification_time(0), + track_id(0), + duration(0), + layer(-1), + alternate_group(-1), + volume(-1), + width(0), + height(0) {} +TrackHeader::~TrackHeader() {} +FourCC TrackHeader::BoxType() const { return FOURCC_TKHD; } + +bool TrackHeader::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + if (reader->version() == 1) { + RCHECK(reader->Read8(&creation_time) && + reader->Read8(&modification_time) && + reader->Read4(&track_id) && + reader->SkipBytes(4) && // reserved + reader->Read8(&duration)); + } else { + RCHECK(reader->Read4Into8(&creation_time) && + reader->Read4Into8(&modification_time) && + reader->Read4(&track_id) && + reader->SkipBytes(4) && // reserved + reader->Read4Into8(&duration)); + } + + RCHECK(reader->SkipBytes(8) && // reserved + reader->Read2s(&layer) && + reader->Read2s(&alternate_group) && + reader->Read2s(&volume) && + reader->SkipBytes(2) && // reserved + reader->SkipBytes(36) && // matrix + reader->Read4(&width) && + reader->Read4(&height)); + width >>= 16; + height >>= 16; + return true; +} + +SampleDescription::SampleDescription() : type(kInvalid) {} +SampleDescription::~SampleDescription() {} +FourCC SampleDescription::BoxType() const { return FOURCC_STSD; } + +bool SampleDescription::Parse(BoxReader* reader) { + uint32_t count; + RCHECK(reader->SkipBytes(4) && + reader->Read4(&count)); + video_entries.clear(); + audio_entries.clear(); + + // Note: this value is preset before scanning begins. See comments in the + // Parse(Media*) function. + if (type == kVideo) { + RCHECK(reader->ReadAllChildren(&video_entries)); + } else if (type == kAudio) { + RCHECK(reader->ReadAllChildren(&audio_entries)); + } + return true; +} + +SampleTable::SampleTable() {} +SampleTable::~SampleTable() {} +FourCC SampleTable::BoxType() const { return FOURCC_STBL; } + +bool SampleTable::Parse(BoxReader* reader) { + return reader->ScanChildren() && + reader->ReadChild(&description); +} + +EditList::EditList() {} +EditList::~EditList() {} +FourCC EditList::BoxType() const { return FOURCC_ELST; } + +bool EditList::Parse(BoxReader* reader) { + uint32_t count; + RCHECK(reader->ReadFullBoxHeader() && reader->Read4(&count)); + + if (reader->version() == 1) { + RCHECK(reader->HasBytes(count * 20)); + } else { + RCHECK(reader->HasBytes(count * 12)); + } + edits.resize(count); + + for (std::vector::iterator edit = edits.begin(); + edit != edits.end(); ++edit) { + if (reader->version() == 1) { + RCHECK(reader->Read8(&edit->segment_duration) && + reader->Read8s(&edit->media_time)); + } else { + RCHECK(reader->Read4Into8(&edit->segment_duration) && + reader->Read4sInto8s(&edit->media_time)); + } + RCHECK(reader->Read2s(&edit->media_rate_integer) && + reader->Read2s(&edit->media_rate_fraction)); + } + return true; +} + +Edit::Edit() {} +Edit::~Edit() {} +FourCC Edit::BoxType() const { return FOURCC_EDTS; } + +bool Edit::Parse(BoxReader* reader) { + return reader->ScanChildren() && reader->ReadChild(&list); +} + +HandlerReference::HandlerReference() : type(kInvalid) {} +HandlerReference::~HandlerReference() {} +FourCC HandlerReference::BoxType() const { return FOURCC_HDLR; } + +bool HandlerReference::Parse(BoxReader* reader) { + FourCC hdlr_type; + RCHECK(reader->SkipBytes(8) && reader->ReadFourCC(&hdlr_type)); + // Note: remaining fields in box ignored + if (hdlr_type == FOURCC_VIDE) { + type = kVideo; + } else if (hdlr_type == FOURCC_SOUN) { + type = kAudio; + } else { + type = kInvalid; + } + return true; +} + +AVCDecoderConfigurationRecord::AVCDecoderConfigurationRecord() + : version(0), + profile_indication(0), + profile_compatibility(0), + avc_level(0), + length_size(0) {} + +AVCDecoderConfigurationRecord::~AVCDecoderConfigurationRecord() {} +FourCC AVCDecoderConfigurationRecord::BoxType() const { return FOURCC_AVCC; } + +bool AVCDecoderConfigurationRecord::Parse(BoxReader* reader) { + RCHECK(reader->Read1(&version) && version == 1 && + reader->Read1(&profile_indication) && + reader->Read1(&profile_compatibility) && + reader->Read1(&avc_level)); + + uint8_t length_size_minus_one; + RCHECK(reader->Read1(&length_size_minus_one) && + (length_size_minus_one & 0xfc) == 0xfc); + length_size = (length_size_minus_one & 0x3) + 1; + + uint8_t num_sps; + RCHECK(reader->Read1(&num_sps) && (num_sps & 0xe0) == 0xe0); + num_sps &= 0x1f; + + sps_list.resize(num_sps); + for (int i = 0; i < num_sps; i++) { + uint16_t sps_length; + RCHECK(reader->Read2(&sps_length) && + reader->ReadVec(&sps_list[i], sps_length)); + } + + uint8_t num_pps; + RCHECK(reader->Read1(&num_pps)); + + pps_list.resize(num_pps); + for (int i = 0; i < num_pps; i++) { + uint16_t pps_length; + RCHECK(reader->Read2(&pps_length) && + reader->ReadVec(&pps_list[i], pps_length)); + } + + return true; +} + +PixelAspectRatioBox::PixelAspectRatioBox() : h_spacing(1), v_spacing(1) {} +PixelAspectRatioBox::~PixelAspectRatioBox() {} +FourCC PixelAspectRatioBox::BoxType() const { return FOURCC_PASP; } + +bool PixelAspectRatioBox::Parse(BoxReader* reader) { + RCHECK(reader->Read4(&h_spacing) && + reader->Read4(&v_spacing)); + return true; +} + +VideoSampleEntry::VideoSampleEntry() + : format(FOURCC_NULL), + data_reference_index(0), + width(0), + height(0) {} + +VideoSampleEntry::~VideoSampleEntry() {} +FourCC VideoSampleEntry::BoxType() const { + DMX_LOG("VideoSampleEntry should be parsed according to the " + "handler type recovered in its Media ancestor.\n"); + return FOURCC_NULL; +} + +bool VideoSampleEntry::Parse(BoxReader* reader) { + format = reader->type(); + RCHECK(reader->SkipBytes(6) && + reader->Read2(&data_reference_index) && + reader->SkipBytes(16) && + reader->Read2(&width) && + reader->Read2(&height) && + reader->SkipBytes(50)); + + RCHECK(reader->ScanChildren() && + reader->MaybeReadChild(&pixel_aspect)); + + if (format == FOURCC_ENCV) { + // Continue scanning until a recognized protection scheme is found, or until + // we run out of protection schemes. + while (sinf.type.type != FOURCC_CENC) { + if (!reader->ReadChild(&sinf)) + return false; + } + } + + if (format == FOURCC_AVC1 || + (format == FOURCC_ENCV && sinf.format.format == FOURCC_AVC1)) { + RCHECK(reader->ReadChild(&avcc)); + } + return true; +} + +ElementaryStreamDescriptor::ElementaryStreamDescriptor() + : object_type(kForbidden) {} + +ElementaryStreamDescriptor::~ElementaryStreamDescriptor() {} + +FourCC ElementaryStreamDescriptor::BoxType() const { + return FOURCC_ESDS; +} + +bool ElementaryStreamDescriptor::Parse(BoxReader* reader) { + std::vector data; + ESDescriptor es_desc; + + RCHECK(reader->ReadFullBoxHeader()); + RCHECK(reader->ReadVec(&data, reader->size() - reader->pos())); + RCHECK(es_desc.Parse(data)); + + object_type = es_desc.object_type(); + + RCHECK(aac.Parse(es_desc.decoder_specific_info())); + + return true; +} + +AudioSampleEntry::AudioSampleEntry() + : format(FOURCC_NULL), + data_reference_index(0), + channelcount(0), + samplesize(0), + samplerate(0) {} + +AudioSampleEntry::~AudioSampleEntry() {} + +FourCC AudioSampleEntry::BoxType() const { + DMX_LOG("AudioSampleEntry should be parsed according to the " + "handler type recovered in its Media ancestor.\n"); + return FOURCC_NULL; +} + +bool AudioSampleEntry::Parse(BoxReader* reader) { + format = reader->type(); + RCHECK(reader->SkipBytes(6) && + reader->Read2(&data_reference_index) && + reader->SkipBytes(8) && + reader->Read2(&channelcount) && + reader->Read2(&samplesize) && + reader->SkipBytes(4) && + reader->Read4(&samplerate)); + // Convert from 16.16 fixed point to integer + samplerate >>= 16; + + RCHECK(reader->ScanChildren()); + if (format == FOURCC_ENCA) { + // Continue scanning until a recognized protection scheme is found, or until + // we run out of protection schemes. + while (sinf.type.type != FOURCC_CENC) { + if (!reader->ReadChild(&sinf)) + return false; + } + } + + RCHECK(reader->ReadChild(&esds)); + return true; +} + +MediaHeader::MediaHeader() + : creation_time(0), + modification_time(0), + timescale(0), + duration(0) {} +MediaHeader::~MediaHeader() {} +FourCC MediaHeader::BoxType() const { return FOURCC_MDHD; } + +bool MediaHeader::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + + if (reader->version() == 1) { + RCHECK(reader->Read8(&creation_time) && + reader->Read8(&modification_time) && + reader->Read4(×cale) && + reader->Read8(&duration)); + } else { + RCHECK(reader->Read4Into8(&creation_time) && + reader->Read4Into8(&modification_time) && + reader->Read4(×cale) && + reader->Read4Into8(&duration)); + } + // Skip language information + return reader->SkipBytes(4); +} + +MediaInformation::MediaInformation() {} +MediaInformation::~MediaInformation() {} +FourCC MediaInformation::BoxType() const { return FOURCC_MINF; } + +bool MediaInformation::Parse(BoxReader* reader) { + return reader->ScanChildren() && + reader->ReadChild(&sample_table); +} + +Media::Media() {} +Media::~Media() {} +FourCC Media::BoxType() const { return FOURCC_MDIA; } + +bool Media::Parse(BoxReader* reader) { + RCHECK(reader->ScanChildren() && + reader->ReadChild(&header) && + reader->ReadChild(&handler)); + + // Maddeningly, the HandlerReference box specifies how to parse the + // SampleDescription box, making the latter the only box (of those that we + // support) which cannot be parsed correctly on its own (or even with + // information from its strict ancestor tree). We thus copy the handler type + // to the sample description box *before* parsing it to provide this + // information while parsing. + information.sample_table.description.type = handler.type; + RCHECK(reader->ReadChild(&information)); + return true; +} + +Track::Track() {} +Track::~Track() {} +FourCC Track::BoxType() const { return FOURCC_TRAK; } + +bool Track::Parse(BoxReader* reader) { + RCHECK(reader->ScanChildren() && + reader->ReadChild(&header) && + reader->ReadChild(&media) && + reader->MaybeReadChild(&edit)); + return true; +} + +MovieExtendsHeader::MovieExtendsHeader() : fragment_duration(0) {} +MovieExtendsHeader::~MovieExtendsHeader() {} +FourCC MovieExtendsHeader::BoxType() const { return FOURCC_MEHD; } + +bool MovieExtendsHeader::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + if (reader->version() == 1) { + RCHECK(reader->Read8(&fragment_duration)); + } else { + RCHECK(reader->Read4Into8(&fragment_duration)); + } + return true; +} + +TrackExtends::TrackExtends() + : track_id(0), + default_sample_description_index(0), + default_sample_duration(0), + default_sample_size(0), + default_sample_flags(0) {} +TrackExtends::~TrackExtends() {} +FourCC TrackExtends::BoxType() const { return FOURCC_TREX; } + +bool TrackExtends::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader() && + reader->Read4(&track_id) && + reader->Read4(&default_sample_description_index) && + reader->Read4(&default_sample_duration) && + reader->Read4(&default_sample_size) && + reader->Read4(&default_sample_flags)); + return true; +} + +MovieExtends::MovieExtends() {} +MovieExtends::~MovieExtends() {} +FourCC MovieExtends::BoxType() const { return FOURCC_MVEX; } + +bool MovieExtends::Parse(BoxReader* reader) { + header.fragment_duration = 0; + return reader->ScanChildren() && + reader->MaybeReadChild(&header) && + reader->ReadChildren(&tracks); +} + +Movie::Movie() : fragmented(false) {} +Movie::~Movie() {} +FourCC Movie::BoxType() const { return FOURCC_MOOV; } + +bool Movie::Parse(BoxReader* reader) { + return reader->ScanChildren() && + reader->ReadChild(&header) && + reader->ReadChildren(&tracks) && + // Media Source specific: 'mvex' required + reader->ReadChild(&extends) && + reader->MaybeReadChildren(&pssh); +} + +TrackFragmentDecodeTime::TrackFragmentDecodeTime() : decode_time(0) {} +TrackFragmentDecodeTime::~TrackFragmentDecodeTime() {} +FourCC TrackFragmentDecodeTime::BoxType() const { return FOURCC_TFDT; } + +bool TrackFragmentDecodeTime::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader()); + if (reader->version() == 1) + return reader->Read8(&decode_time); + else + return reader->Read4Into8(&decode_time); +} + +MovieFragmentHeader::MovieFragmentHeader() : sequence_number(0) {} +MovieFragmentHeader::~MovieFragmentHeader() {} +FourCC MovieFragmentHeader::BoxType() const { return FOURCC_MFHD; } + +bool MovieFragmentHeader::Parse(BoxReader* reader) { + return reader->SkipBytes(4) && reader->Read4(&sequence_number); +} + +TrackFragmentHeader::TrackFragmentHeader() + : track_id(0), + sample_description_index(0), + default_sample_duration(0), + default_sample_size(0), + default_sample_flags(0), + has_default_sample_flags(false) {} + +TrackFragmentHeader::~TrackFragmentHeader() {} +FourCC TrackFragmentHeader::BoxType() const { return FOURCC_TFHD; } + +bool TrackFragmentHeader::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader() && reader->Read4(&track_id)); + + // Media Source specific: reject tracks that set 'base-data-offset-present'. + // Although the Media Source requires that 'default-base-is-moof' (14496-12 + // Amendment 2) be set, we omit this check as many otherwise-valid files in + // the wild don't set it. + // + // RCHECK((flags & 0x020000) && !(flags & 0x1)); + RCHECK(!(reader->flags() & 0x1)); + + if (reader->flags() & 0x2) { + RCHECK(reader->Read4(&sample_description_index)); + } else { + sample_description_index = 0; + } + + if (reader->flags() & 0x8) { + RCHECK(reader->Read4(&default_sample_duration)); + } else { + default_sample_duration = 0; + } + + if (reader->flags() & 0x10) { + RCHECK(reader->Read4(&default_sample_size)); + } else { + default_sample_size = 0; + } + + if (reader->flags() & 0x20) { + RCHECK(reader->Read4(&default_sample_flags)); + has_default_sample_flags = true; + } else { + has_default_sample_flags = false; + } + + return true; +} + +TrackFragmentRun::TrackFragmentRun() + : sample_count(0), data_offset(0) {} +TrackFragmentRun::~TrackFragmentRun() {} +FourCC TrackFragmentRun::BoxType() const { return FOURCC_TRUN; } + +bool TrackFragmentRun::Parse(BoxReader* reader) { + RCHECK(reader->ReadFullBoxHeader() && + reader->Read4(&sample_count)); + const uint32_t flags = reader->flags(); + + bool data_offset_present = (flags & 0x1) != 0; + bool first_sample_flags_present = (flags & 0x4) != 0; + bool sample_duration_present = (flags & 0x100) != 0; + bool sample_size_present = (flags & 0x200) != 0; + bool sample_flags_present = (flags & 0x400) != 0; + bool sample_composition_time_offsets_present = (flags & 0x800) != 0; + + if (data_offset_present) { + RCHECK(reader->Read4(&data_offset)); + } else { + data_offset = 0; + } + + uint32_t first_sample_flags; + if (first_sample_flags_present) + RCHECK(reader->Read4(&first_sample_flags)); + + int fields = sample_duration_present + sample_size_present + + sample_flags_present + sample_composition_time_offsets_present; + RCHECK(reader->HasBytes(fields * sample_count)); + + if (sample_duration_present) + sample_durations.resize(sample_count); + if (sample_size_present) + sample_sizes.resize(sample_count); + if (sample_flags_present) + sample_flags.resize(sample_count); + if (sample_composition_time_offsets_present) + sample_composition_time_offsets.resize(sample_count); + + for (uint32_t i = 0; i < sample_count; ++i) { + if (sample_duration_present) + RCHECK(reader->Read4(&sample_durations[i])); + if (sample_size_present) + RCHECK(reader->Read4(&sample_sizes[i])); + if (sample_flags_present) + RCHECK(reader->Read4(&sample_flags[i])); + if (sample_composition_time_offsets_present) + RCHECK(reader->Read4s(&sample_composition_time_offsets[i])); + } + + if (first_sample_flags_present) { + if (sample_flags.size() == 0) { + sample_flags.push_back(first_sample_flags); + } else { + sample_flags[0] = first_sample_flags; + } + } + return true; +} + +TrackFragment::TrackFragment() {} +TrackFragment::~TrackFragment() {} +FourCC TrackFragment::BoxType() const { return FOURCC_TRAF; } + +bool TrackFragment::Parse(BoxReader* reader) { + return reader->ScanChildren() && + reader->ReadChild(&header) && + // Media Source specific: 'tfdt' required + reader->ReadChild(&decode_time) && + reader->MaybeReadChildren(&runs) && + reader->MaybeReadChild(&auxiliary_offset) && + reader->MaybeReadChild(&auxiliary_size); +} + +MovieFragment::MovieFragment() {} +MovieFragment::~MovieFragment() {} +FourCC MovieFragment::BoxType() const { return FOURCC_MOOF; } + +bool MovieFragment::Parse(BoxReader* reader) { + RCHECK(reader->ScanChildren() && + reader->ReadChild(&header) && + reader->ReadChildren(&tracks) && + reader->MaybeReadChildren(&pssh)); + return true; +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/box_definitions.h b/content/media/fmp4/demuxer/box_definitions.h new file mode 100644 index 000000000000..d82c7269fbda --- /dev/null +++ b/content/media/fmp4/demuxer/box_definitions.h @@ -0,0 +1,349 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_BOX_DEFINITIONS_H_ +#define MEDIA_MP4_BOX_DEFINITIONS_H_ + +#include +#include + +#include "mp4_demuxer/box_reader.h" + +#include "mp4_demuxer/basictypes.h" +#include "mp4_demuxer/aac.h" +#include "mp4_demuxer/avc.h" +#include "mp4_demuxer/box_reader.h" +#include "mp4_demuxer/fourccs.h" + +namespace mp4_demuxer { + +enum TrackType { + kInvalid = 0, + kVideo, + kAudio, + kHint +}; + +#define DECLARE_BOX_METHODS(T) \ + T(); \ + virtual ~T(); \ + virtual bool Parse(BoxReader* reader) OVERRIDE; \ + virtual FourCC BoxType() const OVERRIDE; \ + +struct FileType : Box { + DECLARE_BOX_METHODS(FileType); + + FourCC major_brand; + uint32_t minor_version; +}; + +struct ProtectionSystemSpecificHeader : Box { + DECLARE_BOX_METHODS(ProtectionSystemSpecificHeader); + + std::vector system_id; + std::vector raw_box; +}; + +struct SampleAuxiliaryInformationOffset : Box { + DECLARE_BOX_METHODS(SampleAuxiliaryInformationOffset); + + std::vector offsets; +}; + +struct SampleAuxiliaryInformationSize : Box { + DECLARE_BOX_METHODS(SampleAuxiliaryInformationSize); + + uint8_t default_sample_info_size; + uint32_t sample_count; + std::vector sample_info_sizes; +}; + +struct OriginalFormat : Box { + DECLARE_BOX_METHODS(OriginalFormat); + + FourCC format; +}; + +struct SchemeType : Box { + DECLARE_BOX_METHODS(SchemeType); + + FourCC type; + uint32_t version; +}; + +struct TrackEncryption : Box { + DECLARE_BOX_METHODS(TrackEncryption); + + // Note: this definition is specific to the CENC protection type. + bool is_encrypted; + uint8_t default_iv_size; + std::vector default_kid; +}; + +struct SchemeInfo : Box { + DECLARE_BOX_METHODS(SchemeInfo); + + TrackEncryption track_encryption; +}; + +struct ProtectionSchemeInfo : Box { + DECLARE_BOX_METHODS(ProtectionSchemeInfo); + + OriginalFormat format; + SchemeType type; + SchemeInfo info; +}; + +struct MovieHeader : Box { + DECLARE_BOX_METHODS(MovieHeader); + + uint64_t creation_time; + uint64_t modification_time; + uint32_t timescale; + uint64_t duration; + int32_t rate; + int16_t volume; + uint32_t next_track_id; +}; + +struct TrackHeader : Box { + DECLARE_BOX_METHODS(TrackHeader); + + uint64_t creation_time; + uint64_t modification_time; + uint32_t track_id; + uint64_t duration; + int16_t layer; + int16_t alternate_group; + int16_t volume; + uint32_t width; + uint32_t height; +}; + +struct EditListEntry { + uint64_t segment_duration; + int64_t media_time; + int16_t media_rate_integer; + int16_t media_rate_fraction; +}; + +struct EditList : Box { + DECLARE_BOX_METHODS(EditList); + + std::vector edits; +}; + +struct Edit : Box { + DECLARE_BOX_METHODS(Edit); + + EditList list; +}; + +struct HandlerReference : Box { + DECLARE_BOX_METHODS(HandlerReference); + + TrackType type; +}; + +struct AVCDecoderConfigurationRecord : Box { + DECLARE_BOX_METHODS(AVCDecoderConfigurationRecord); + + uint8_t version; + uint8_t profile_indication; + uint8_t profile_compatibility; + uint8_t avc_level; + uint8_t length_size; + + typedef std::vector SPS; + typedef std::vector PPS; + + std::vector sps_list; + std::vector pps_list; +}; + +struct PixelAspectRatioBox : Box { + DECLARE_BOX_METHODS(PixelAspectRatioBox); + + uint32_t h_spacing; + uint32_t v_spacing; +}; + +struct VideoSampleEntry : Box { + DECLARE_BOX_METHODS(VideoSampleEntry); + + FourCC format; + uint16_t data_reference_index; + uint16_t width; + uint16_t height; + + PixelAspectRatioBox pixel_aspect; + ProtectionSchemeInfo sinf; + + // Currently expected to be present regardless of format. + AVCDecoderConfigurationRecord avcc; +}; + +struct ElementaryStreamDescriptor : Box { + DECLARE_BOX_METHODS(ElementaryStreamDescriptor); + + uint8_t object_type; + AAC aac; +}; + +struct AudioSampleEntry : Box { + DECLARE_BOX_METHODS(AudioSampleEntry); + + FourCC format; + uint16_t data_reference_index; + uint16_t channelcount; + uint16_t samplesize; + uint32_t samplerate; + + ProtectionSchemeInfo sinf; + ElementaryStreamDescriptor esds; +}; + +struct SampleDescription : Box { + DECLARE_BOX_METHODS(SampleDescription); + + TrackType type; + std::vector video_entries; + std::vector audio_entries; +}; + +struct SampleTable : Box { + DECLARE_BOX_METHODS(SampleTable); + + // Media Source specific: we ignore many of the sub-boxes in this box, + // including some that are required to be present in the BMFF spec. This + // includes the 'stts', 'stsc', and 'stco' boxes, which must contain no + // samples in order to be compliant files. + SampleDescription description; +}; + +struct MediaHeader : Box { + DECLARE_BOX_METHODS(MediaHeader); + + uint64_t creation_time; + uint64_t modification_time; + uint32_t timescale; + uint64_t duration; +}; + +struct MediaInformation : Box { + DECLARE_BOX_METHODS(MediaInformation); + + SampleTable sample_table; +}; + +struct Media : Box { + DECLARE_BOX_METHODS(Media); + + MediaHeader header; + HandlerReference handler; + MediaInformation information; +}; + +struct Track : Box { + DECLARE_BOX_METHODS(Track); + + TrackHeader header; + Media media; + Edit edit; +}; + +struct MovieExtendsHeader : Box { + DECLARE_BOX_METHODS(MovieExtendsHeader); + + uint64_t fragment_duration; +}; + +struct TrackExtends : Box { + DECLARE_BOX_METHODS(TrackExtends); + + uint32_t track_id; + uint32_t default_sample_description_index; + uint32_t default_sample_duration; + uint32_t default_sample_size; + uint32_t default_sample_flags; +}; + +struct MovieExtends : Box { + DECLARE_BOX_METHODS(MovieExtends); + + MovieExtendsHeader header; + std::vector tracks; +}; + +struct Movie : Box { + DECLARE_BOX_METHODS(Movie); + + bool fragmented; + MovieHeader header; + MovieExtends extends; + std::vector tracks; + std::vector pssh; +}; + +struct TrackFragmentDecodeTime : Box { + DECLARE_BOX_METHODS(TrackFragmentDecodeTime); + + uint64_t decode_time; +}; + +struct MovieFragmentHeader : Box { + DECLARE_BOX_METHODS(MovieFragmentHeader); + + uint32_t sequence_number; +}; + +struct TrackFragmentHeader : Box { + DECLARE_BOX_METHODS(TrackFragmentHeader); + + uint32_t track_id; + + uint32_t sample_description_index; + uint32_t default_sample_duration; + uint32_t default_sample_size; + uint32_t default_sample_flags; + + // As 'flags' might be all zero, we cannot use zeroness alone to identify + // when default_sample_flags wasn't specified, unlike the other values. + bool has_default_sample_flags; +}; + +struct TrackFragmentRun : Box { + DECLARE_BOX_METHODS(TrackFragmentRun); + + uint32_t sample_count; + uint32_t data_offset; + std::vector sample_flags; + std::vector sample_sizes; + std::vector sample_durations; + std::vector sample_composition_time_offsets; +}; + +struct TrackFragment : Box { + DECLARE_BOX_METHODS(TrackFragment); + + TrackFragmentHeader header; + std::vector runs; + TrackFragmentDecodeTime decode_time; + SampleAuxiliaryInformationOffset auxiliary_offset; + SampleAuxiliaryInformationSize auxiliary_size; +}; + +struct MovieFragment : Box { + DECLARE_BOX_METHODS(MovieFragment); + + MovieFragmentHeader header; + std::vector tracks; + std::vector pssh; +}; + +#undef DECLARE_BOX + +} // namespace mp4_demuxer + +#endif // MEDIA_MP4_BOX_DEFINITIONS_H_ diff --git a/content/media/fmp4/demuxer/box_reader.cc b/content/media/fmp4/demuxer/box_reader.cc new file mode 100644 index 000000000000..8b72e01e0eb9 --- /dev/null +++ b/content/media/fmp4/demuxer/box_reader.cc @@ -0,0 +1,263 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/box_reader.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "mp4_demuxer/box_definitions.h" + +#include "mp4_demuxer/Streams.h" + +using namespace std; + +namespace mp4_demuxer { + +Box::~Box() {} + +bool StreamReader::Read1(uint8_t* v) { + RCHECK(HasBytes(1)); + assert(start_ + pos_ <= stream_->Length()); + uint32_t bytesRead = 0; + if (!stream_->ReadAt(start_ + pos_, v, 1, &bytesRead) || bytesRead != 1) { + return false; + } + pos_ += bytesRead; + return true; +} + +// Internal implementation of multi-byte reads +template bool StreamReader::Read(T* v) { + RCHECK(HasBytes(sizeof(T))); + + T tmp = 0; + for (size_t i = 0; i < sizeof(T); i++) { + tmp <<= 8; + uint8_t byte; + Read1(&byte); + tmp += byte; + } + *v = tmp; + return true; +} + +bool StreamReader::Read2(uint16_t* v) { return Read(v); } +bool StreamReader::Read2s(int16_t* v) { return Read(v); } +bool StreamReader::Read4(uint32_t* v) { return Read(v); } +bool StreamReader::Read4s(int32_t* v) { return Read(v); } +bool StreamReader::Read8(uint64_t* v) { return Read(v); } +bool StreamReader::Read8s(int64_t* v) { return Read(v); } + +bool StreamReader::ReadFourCC(FourCC* v) { + return Read4(reinterpret_cast(v)); +} + +bool StreamReader::ReadVec(std::vector* vec, int count) { + RCHECK(HasBytes(count)); + vec->resize(count); + assert(start_ + pos_ <= stream_->Length()); + uint32_t bytesRead = 0; + if (!stream_->ReadAt(start_ + pos_, vec->data(), count, &bytesRead)) { + return false; + } + pos_ += bytesRead; + return true; +} + +bool StreamReader::SkipBytes(int bytes) { + RCHECK(HasBytes(bytes)); + pos_ += bytes; + return true; +} + +bool StreamReader::Read4Into8(uint64_t* v) { + uint32_t tmp; + RCHECK(Read4(&tmp)); + *v = tmp; + return true; +} + +bool StreamReader::Read4sInto8s(int64_t* v) { + // Beware of the need for sign extension. + int32_t tmp; + RCHECK(Read4s(&tmp)); + *v = tmp; + return true; +} + +int64_t StreamReader::size() const { + return size_; +} + +int64_t StreamReader::pos() const { + return pos_; +} + +BoxReader::BoxReader(Stream* stream, int64_t offset, int64_t size) + : StreamReader(stream, offset, size), + type_(FOURCC_NULL), + version_(0), + flags_(0), + scanned_(false) { +} + +BoxReader::~BoxReader() { + if (scanned_ && !children_.empty()) { + for (ChildMap::iterator itr = children_.begin(); + itr != children_.end(); ++itr) { + auto reader = itr->second; + DMX_LOG("Skipping unknown box: '%s' reader type'%s'\n", + FourCCToString(itr->first).c_str(), + FourCCToString(reader.type()).c_str()); + } + } +} + +// static +BoxReader* BoxReader::ReadTopLevelBox(Stream* stream, + int64_t offset, + bool* err) { + nsAutoPtr reader(new BoxReader(stream, offset, stream->Length())); + if (!reader->ReadHeader(err)) + return NULL; + + if (!IsValidTopLevelBox(reader->type())) { + *err = true; + return NULL; + } + + if (reader->size() <= stream->Length()) + return reader.forget(); + + return NULL; +} + +// static +bool BoxReader::StartTopLevelBox(Stream* stream, + int64_t offset, + FourCC* type, + int* box_size) { + assert(stream->Length() > offset); + BoxReader reader(stream, offset, stream->Length() - offset); + bool err = false; + if (!reader.ReadHeader(&err)) return false; + if (!IsValidTopLevelBox(reader.type()) || err) { + return false; + } + *type = reader.type(); + *box_size = reader.size(); + return true; +} + +// static +bool BoxReader::IsValidTopLevelBox(const FourCC& type) { + switch (type) { + case FOURCC_FTYP: + case FOURCC_PDIN: + case FOURCC_BLOC: + case FOURCC_MOOV: + case FOURCC_MOOF: + case FOURCC_MFRA: + case FOURCC_MDAT: + case FOURCC_FREE: + case FOURCC_SKIP: + case FOURCC_META: + case FOURCC_MECO: + case FOURCC_STYP: + case FOURCC_SIDX: + case FOURCC_SSIX: + case FOURCC_PRFT: + return true; + default: + // Hex is used to show nonprintable characters and aid in debugging + DMX_LOG("Unrecognized top-level box type 0x%x\n", type); + return false; + } +} + +bool BoxReader::ScanChildren() { + DCHECK(!scanned_); + scanned_ = true; + + bool err = false; + while (pos() < size()) { + BoxReader child(stream_, start_ + pos_, size_ - pos_); + if (!child.ReadHeader(&err)) break; + assert(child.size() < size()); + children_.insert(std::pair(child.type(), child)); + pos_ += child.size(); + } + + DCHECK(!err); + return !err && pos() == size(); +} + +bool BoxReader::ReadChild(Box* child) { + DCHECK(scanned_); + FourCC child_type = child->BoxType(); + + ChildMap::iterator itr = children_.find(child_type); + if (itr == children_.end()) { + DMX_LOG("No child of type %s\n", FourCCToString(child_type).c_str()); + } + RCHECK(itr != children_.end()); + DMX_LOG("Found a %s box\n", FourCCToString(child_type).c_str()); + RCHECK(child->Parse(&itr->second)); + children_.erase(itr); + return true; +} + +bool BoxReader::MaybeReadChild(Box* child) { + if (!children_.count(child->BoxType())) return true; + return ReadChild(child); +} + +bool BoxReader::ReadFullBoxHeader() { + uint32_t vflags; + RCHECK(Read4(&vflags)); + version_ = vflags >> 24; + flags_ = vflags & 0xffffff; + return true; +} + +bool BoxReader::ReadHeader(bool* err) { + uint64_t size = 0; + *err = false; + + if (!HasBytes(8)) return false; + CHECK(Read4Into8(&size) && ReadFourCC(&type_)); + + DMX_LOG("BoxReader::ReadHeader() read %s size=%d\n", FourCCToString(type_).c_str(), size); + + if (size == 0) { + // Media Source specific: we do not support boxes that run to EOS. + *err = true; + return false; + } else if (size == 1) { + if (!HasBytes(8)) return false; + CHECK(Read8(&size)); + } + + // Implementation-specific: support for boxes larger than 2^31 has been + // removed. + if (size < static_cast(pos_) || + size > static_cast(kint32max)) { + *err = true; + return false; + } + + // Note that the pos_ head has advanced to the byte immediately after the + // header, which is where we want it. + size_ = size; + + return true; +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/box_reader.h b/content/media/fmp4/demuxer/box_reader.h new file mode 100644 index 000000000000..9c891328092d --- /dev/null +++ b/content/media/fmp4/demuxer/box_reader.h @@ -0,0 +1,216 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_BOX_READER_H_ +#define MEDIA_MP4_BOX_READER_H_ + +#include +#include + +#include "mp4_demuxer/basictypes.h" +#include "mp4_demuxer/fourccs.h" + +#include "nsAutoPtr.h" + +namespace mp4_demuxer { + +class BoxReader; +class Stream; + +struct Box { + virtual ~Box(); + virtual bool Parse(BoxReader* reader) = 0; + virtual FourCC BoxType() const = 0; +}; + +class StreamReader { + public: + StreamReader(Stream* stream, int64_t offset, int64_t size) + : stream_(stream), start_(offset), pos_(0), size_(size) {} + + bool HasBytes(int count) { return (pos() + count <= size()); } + + // Read a value from the stream, perfoming endian correction, and advance the + // stream pointer. + bool Read1(uint8_t* v) WARN_UNUSED_RESULT; + bool Read2(uint16_t* v) WARN_UNUSED_RESULT; + bool Read2s(int16_t* v) WARN_UNUSED_RESULT; + bool Read4(uint32_t* v) WARN_UNUSED_RESULT; + bool Read4s(int32_t* v) WARN_UNUSED_RESULT; + bool Read8(uint64_t* v) WARN_UNUSED_RESULT; + bool Read8s(int64_t* v) WARN_UNUSED_RESULT; + + bool ReadFourCC(FourCC* v) WARN_UNUSED_RESULT; + + bool ReadVec(std::vector* t, int count) WARN_UNUSED_RESULT; + + // These variants read a 4-byte integer of the corresponding signedness and + // store it in the 8-byte return type. + bool Read4Into8(uint64_t* v) WARN_UNUSED_RESULT; + bool Read4sInto8s(int64_t* v) WARN_UNUSED_RESULT; + + // Advance the stream by this many bytes. + bool SkipBytes(int nbytes) WARN_UNUSED_RESULT; + + //const uint8_t* data() const { return buf_; } + int64_t size() const; + int64_t pos() const; + +protected: + + // The start offset of the box in the stream. + const int64_t start_; + + // The size of the box in the stream. + int64_t size_; + + // The offset from start_ at which the read cursor is. + // 0 initially. + int64_t pos_; + + // The stream that we read from. + Stream* stream_; + + template bool Read(T* t) WARN_UNUSED_RESULT; +}; + +class BoxReader : public StreamReader { + public: + ~BoxReader(); + + // Create a BoxReader from a buffer. Note that this function may return NULL + // if an intact, complete box was not available in the buffer. If |*err| is + // set, there was a stream-level error when creating the box; otherwise, NULL + // values are only expected when insufficient data is available. + // + // |buf| is retained but not owned, and must outlive the BoxReader instance. + static BoxReader* ReadTopLevelBox(Stream* stream, + int64_t offset, + bool* err); + + // Read the box header from the current buffer. This function returns true if + // the header is sane; that is, it does not check to ensure the entire box is + // in the buffer before returning true. + static bool StartTopLevelBox(Stream* stream, + int64_t offset, + FourCC* type, + int* box_size); + + // Returns true if |type| is recognized to be a top-level box, false + // otherwise. This returns true for some boxes which we do not parse. + // Helpful in debugging misaligned appends. + static bool IsValidTopLevelBox(const FourCC& type); + + // Scan through all boxes within the current box, starting at the current + // buffer position. Must be called before any of the *Child functions work. + bool ScanChildren() WARN_UNUSED_RESULT; + + // Read exactly one child box from the set of children. The type of the child + // will be determined by the BoxType() method of |child|. + bool ReadChild(Box* child) WARN_UNUSED_RESULT; + + // Read one child if available. Returns false on error, true on successful + // read or on child absent. + bool MaybeReadChild(Box* child) WARN_UNUSED_RESULT; + + // Read at least one child. False means error or no such child present. + template bool ReadChildren( + std::vector* children) WARN_UNUSED_RESULT; + + // Read any number of children. False means error. + template bool MaybeReadChildren( + std::vector* children) WARN_UNUSED_RESULT; + + // Read all children, regardless of FourCC. This is used from exactly one box, + // corresponding to a rather significant inconsistency in the BMFF spec. + // Note that this method is mutually exclusive with ScanChildren(). + template bool ReadAllChildren( + std::vector* children) WARN_UNUSED_RESULT; + + // Populate the values of 'version()' and 'flags()' from a full box header. + // Many boxes, but not all, use these values. This call should happen after + // the box has been initialized, and does not re-read the main box header. + bool ReadFullBoxHeader() WARN_UNUSED_RESULT; + + FourCC type() const { return type_; } + uint8_t version() const { return version_; } + uint32_t flags() const { return flags_; } + +private: + + BoxReader(Stream* stream, int64_t offset, int64_t size); + + // Must be called immediately after init. If the return is false, this + // indicates that the box header and its contents were not available in the + // stream or were nonsensical, and that the box must not be used further. In + // this case, if |*err| is false, the problem was simply a lack of data, and + // should only be an error condition if some higher-level component knows that + // no more data is coming (i.e. EOS or end of containing box). If |*err| is + // true, the error is unrecoverable and the stream should be aborted. + bool ReadHeader(bool* err); + + FourCC type_; + uint8_t version_; + uint32_t flags_; + + typedef std::multimap ChildMap; + + // The set of child box FourCCs and their corresponding buffer readers. Only + // valid if scanned_ is true. + ChildMap children_; + bool scanned_; +}; + +// Template definitions +template bool BoxReader::ReadChildren(std::vector* children) { + RCHECK(MaybeReadChildren(children) && !children->empty()); + return true; +} + +template +bool BoxReader::MaybeReadChildren(std::vector* children) { + DCHECK(scanned_); + DCHECK(children->empty()); + + children->resize(1); + FourCC child_type = (*children)[0].BoxType(); + + ChildMap::iterator start_itr = children_.lower_bound(child_type); + ChildMap::iterator end_itr = children_.upper_bound(child_type); + children->resize(std::distance(start_itr, end_itr)); + typename std::vector::iterator child_itr = children->begin(); + for (ChildMap::iterator itr = start_itr; itr != end_itr; ++itr) { + RCHECK(child_itr->Parse(&itr->second)); + ++child_itr; + } + children_.erase(start_itr, end_itr); + + DMX_LOG("Found %d %s boxes\n", + children->size(), + FourCCToString(child_type).c_str()); + + return true; +} + +template +bool BoxReader::ReadAllChildren(std::vector* children) { + DCHECK(!scanned_); + scanned_ = true; + + bool err = false; + while (pos() < size()) { + BoxReader child_reader(stream_, start_ + pos_, size_ - pos_); + if (!child_reader.ReadHeader(&err)) break; + T child; + RCHECK(child.Parse(&child_reader)); + children->push_back(child); + pos_ += child_reader.size(); + } + + return !err; +} + +} // namespace mp4_demuxer + +#endif // MEDIA_MP4_BOX_READER_H_ diff --git a/content/media/fmp4/demuxer/cenc.cc b/content/media/fmp4/demuxer/cenc.cc new file mode 100644 index 000000000000..533cbe1f8333 --- /dev/null +++ b/content/media/fmp4/demuxer/cenc.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/cenc.h" + +#include + +#include "mp4_demuxer/box_reader.h" + +namespace mp4_demuxer { + +FrameCENCInfo::FrameCENCInfo() {} +FrameCENCInfo::~FrameCENCInfo() {} + +bool FrameCENCInfo::Parse(int iv_size, StreamReader* reader) { + const int kEntrySize = 6; + // Mandated by CENC spec + RCHECK(iv_size == 8 || iv_size == 16); + + memset(iv, 0, sizeof(iv)); + for (int i = 0; i < iv_size; i++) + RCHECK(reader->Read1(&iv[i])); + + if (!reader->HasBytes(1)) return true; + + uint16_t subsample_count; + RCHECK(reader->Read2(&subsample_count) && + reader->HasBytes(subsample_count * kEntrySize)); + + subsamples.resize(subsample_count); + for (int i = 0; i < subsample_count; i++) { + uint16_t clear_bytes; + uint32_t cypher_bytes; + RCHECK(reader->Read2(&clear_bytes) && + reader->Read4(&cypher_bytes)); + subsamples[i].clear_bytes = clear_bytes; + subsamples[i].cypher_bytes = cypher_bytes; + } + return true; +} + +size_t FrameCENCInfo::GetTotalSizeOfSubsamples() const { + size_t size = 0; + for (size_t i = 0; i < subsamples.size(); i++) { + size += subsamples[i].clear_bytes + subsamples[i].cypher_bytes; + } + return size; +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/cenc.h b/content/media/fmp4/demuxer/cenc.h new file mode 100644 index 000000000000..6a3d62ff5151 --- /dev/null +++ b/content/media/fmp4/demuxer/cenc.h @@ -0,0 +1,29 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_CENC_H_ +#define MEDIA_MP4_CENC_H_ + +#include + +#include "mp4_demuxer/basictypes.h" +#include "mp4_demuxer/decrypt_config.h" + +namespace mp4_demuxer { + +class StreamReader; + +struct FrameCENCInfo { + uint8_t iv[16]; + std::vector subsamples; + + FrameCENCInfo(); + ~FrameCENCInfo(); + bool Parse(int iv_size, StreamReader* r); + size_t GetTotalSizeOfSubsamples() const; +}; + +} // namespace mp4_demuxer + +#endif // MEDIA_MP4_CENC_H_ diff --git a/content/media/fmp4/demuxer/channel_layout.cc b/content/media/fmp4/demuxer/channel_layout.cc new file mode 100644 index 000000000000..ba7d275d346c --- /dev/null +++ b/content/media/fmp4/demuxer/channel_layout.cc @@ -0,0 +1,186 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/channel_layout.h" + +#include "mp4_demuxer/basictypes.h" + +namespace mp4_demuxer { + +static const int kLayoutToChannels[] = { + 0, // CHANNEL_LAYOUT_NONE + 0, // CHANNEL_LAYOUT_UNSUPPORTED + 1, // CHANNEL_LAYOUT_MONO + 2, // CHANNEL_LAYOUT_STEREO + 3, // CHANNEL_LAYOUT_2_1 + 3, // CHANNEL_LAYOUT_SURROUND + 4, // CHANNEL_LAYOUT_4_0 + 4, // CHANNEL_LAYOUT_2_2 + 4, // CHANNEL_LAYOUT_QUAD + 5, // CHANNEL_LAYOUT_5_0 + 6, // CHANNEL_LAYOUT_5_1 + 5, // CHANNEL_LAYOUT_5_0_BACK + 6, // CHANNEL_LAYOUT_5_1_BACK + 7, // CHANNEL_LAYOUT_7_0 + 8, // CHANNEL_LAYOUT_7_1 + 8, // CHANNEL_LAYOUT_7_1_WIDE + 2, // CHANNEL_LAYOUT_STEREO_DOWNMIX + 3, // CHANNEL_LAYOUT_2POINT1 + 4, // CHANNEL_LAYOUT_3_1 + 5, // CHANNEL_LAYOUT_4_1 + 6, // CHANNEL_LAYOUT_6_0 + 6, // CHANNEL_LAYOUT_6_0_FRONT + 6, // CHANNEL_LAYOUT_HEXAGONAL + 7, // CHANNEL_LAYOUT_6_1 + 7, // CHANNEL_LAYOUT_6_1_BACK + 7, // CHANNEL_LAYOUT_6_1_FRONT + 7, // CHANNEL_LAYOUT_7_0_FRONT + 8, // CHANNEL_LAYOUT_7_1_WIDE_BACK + 8, // CHANNEL_LAYOUT_OCTAGONAL + 0, // CHANNEL_LAYOUT_DISCRETE +}; + +// The channel orderings for each layout as specified by FFmpeg. Each value +// represents the index of each channel in each layout. Values of -1 mean the +// channel at that index is not used for that layout.For example, the left side +// surround sound channel in FFmpeg's 5.1 layout is in the 5th position (because +// the order is L, R, C, LFE, LS, RS), so +// kChannelOrderings[CHANNEL_LAYOUT_5POINT1][SIDE_LEFT] = 4; +static const int kChannelOrderings[CHANNEL_LAYOUT_MAX][CHANNELS_MAX] = { + // FL | FR | FC | LFE | BL | BR | FLofC | FRofC | BC | SL | SR + + // CHANNEL_LAYOUT_NONE + { -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_UNSUPPORTED + { -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_MONO + { -1 , -1 , 0 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_STEREO + { 0 , 1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_2_1 + { 0 , 1 , -1 , -1 , -1 , -1 , -1 , -1 , 2 , -1 , -1 }, + + // CHANNEL_LAYOUT_SURROUND + { 0 , 1 , 2 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_4_0 + { 0 , 1 , 2 , -1 , -1 , -1 , -1 , -1 , 3 , -1 , -1 }, + + // CHANNEL_LAYOUT_2_2 + { 0 , 1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , 2 , 3 }, + + // CHANNEL_LAYOUT_QUAD + { 0 , 1 , -1 , -1 , 2 , 3 , -1 , -1 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_5_0 + { 0 , 1 , 2 , -1 , -1 , -1 , -1 , -1 , -1 , 3 , 4 }, + + // CHANNEL_LAYOUT_5_1 + { 0 , 1 , 2 , 3 , -1 , -1 , -1 , -1 , -1 , 4 , 5 }, + + // FL | FR | FC | LFE | BL | BR | FLofC | FRofC | BC | SL | SR + + // CHANNEL_LAYOUT_5_0_BACK + { 0 , 1 , 2 , -1 , 3 , 4 , -1 , -1 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_5_1_BACK + { 0 , 1 , 2 , 3 , 4 , 5 , -1 , -1 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_7_0 + { 0 , 1 , 2 , -1 , 5 , 6 , -1 , -1 , -1 , 3 , 4 }, + + // CHANNEL_LAYOUT_7_1 + { 0 , 1 , 2 , 3 , 6 , 7 , -1 , -1 , -1 , 4 , 5 }, + + // CHANNEL_LAYOUT_7_1_WIDE + { 0 , 1 , 2 , 3 , -1 , -1 , 6 , 7 , -1 , 4 , 5 }, + + // CHANNEL_LAYOUT_STEREO_DOWNMIX + { 0 , 1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_2POINT1 + { 0 , 1 , -1 , 2 , -1 , -1 , -1 , -1 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_3_1 + { 0 , 1 , 2 , 3 , -1 , -1 , -1 , -1 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_4_1 + { 0 , 1 , 2 , 4 , -1 , -1 , -1 , -1 , 3 , -1 , -1 }, + + // CHANNEL_LAYOUT_6_0 + { 0 , 1 , 2 , -1 , -1 , -1 , -1 , -1 , 5 , 3 , 4 }, + + // CHANNEL_LAYOUT_6_0_FRONT + { 0 , 1 , -1 , -1 , -1 , -1 , 4 , 5 , -1 , 2 , 3 }, + + // FL | FR | FC | LFE | BL | BR | FLofC | FRofC | BC | SL | SR + + // CHANNEL_LAYOUT_HEXAGONAL + { 0 , 1 , 2 , -1 , 3 , 4 , -1 , -1 , 5 , -1 , -1 }, + + // CHANNEL_LAYOUT_6_1 + { 0 , 1 , 2 , 3 , -1 , -1 , -1 , -1 , 6 , 4 , 5 }, + + // CHANNEL_LAYOUT_6_1_BACK + { 0 , 1 , 2 , 3 , 4 , 5 , -1 , -1 , 6 , -1 , -1 }, + + // CHANNEL_LAYOUT_6_1_FRONT + { 0 , 1 , -1 , 6 , -1 , -1 , 4 , 5 , -1 , 2 , 3 }, + + // CHANNEL_LAYOUT_7_0_FRONT + { 0 , 1 , 2 , -1 , -1 , -1 , 5 , 6 , -1 , 3 , 4 }, + + // CHANNEL_LAYOUT_7_1_WIDE_BACK + { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , -1 , -1 , -1 }, + + // CHANNEL_LAYOUT_OCTAGONAL + { 0 , 1 , 2 , -1 , 5 , 6 , -1 , -1 , 7 , 3 , 4 }, + + // CHANNEL_LAYOUT_DISCRETE + { -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 }, + + // FL | FR | FC | LFE | BL | BR | FLofC | FRofC | BC | SL | SR +}; + +int ChannelLayoutToChannelCount(ChannelLayout layout) { + DCHECK_LT(static_cast(layout), arraysize(kLayoutToChannels)); + return kLayoutToChannels[layout]; +} + +// Converts a channel count into a channel layout. +ChannelLayout GuessChannelLayout(int channels) { + switch (channels) { + case 1: + return CHANNEL_LAYOUT_MONO; + case 2: + return CHANNEL_LAYOUT_STEREO; + case 3: + return CHANNEL_LAYOUT_SURROUND; + case 4: + return CHANNEL_LAYOUT_QUAD; + case 5: + return CHANNEL_LAYOUT_5_0; + case 6: + return CHANNEL_LAYOUT_5_1; + case 7: + return CHANNEL_LAYOUT_6_1; + case 8: + return CHANNEL_LAYOUT_7_1; + default: + DMX_LOG("Unsupported channel count: %d\n", channels); + } + return CHANNEL_LAYOUT_UNSUPPORTED; +} + +int ChannelOrder(ChannelLayout layout, Channels channel) { + DCHECK_LT(static_cast(layout), arraysize(kChannelOrderings)); + DCHECK_LT(static_cast(channel), arraysize(kChannelOrderings[0])); + return kChannelOrderings[layout][channel]; +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/channel_layout.h b/content/media/fmp4/demuxer/channel_layout.h new file mode 100644 index 000000000000..6377b28575d1 --- /dev/null +++ b/content/media/fmp4/demuxer/channel_layout.h @@ -0,0 +1,134 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_CHANNEL_LAYOUT_H_ +#define MEDIA_BASE_CHANNEL_LAYOUT_H_ + + +namespace mp4_demuxer { + +// Enumerates the various representations of the ordering of audio channels. +// Logged to UMA, so never reuse a value, always add new/greater ones! +enum ChannelLayout { + CHANNEL_LAYOUT_NONE = 0, + CHANNEL_LAYOUT_UNSUPPORTED = 1, + + // Front C + CHANNEL_LAYOUT_MONO = 2, + + // Front L, Front R + CHANNEL_LAYOUT_STEREO = 3, + + // Front L, Front R, Back C + CHANNEL_LAYOUT_2_1 = 4, + + // Front L, Front R, Front C + CHANNEL_LAYOUT_SURROUND = 5, + + // Front L, Front R, Front C, Back C + CHANNEL_LAYOUT_4_0 = 6, + + // Front L, Front R, Side L, Side R + CHANNEL_LAYOUT_2_2 = 7, + + // Front L, Front R, Back L, Back R + CHANNEL_LAYOUT_QUAD = 8, + + // Front L, Front R, Front C, Side L, Side R + CHANNEL_LAYOUT_5_0 = 9, + + // Front L, Front R, Front C, Side L, Side R, LFE + CHANNEL_LAYOUT_5_1 = 10, + + // Front L, Front R, Front C, Back L, Back R + CHANNEL_LAYOUT_5_0_BACK = 11, + + // Front L, Front R, Front C, Back L, Back R, LFE + CHANNEL_LAYOUT_5_1_BACK = 12, + + // Front L, Front R, Front C, Side L, Side R, Back L, Back R + CHANNEL_LAYOUT_7_0 = 13, + + // Front L, Front R, Front C, Side L, Side R, LFE, Back L, Back R + CHANNEL_LAYOUT_7_1 = 14, + + // Front L, Front R, Front C, Side L, Side R, LFE, Front LofC, Front RofC + CHANNEL_LAYOUT_7_1_WIDE = 15, + + // Stereo L, Stereo R + CHANNEL_LAYOUT_STEREO_DOWNMIX = 16, + + // Stereo L, Stereo R, LFE + CHANNEL_LAYOUT_2POINT1 = 17, + + // Stereo L, Stereo R, Front C, LFE + CHANNEL_LAYOUT_3_1 = 18, + + // Stereo L, Stereo R, Front C, Rear C, LFE + CHANNEL_LAYOUT_4_1 = 19, + + // Stereo L, Stereo R, Front C, Side L, Side R, Back C + CHANNEL_LAYOUT_6_0 = 20, + + // Stereo L, Stereo R, Side L, Side R, Front LofC, Front RofC + CHANNEL_LAYOUT_6_0_FRONT = 21, + + // Stereo L, Stereo R, Side L, Side R, Front C, Rear C. + CHANNEL_LAYOUT_HEXAGONAL = 22, + + // Stereo L, Stereo R, Side L, Side R, Front C, Rear Center, LFE + CHANNEL_LAYOUT_6_1 = 23, + + // Stereo L, Stereo R, Back L, Back R, Front C, Rear Center, LFE + CHANNEL_LAYOUT_6_1_BACK = 24, + + // Stereo L, Stereo R, Side L, Side R, Front LofC, Front RofC, LFE + CHANNEL_LAYOUT_6_1_FRONT = 25, + + // Front L, Front R, Front C, Side L, Side R, Front LofC, Front RofC + CHANNEL_LAYOUT_7_0_FRONT = 26, + + // Front L, Front R, Front C, Back L, Back R, LFE, Front LofC, Front RofC + CHANNEL_LAYOUT_7_1_WIDE_BACK = 27, + + // Front L, Front R, Front C, Side L, Side R, Rear C, Back L, Back R. + CHANNEL_LAYOUT_OCTAGONAL = 28, + + // Channels are not explicitly mapped to speakers. + CHANNEL_LAYOUT_DISCRETE = 29, + + // Total number of layouts. + CHANNEL_LAYOUT_MAX // Must always be last! +}; + +enum Channels { + LEFT = 0, + RIGHT, + CENTER, + LFE, + BACK_LEFT, + BACK_RIGHT, + LEFT_OF_CENTER, + RIGHT_OF_CENTER, + BACK_CENTER, + SIDE_LEFT, + SIDE_RIGHT, + CHANNELS_MAX +}; + +// Returns the expected channel position in an interleaved stream. Values of -1 +// mean the channel at that index is not used for that layout. Values range +// from 0 to CHANNELS_MAX - 1. +int ChannelOrder(ChannelLayout layout, Channels channel); + +// Returns the number of channels in a given ChannelLayout. +int ChannelLayoutToChannelCount(ChannelLayout layout); + +// Given the number of channels, return the best layout, +// or return CHANNEL_LAYOUT_UNSUPPORTED if there is no good match. +ChannelLayout GuessChannelLayout(int channels); + +} // namespace mp4_demuxer + +#endif // MEDIA_BASE_CHANNEL_LAYOUT_H_ diff --git a/content/media/fmp4/demuxer/decrypt_config.cc b/content/media/fmp4/demuxer/decrypt_config.cc new file mode 100644 index 000000000000..809725db1d58 --- /dev/null +++ b/content/media/fmp4/demuxer/decrypt_config.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/decrypt_config.h" + +namespace mp4_demuxer { + +DecryptConfig::DecryptConfig(const std::string& key_id, + const std::string& iv, + const int data_offset, + const std::vector& subsamples) + : key_id_(key_id), + iv_(iv), + data_offset_(data_offset), + subsamples_(subsamples) { + DCHECK_GT(key_id.size(), 0u); + DCHECK(iv.size() == static_cast(DecryptConfig::kDecryptionKeySize) || + iv.empty()); + DCHECK_GE(data_offset, 0); +} + +DecryptConfig::~DecryptConfig() {} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/decrypt_config.h b/content/media/fmp4/demuxer/decrypt_config.h new file mode 100644 index 000000000000..3c56eb1f4a0b --- /dev/null +++ b/content/media/fmp4/demuxer/decrypt_config.h @@ -0,0 +1,78 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_DECRYPT_CONFIG_H_ +#define MEDIA_BASE_DECRYPT_CONFIG_H_ + +#include +#include + +#include "mp4_demuxer/basictypes.h" + +namespace mp4_demuxer { + +// The Common Encryption spec provides for subsample encryption, where portions +// of a sample are set in cleartext. A SubsampleEntry specifies the number of +// clear and encrypted bytes in each subsample. For decryption, all of the +// encrypted bytes in a sample should be considered a single logical stream, +// regardless of how they are divided into subsamples, and the clear bytes +// should not be considered as part of decryption. This is logically equivalent +// to concatenating all 'cypher_bytes' portions of subsamples, decrypting that +// result, and then copying each byte from the decrypted block over the +// position of the corresponding encrypted byte. +struct SubsampleEntry { + uint32_t clear_bytes; + uint32_t cypher_bytes; +}; + +// Contains all information that a decryptor needs to decrypt a media sample. +class DecryptConfig { + public: + // Keys are always 128 bits. + static const int kDecryptionKeySize = 16; + + // |key_id| is the ID that references the decryption key for this sample. + // |iv| is the initialization vector defined by the encrypted format. + // Currently |iv| must be 16 bytes as defined by WebM and ISO. Or must be + // empty which signals an unencrypted frame. + // |data_offset| is the amount of data that should be discarded from the + // head of the sample buffer before applying subsample information. A + // decrypted buffer will be shorter than an encrypted buffer by this amount. + // |subsamples| defines the clear and encrypted portions of the sample as + // described above. A decrypted buffer will be equal in size to the sum + // of the subsample sizes. + // + // |data_offset| is applied before |subsamples|. + DecryptConfig(const std::string& key_id, + const std::string& iv, + const int data_offset, + const std::vector& subsamples); + ~DecryptConfig(); + + const std::string& key_id() const { return key_id_; } + const std::string& iv() const { return iv_; } + int data_offset() const { return data_offset_; } + const std::vector& subsamples() const { return subsamples_; } + + private: + const std::string key_id_; + + // Initialization vector. + const std::string iv_; + + // TODO(fgalligan): Remove |data_offset_| if there is no plan to use it in + // the future. + // Amount of data to be discarded before applying subsample information. + const int data_offset_; + + // Subsample information. May be empty for some formats, meaning entire frame + // (less data ignored by data_offset_) is encrypted. + const std::vector subsamples_; + + DISALLOW_COPY_AND_ASSIGN(DecryptConfig); +}; + +} // namespace mp4_demuxer + +#endif // MEDIA_BASE_DECRYPT_CONFIG_H_ diff --git a/content/media/fmp4/demuxer/es_descriptor.cc b/content/media/fmp4/demuxer/es_descriptor.cc new file mode 100644 index 000000000000..fb7c5f20fc9f --- /dev/null +++ b/content/media/fmp4/demuxer/es_descriptor.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/es_descriptor.h" +#include "mp4_demuxer/bit_reader.h" + +namespace mp4_demuxer { + +// The elementary stream size is specific by up to 4 bytes. +// The MSB of a byte indicates if there are more bytes for the size. +static bool ReadESSize(BitReader* reader, uint32_t* size) { + uint8_t msb; + uint8_t byte; + + *size = 0; + + for (size_t i = 0; i < 4; ++i) { + RCHECK(reader->ReadBits(1, &msb)); + RCHECK(reader->ReadBits(7, &byte)); + *size = (*size << 7) + byte; + + if (msb == 0) + break; + } + + return true; +} + +ESDescriptor::ESDescriptor() + : object_type_(kForbidden) { +} + +ESDescriptor::~ESDescriptor() {} + +bool ESDescriptor::Parse(const std::vector& data) { + BitReader reader(&data[0], data.size()); + uint8_t tag; + uint32_t size; + uint8_t stream_dependency_flag; + uint8_t url_flag; + uint8_t ocr_stream_flag; + uint16_t dummy; + + RCHECK(reader.ReadBits(8, &tag)); + RCHECK(tag == kESDescrTag); + RCHECK(ReadESSize(&reader, &size)); + + RCHECK(reader.ReadBits(16, &dummy)); // ES_ID + RCHECK(reader.ReadBits(1, &stream_dependency_flag)); + RCHECK(reader.ReadBits(1, &url_flag)); + RCHECK(!url_flag); // We don't support url flag + RCHECK(reader.ReadBits(1, &ocr_stream_flag)); + RCHECK(reader.ReadBits(5, &dummy)); // streamPriority + + if (stream_dependency_flag) + RCHECK(reader.ReadBits(16, &dummy)); // dependsOn_ES_ID + if (ocr_stream_flag) + RCHECK(reader.ReadBits(16, &dummy)); // OCR_ES_Id + + RCHECK(ParseDecoderConfigDescriptor(&reader)); + + return true; +} + +uint8_t ESDescriptor::object_type() const { + return object_type_; +} + +const std::vector& ESDescriptor::decoder_specific_info() const { + return decoder_specific_info_; +} + +bool ESDescriptor::ParseDecoderConfigDescriptor(BitReader* reader) { + uint8_t tag; + uint32_t size; + uint64_t dummy; + + RCHECK(reader->ReadBits(8, &tag)); + RCHECK(tag == kDecoderConfigDescrTag); + RCHECK(ReadESSize(reader, &size)); + + RCHECK(reader->ReadBits(8, &object_type_)); + RCHECK(reader->ReadBits(64, &dummy)); + RCHECK(reader->ReadBits(32, &dummy)); + RCHECK(ParseDecoderSpecificInfo(reader)); + + return true; +} + +bool ESDescriptor::ParseDecoderSpecificInfo(BitReader* reader) { + uint8_t tag; + uint32_t size; + + RCHECK(reader->ReadBits(8, &tag)); + RCHECK(tag == kDecoderSpecificInfoTag); + RCHECK(ReadESSize(reader, &size)); + + decoder_specific_info_.resize(size); + for (uint32_t i = 0; i < size; ++i) + RCHECK(reader->ReadBits(8, &decoder_specific_info_[i])); + + return true; +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/es_descriptor.h b/content/media/fmp4/demuxer/es_descriptor.h new file mode 100644 index 000000000000..b213eba274ec --- /dev/null +++ b/content/media/fmp4/demuxer/es_descriptor.h @@ -0,0 +1,53 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_ES_DESCRIPTOR_H_ +#define MEDIA_MP4_ES_DESCRIPTOR_H_ + +#include + +#include "mp4_demuxer/basictypes.h" + +namespace mp4_demuxer { + +class BitReader; + +// The following values are extracted from ISO 14496 Part 1 Table 5 - +// objectTypeIndication Values. Only values currently in use are included. +enum ObjectType { + kForbidden = 0, + kISO_14496_3 = 0x40, // MPEG4 AAC + kISO_13818_7_AAC_LC = 0x67 // MPEG2 AAC-LC +}; + +// This class parse object type and decoder specific information from an +// elementary stream descriptor, which is usually contained in an esds box. +// Please refer to ISO 14496 Part 1 7.2.6.5 for more details. +class ESDescriptor { + public: + ESDescriptor(); + ~ESDescriptor(); + + bool Parse(const std::vector& data); + + uint8_t object_type() const; + const std::vector& decoder_specific_info() const; + + private: + enum Tag { + kESDescrTag = 0x03, + kDecoderConfigDescrTag = 0x04, + kDecoderSpecificInfoTag = 0x05 + }; + + bool ParseDecoderConfigDescriptor(BitReader* reader); + bool ParseDecoderSpecificInfo(BitReader* reader); + + uint8_t object_type_; + std::vector decoder_specific_info_; +}; + +} // namespace mp4_demuxer + +#endif // MEDIA_MP4_ES_DESCRIPTOR_H_ diff --git a/content/media/fmp4/demuxer/fourccs.h b/content/media/fmp4/demuxer/fourccs.h new file mode 100644 index 000000000000..3c584aa462d6 --- /dev/null +++ b/content/media/fmp4/demuxer/fourccs.h @@ -0,0 +1,97 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_FOURCCS_H_ +#define MEDIA_MP4_FOURCCS_H_ + +#include + +namespace mp4_demuxer { + +enum FourCC { + FOURCC_NULL = 0, + FOURCC_AVC1 = 0x61766331, + FOURCC_AVCC = 0x61766343, + FOURCC_BLOC = 0x626C6F63, + FOURCC_CENC = 0x63656e63, + FOURCC_CO64 = 0x636f3634, + FOURCC_CTTS = 0x63747473, + FOURCC_DINF = 0x64696e66, + FOURCC_EDTS = 0x65647473, + FOURCC_ELST = 0x656c7374, + FOURCC_ENCA = 0x656e6361, + FOURCC_ENCV = 0x656e6376, + FOURCC_ESDS = 0x65736473, + FOURCC_FREE = 0x66726565, + FOURCC_FRMA = 0x66726d61, + FOURCC_FTYP = 0x66747970, + FOURCC_HDLR = 0x68646c72, + FOURCC_HINT = 0x68696e74, + FOURCC_IODS = 0x696f6473, + FOURCC_MDAT = 0x6d646174, + FOURCC_MDHD = 0x6d646864, + FOURCC_MDIA = 0x6d646961, + FOURCC_MECO = 0x6d65636f, + FOURCC_MEHD = 0x6d656864, + FOURCC_META = 0x6d657461, + FOURCC_MFHD = 0x6d666864, + FOURCC_MFRA = 0x6d667261, + FOURCC_MINF = 0x6d696e66, + FOURCC_MOOF = 0x6d6f6f66, + FOURCC_MOOV = 0x6d6f6f76, + FOURCC_MP4A = 0x6d703461, + FOURCC_MP4V = 0x6d703476, + FOURCC_MVEX = 0x6d766578, + FOURCC_MVHD = 0x6d766864, + FOURCC_PASP = 0x70617370, + FOURCC_PDIN = 0x7064696e, + FOURCC_PRFT = 0x70726674, + FOURCC_PSSH = 0x70737368, + FOURCC_SAIO = 0x7361696f, + FOURCC_SAIZ = 0x7361697a, + FOURCC_SCHI = 0x73636869, + FOURCC_SCHM = 0x7363686d, + FOURCC_SDTP = 0x73647470, + FOURCC_SIDX = 0x73696478, + FOURCC_SINF = 0x73696e66, + FOURCC_SKIP = 0x736b6970, + FOURCC_SMHD = 0x736d6864, + FOURCC_SOUN = 0x736f756e, + FOURCC_SSIX = 0x73736978, + FOURCC_STBL = 0x7374626c, + FOURCC_STCO = 0x7374636f, + FOURCC_STSC = 0x73747363, + FOURCC_STSD = 0x73747364, + FOURCC_STSS = 0x73747373, + FOURCC_STSZ = 0x7374737a, + FOURCC_STTS = 0x73747473, + FOURCC_STYP = 0x73747970, + FOURCC_TENC = 0x74656e63, + FOURCC_TFDT = 0x74666474, + FOURCC_TFHD = 0x74666864, + FOURCC_TKHD = 0x746b6864, + FOURCC_TRAF = 0x74726166, + FOURCC_TRAK = 0x7472616b, + FOURCC_TREX = 0x74726578, + FOURCC_TRUN = 0x7472756e, + FOURCC_UDTA = 0x75647461, + FOURCC_UUID = 0x75756964, + FOURCC_VIDE = 0x76696465, + FOURCC_VMHD = 0x766d6864, + FOURCC_WIDE = 0x77696465, +}; + +const inline std::string FourCCToString(FourCC fourcc) { + char buf[5]; + buf[0] = (fourcc >> 24) & 0xff; + buf[1] = (fourcc >> 16) & 0xff; + buf[2] = (fourcc >> 8) & 0xff; + buf[3] = (fourcc) & 0xff; + buf[4] = 0; + return std::string(buf); +} + +} // namespace mp4_demuxer + +#endif // MEDIA_MP4_FOURCCS_H_ diff --git a/content/media/fmp4/demuxer/mp4_demuxer.cc b/content/media/fmp4/demuxer/mp4_demuxer.cc new file mode 100644 index 000000000000..b1994644b077 --- /dev/null +++ b/content/media/fmp4/demuxer/mp4_demuxer.cc @@ -0,0 +1,535 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +#include "mp4_demuxer/mp4_demuxer.h" + +#include "mp4_demuxer/Streams.h" +#include "mp4_demuxer/box_reader.h" +#include "mp4_demuxer/box_definitions.h" +#include "mp4_demuxer/basictypes.h" +#include "mp4_demuxer/es_descriptor.h" +#include "mp4_demuxer/video_util.h" +#include "mp4_demuxer/track_run_iterator.h" +#include "mp4_demuxer/audio_decoder_config.h" +#include "mp4_demuxer/video_decoder_config.h" + +#include + +using namespace std; + +namespace mp4_demuxer { + + +MP4Sample::MP4Sample(Microseconds _decode_timestamp, + Microseconds _composition_timestamp, + Microseconds _duration, + int64_t _byte_offset, + std::vector* _data, + TrackType _type, + DecryptConfig* _decrypt_config, + bool _is_sync_point) + : decode_timestamp(_decode_timestamp), + composition_timestamp(_composition_timestamp), + duration(_duration), + byte_offset(_byte_offset), + data(_data), + type(_type), + decrypt_config(_decrypt_config), + is_sync_point(_is_sync_point) +{ +} + +MP4Sample::~MP4Sample() +{ +} + +bool MP4Sample::is_encrypted() const { + return decrypt_config != nullptr; +}; + + + + +MP4Demuxer::MP4Demuxer(Stream* stream) + : state_(kWaitingForInit), + stream_(stream), + stream_offset_(0), + duration_(InfiniteMicroseconds), + moof_head_(0), + mdat_tail_(0), + audio_track_id_(0), + video_track_id_(0), + audio_frameno(0), + video_frameno(0), + has_audio_(false), + has_sbr_(false), + is_audio_track_encrypted_(false), + has_video_(false), + is_video_track_encrypted_(false), + can_seek_(false) +{ +} + +MP4Demuxer::~MP4Demuxer() +{ +} + +bool MP4Demuxer::Init() +{ + ChangeState(kParsingBoxes); + + // Read from the stream until the moov box is read. This will have the + // header data that we need to initialize the decoders. + bool ok = true; + const int64_t length = stream_->Length(); + while (ok && + stream_offset_ < length && + !moov_ && + state_ == kParsingBoxes) { + ok = ParseBox(); + } + return state_ >= kParsingBoxes && + state_ < kError; +} + +void MP4Demuxer::Reset() { + moov_ = nullptr; + runs_ = nullptr; + stream_offset_; // TODO; Not sure if this needs to be reset? + DMX_LOG("Warning: resetting stream_offset_\n"); + moof_head_ = 0; + mdat_tail_ = 0; +} + +// TODO(xhwang): Figure out the init data type appropriately once it's spec'ed. +static const char kMp4InitDataType[] = "video/mp4"; + +bool MP4Demuxer::ParseMoov(BoxReader* reader) { + RCHECK(state_ < kError); + moov_ = new Movie(); + RCHECK(moov_->Parse(reader)); + runs_ = new TrackRunIterator(moov_.get()); + + has_audio_ = false; + has_video_ = false; + + for (std::vector::const_iterator track = moov_->tracks.begin(); + track != moov_->tracks.end(); ++track) { + // TODO(strobe): Only the first audio and video track present in a file are + // used. (Track selection is better accomplished via Source IDs, though, so + // adding support for track selection within a stream is low-priority.) + const SampleDescription& samp_descr = + track->media.information.sample_table.description; + + // TODO(strobe): When codec reconfigurations are supported, detect and send + // a codec reconfiguration for fragments using a sample description index + // different from the previous one + size_t desc_idx = 0; + for (size_t t = 0; t < moov_->extends.tracks.size(); t++) { + const TrackExtends& trex = moov_->extends.tracks[t]; + if (trex.track_id == track->header.track_id) { + desc_idx = trex.default_sample_description_index; + break; + } + } + RCHECK(desc_idx > 0); + desc_idx -= 1; // BMFF descriptor index is one-based + + if (track->media.handler.type == kAudio && !audio_config_.IsValidConfig()) { + RCHECK(!samp_descr.audio_entries.empty()); + + // It is not uncommon to find otherwise-valid files with incorrect sample + // description indices, so we fail gracefully in that case. + if (desc_idx >= samp_descr.audio_entries.size()) + desc_idx = 0; + const AudioSampleEntry& entry = samp_descr.audio_entries[desc_idx]; + const AAC& aac = entry.esds.aac; + + if (!(entry.format == FOURCC_MP4A || + (entry.format == FOURCC_ENCA && + entry.sinf.format.format == FOURCC_MP4A))) { + DMX_LOG("Unsupported audio format 0x%x in stsd box\n", entry.format); + return false; + } + + int audio_type = entry.esds.object_type; + DMX_LOG("audio_type 0x%x\n", audio_type); + + const std::vector& asc = aac.AudioSpecificConfig(); + if (asc.size() > 0) { + DMX_LOG("audio specific config:"); + for (unsigned i=0; iheader.track_id; + } + if (track->media.handler.type == kVideo && !video_config_.IsValidConfig()) { + RCHECK(!samp_descr.video_entries.empty()); + if (desc_idx >= samp_descr.video_entries.size()) + desc_idx = 0; + const VideoSampleEntry& entry = samp_descr.video_entries[desc_idx]; + + if (!(entry.format == FOURCC_AVC1 || + (entry.format == FOURCC_ENCV && + entry.sinf.format.format == FOURCC_AVC1))) { + DMX_LOG("Unsupported video format 0x%x in stsd box.\n", entry.format); + return false; + } + + // TODO(strobe): Recover correct crop box + IntSize coded_size(entry.width, entry.height); + IntRect visible_rect(0, 0, coded_size.width(), coded_size.height()); + IntSize natural_size = GetNaturalSize(visible_rect.size(), + entry.pixel_aspect.h_spacing, + entry.pixel_aspect.v_spacing); + is_video_track_encrypted_ = entry.sinf.info.track_encryption.is_encrypted; + DMX_LOG("is_video_track_encrypted_: %d\n", is_video_track_encrypted_); + video_config_.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrameFormat::YV12, + coded_size, visible_rect, natural_size, + // No decoder-specific buffer needed for AVC; + // SPS/PPS are embedded in the video stream + NULL, 0, is_video_track_encrypted_, true); + has_video_ = true; + video_track_id_ = track->header.track_id; + } + } + + //RCHECK(config_cb_.Run(audio_config, video_config)); + + if (moov_->extends.header.fragment_duration > 0) { + duration_ = MicrosecondsFromRational(moov_->extends.header.fragment_duration, + moov_->header.timescale); + } else if (moov_->header.duration > 0 && + moov_->header.duration != kuint64max) { + duration_ = MicrosecondsFromRational(moov_->header.duration, + moov_->header.timescale); + } else { + duration_ = InfiniteMicroseconds; + } + + //if (!init_cb_.is_null()) + // base::ResetAndReturn(&init_cb_).Run(true, duration); + + return true; +} + +Microseconds +MP4Demuxer::Duration() const { + return duration_; +} + +bool MP4Demuxer::ParseMoof(BoxReader* reader) { + RCHECK(state_ < kError); + RCHECK(moov_.get()); // Must already have initialization segment + MovieFragment moof; + RCHECK(moof.Parse(reader)); + RCHECK(runs_->Init(moof)); + //new_segment_cb_.Run(runs_->GetMinDecodeTimestamp()); + ChangeState(kEmittingSamples); + return true; +} + +bool MP4Demuxer::ParseBox() { + RCHECK(state_ < kError); + bool err = false; + nsAutoPtr reader(BoxReader::ReadTopLevelBox(stream_, + stream_offset_, + &err)); + if (!reader || err) { + DMX_LOG("Failed to read box at offset=%lld", stream_offset_); + return false; + } + string type = FourCCToString(reader->type()); + + DMX_LOG("offset=%lld version=0x%x flags=0x%x size=%d", + stream_offset_, (uint32_t)reader->version(), + reader->flags(), reader->size()); + + if (reader->type() == FOURCC_MOOV) { + DMX_LOG("ParseMoov\n"); + if (!ParseMoov(reader.get())) { + DMX_LOG("ParseMoov failed\n"); + return false; + } + } else if (reader->type() == FOURCC_MOOF) { + DMX_LOG("MOOF encountered\n."); + moof_head_ = stream_offset_; + if (!ParseMoof(reader.get())) { + DMX_LOG("ParseMoof failed\n"); + return false; + } + mdat_tail_ = stream_offset_ + reader->size(); + } + + stream_offset_ += reader->size(); + + return true; +} + +bool MP4Demuxer::EmitSample(nsAutoPtr* sample) { + bool ok = true; + if (!runs_->IsRunValid()) { + + // Flush any buffers we've gotten in this chunk so that buffers don't + // cross NewSegment() calls + //ok = SendAndFlushSamples(/*audio_buffers, video_buffers*/); + //if (!ok) + // return false; + + ChangeState(kParsingBoxes); + //end_of_segment_cb_.Run(); + return true; + } + + if (!runs_->IsSampleValid()) { + runs_->AdvanceRun(); + return true; + } + + bool audio = has_audio_ && audio_track_id_ == runs_->track_id(); + bool video = has_video_ && video_track_id_ == runs_->track_id(); + + // Skip this entire track if it's not one we're interested in + if (!audio && !video) + runs_->AdvanceRun(); + + // Attempt to cache the auxiliary information first. Aux info is usually + // placed in a contiguous block before the sample data, rather than being + // interleaved. If we didn't cache it, this would require that we retain the + // start of the segment buffer while reading samples. Aux info is typically + // quite small compared to sample data, so this pattern is useful on + // memory-constrained devices where the source buffer consumes a substantial + // portion of the total system memory. + if (runs_->AuxInfoNeedsToBeCached()) { + int64_t aux_info_offset = runs_->aux_info_offset() + moof_head_; + if (stream_->Length() - aux_info_offset < runs_->aux_info_size()) { + return false; + } + + return runs_->CacheAuxInfo(stream_, moof_head_); + } + + nsAutoPtr decrypt_config; + std::vector subsamples; + if (runs_->is_encrypted()) { + runs_->GetDecryptConfig(decrypt_config); + subsamples = decrypt_config->subsamples(); + } + + nsAutoPtr> frame_buf(new vector()); + const int64_t sample_offset = runs_->sample_offset() + moof_head_; + StreamReader reader(stream_, sample_offset, runs_->sample_size()); + reader.ReadVec(frame_buf, runs_->sample_size()); + + if (video) { + if (!PrepareAVCBuffer(runs_->video_description().avcc, + frame_buf, &subsamples)) { + DMX_LOG("Failed to prepare AVC sample for decode\n"); + return false; + } + } + + if (audio) { + if (!PrepareAACBuffer(runs_->audio_description().esds.aac, + frame_buf, &subsamples)) { + DMX_LOG("Failed to prepare AAC sample for decode\n"); + return false; + } + } + + const bool is_encrypted = (audio && is_audio_track_encrypted_) || + (video && is_video_track_encrypted_); + assert(runs_->is_encrypted() == is_encrypted); + if (decrypt_config) { + if (!subsamples.empty()) { + // Create a new config with the updated subsamples. + decrypt_config = new DecryptConfig(decrypt_config->key_id(), + decrypt_config->iv(), + decrypt_config->data_offset(), + subsamples); + } + // else, use the existing config. + } else if (is_encrypted) { + // The media pipeline requires a DecryptConfig with an empty |iv|. + // TODO(ddorwin): Refactor so we do not need a fake key ID ("1"); + decrypt_config = new DecryptConfig("1", "", 0, std::vector()); + } + + assert(audio || video); + *sample = new MP4Sample(runs_->dts(), + runs_->cts(), + runs_->duration(), + sample_offset, + frame_buf.forget(), + audio ? kAudio : kVideo, + decrypt_config.forget(), + runs_->is_keyframe()); + runs_->AdvanceSample(); + return true; +} + +bool MP4Demuxer::PrepareAVCBuffer( + const AVCDecoderConfigurationRecord& avc_config, + std::vector* frame_buf, + std::vector* subsamples) const { + // Convert the AVC NALU length fields to Annex B headers, as expected by + // decoding libraries. Since this may enlarge the size of the buffer, we also + // update the clear byte count for each subsample if encryption is used to + // account for the difference in size between the length prefix and Annex B + // start code. + RCHECK(AVC::ConvertFrameToAnnexB(avc_config.length_size, frame_buf)); + if (!subsamples->empty()) { + const int nalu_size_diff = 4 - avc_config.length_size; + size_t expected_size = runs_->sample_size() + + subsamples->size() * nalu_size_diff; + RCHECK(frame_buf->size() == expected_size); + for (size_t i = 0; i < subsamples->size(); i++) + (*subsamples)[i].clear_bytes += nalu_size_diff; + } + + if (runs_->is_keyframe()) { + // If this is a keyframe, we (re-)inject SPS and PPS headers at the start of + // a frame. If subsample info is present, we also update the clear byte + // count for that first subsample. + std::vector param_sets; + RCHECK(AVC::ConvertConfigToAnnexB(avc_config, ¶m_sets)); + frame_buf->insert(frame_buf->begin(), + param_sets.begin(), param_sets.end()); + if (!subsamples->empty()) + (*subsamples)[0].clear_bytes += param_sets.size(); + } + return true; +} + +bool MP4Demuxer::PrepareAACBuffer(const AAC& aac_config, + std::vector* frame_buf, + std::vector* subsamples) const { + // Append an ADTS header to every audio sample. + RCHECK(aac_config.ConvertEsdsToADTS(frame_buf)); + + // As above, adjust subsample information to account for the headers. AAC is + // not required to use subsample encryption, so we may need to add an entry. + if (subsamples->empty()) { + SubsampleEntry entry; + entry.clear_bytes = AAC::kADTSHeaderSize; + entry.cypher_bytes = frame_buf->size() - AAC::kADTSHeaderSize; + subsamples->push_back(entry); + } else { + (*subsamples)[0].clear_bytes += AAC::kADTSHeaderSize; + } + return true; +} + +// Reads the metadata boxes. +bool MP4Demuxer::Demux(nsAutoPtr* sample, + bool* end_of_stream) +{ + RCHECK(state_ < kError); + assert(state_ > kWaitingForInit); + *end_of_stream = false; + + const int64_t length = stream_->Length(); + bool ok = true; + while (ok) { + if (state_ == kParsingBoxes) { + if (stream_offset_ < length) { + ok = ParseBox(); + } else { + DMX_LOG("End of stream reached.\n"); + *end_of_stream = true; + break; + } + } else { + DCHECK_EQ(kEmittingSamples, state_); + ok = EmitSample(sample); + if (ok && *sample) { + // Got a sample, return. + break; + } + } + } + + if (!ok) { + DMX_LOG("Error demuxing stream\n"); + ChangeState(kError); + return false; + } + + return true; +} + +void MP4Demuxer::ChangeState(State new_state) { + DMX_LOG("Demuxer changing state: %d\n", new_state); + state_ = new_state; + if (state_ == kError) { + Reset(); + } +} + +const AudioDecoderConfig& +MP4Demuxer::AudioConfig() const +{ + return audio_config_; +} + +const VideoDecoderConfig& +MP4Demuxer::VideoConfig() const +{ + return video_config_; +} + +bool +MP4Demuxer::HasAudio() const +{ + return has_audio_; +} + +bool +MP4Demuxer::HasVideo() const +{ + return has_video_; +} + +bool +MP4Demuxer::CanSeek() const +{ + return can_seek_; +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/mp4_demuxer.h b/content/media/fmp4/demuxer/mp4_demuxer.h new file mode 100644 index 000000000000..05d90df2b61b --- /dev/null +++ b/content/media/fmp4/demuxer/mp4_demuxer.h @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_MP4DEMUXER_H +#define MEDIA_MP4_MP4DEMUXER_H + +#include "mp4_demuxer/audio_decoder_config.h" +#include "mp4_demuxer/video_decoder_config.h" +#include "mp4_demuxer/decrypt_config.h" +#include "mp4_demuxer/box_definitions.h" + + +#include "nsAutoPtr.h" +#include + +namespace mp4_demuxer { + +class Stream; +class BoxReader; +struct Movie; +class TrackRunIterator; +struct AVCDecoderConfigurationRecord; +class AAC; + +// Constructs an MP4 Sample. Note this assumes ownership of the |data| vector +// passed in. +struct MP4Sample { + MP4Sample(Microseconds decode_timestamp, + Microseconds composition_timestamp, + Microseconds duration, + int64_t byte_offset, + std::vector* data, + TrackType type, + DecryptConfig* decrypt_config, + bool is_sync_point); + ~MP4Sample(); + + const Microseconds decode_timestamp; + + const Microseconds composition_timestamp; + + const Microseconds duration; + + // Offset of sample in byte stream. + const int64_t byte_offset; + + // Raw demuxed data. + const nsAutoPtr> data; + + // Is this an audio or video sample? + const TrackType type; + + const nsAutoPtr decrypt_config; + + // Whether this is a keyframe or not. + const bool is_sync_point; + + bool is_encrypted() const; +}; + +class MP4Demuxer { +public: + MP4Demuxer(Stream* stream); + ~MP4Demuxer(); + + bool Init(); + + // Reads the metadata boxes, up to the first fragment. + bool Demux(nsAutoPtr* sample, + bool* end_of_stream); + + bool HasAudio() const; + const AudioDecoderConfig& AudioConfig() const; + + bool HasVideo() const; + const VideoDecoderConfig& VideoConfig() const; + + Microseconds Duration() const; + + bool CanSeek() const; + +private: + + enum State { + kWaitingForInit, + kParsingBoxes, + kEmittingSamples, + kError + }; + + // Parses the bitstream. Returns false on error. + bool Parse(nsAutoPtr* sample, + bool& end_of_stream); + + void ChangeState(State new_state); + + // Return true on success, false on failure. + bool ParseBox(); + bool ParseMoov(BoxReader* reader); + bool ParseMoof(BoxReader* reader); + + void Reset(); + + bool EmitSample(nsAutoPtr* sample); + + bool PrepareAACBuffer(const AAC& aac_config, + std::vector* frame_buf, + std::vector* subsamples) const; + + bool PrepareAVCBuffer(const AVCDecoderConfigurationRecord& avc_config, + std::vector* frame_buf, + std::vector* subsamples) const; + + State state_; + + // Stream abstraction that we read from. It is the responsibility of the + // owner of the demuxer to ensure that it stays alive for the lifetime + // of the demuxer. + Stream* stream_; + int64_t stream_offset_; + + Microseconds duration_; + + // These two parameters are only valid in the |kEmittingSegments| state. + // + // |moof_head_| is the offset of the start of the most recently parsed moof + // block. All byte offsets in sample information are relative to this offset, + // as mandated by the Media Source spec. + int64_t moof_head_; + // |mdat_tail_| is the stream offset of the end of the current 'mdat' box. + // Valid iff it is greater than the head of the queue. + int64_t mdat_tail_; + + nsAutoPtr moov_; + nsAutoPtr runs_; + + uint32_t audio_track_id_; + uint32_t video_track_id_; + + uint32_t audio_frameno; + uint32_t video_frameno; + + AudioDecoderConfig audio_config_; + VideoDecoderConfig video_config_; + + bool has_audio_; + bool has_sbr_; // NOTE: This is not initialized! + bool is_audio_track_encrypted_; + + bool has_video_; + bool is_video_track_encrypted_; + + bool can_seek_; +}; + +} // mp4_demuxer + +#endif // MEDIA_MP4_MP4DEMUXER_H diff --git a/content/media/fmp4/demuxer/track_run_iterator.cc b/content/media/fmp4/demuxer/track_run_iterator.cc new file mode 100644 index 000000000000..e0eaa523d3e6 --- /dev/null +++ b/content/media/fmp4/demuxer/track_run_iterator.cc @@ -0,0 +1,451 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/track_run_iterator.h" +#include "mp4_demuxer/basictypes.h" +#include "mp4_demuxer/Streams.h" + +#include +#include +#include + +using namespace std; + +namespace mp4_demuxer { + +static const uint32_t kSampleIsDifferenceSampleFlagMask = 0x10000; + +struct SampleInfo { + int size; + int duration; + int cts_offset; + bool is_keyframe; +}; + +struct TrackRunInfo { + uint32_t track_id; + std::vector samples; + int64_t timescale; + int64_t start_dts; + int64_t sample_start_offset; + + bool is_audio; + const AudioSampleEntry* audio_description; + const VideoSampleEntry* video_description; + + int64_t aux_info_start_offset; // Only valid if aux_info_total_size > 0. + int aux_info_default_size; + std::vector aux_info_sizes; // Populated if default_size == 0. + int aux_info_total_size; + + TrackRunInfo(); + ~TrackRunInfo(); +}; + +TrackRunInfo::TrackRunInfo() + : track_id(0), + timescale(-1), + start_dts(-1), + sample_start_offset(-1), + is_audio(false), + aux_info_start_offset(-1), + aux_info_default_size(-1), + aux_info_total_size(-1) { +} +TrackRunInfo::~TrackRunInfo() {} + +Microseconds TimeDeltaFromRational(int64_t numer, int64_t denom) { + DCHECK_LT((numer > 0 ? numer : -numer), + kint64max / MicrosecondsPerSecond); + return MicrosecondsPerSecond * numer / denom; +} + +TrackRunIterator::TrackRunIterator(const Movie* moov) + : moov_(moov), sample_offset_(0) { + CHECK(moov); +} + +TrackRunIterator::~TrackRunIterator() {} + +static void PopulateSampleInfo(const TrackExtends& trex, + const TrackFragmentHeader& tfhd, + const TrackFragmentRun& trun, + const int64_t edit_list_offset, + const uint32_t i, + SampleInfo* sample_info) { + if (i < trun.sample_sizes.size()) { + sample_info->size = trun.sample_sizes[i]; + } else if (tfhd.default_sample_size > 0) { + sample_info->size = tfhd.default_sample_size; + } else { + sample_info->size = trex.default_sample_size; + } + + if (i < trun.sample_durations.size()) { + sample_info->duration = trun.sample_durations[i]; + } else if (tfhd.default_sample_duration > 0) { + sample_info->duration = tfhd.default_sample_duration; + } else { + sample_info->duration = trex.default_sample_duration; + } + + if (i < trun.sample_composition_time_offsets.size()) { + sample_info->cts_offset = trun.sample_composition_time_offsets[i]; + } else { + sample_info->cts_offset = 0; + } + sample_info->cts_offset += edit_list_offset; + + uint32_t flags; + if (i < trun.sample_flags.size()) { + flags = trun.sample_flags[i]; + } else if (tfhd.has_default_sample_flags) { + flags = tfhd.default_sample_flags; + } else { + flags = trex.default_sample_flags; + } + sample_info->is_keyframe = !(flags & kSampleIsDifferenceSampleFlagMask); +} + +// In well-structured encrypted media, each track run will be immediately +// preceded by its auxiliary information; this is the only optimal storage +// pattern in terms of minimum number of bytes from a serial stream needed to +// begin playback. It also allows us to optimize caching on memory-constrained +// architectures, because we can cache the relatively small auxiliary +// information for an entire run and then discard data from the input stream, +// instead of retaining the entire 'mdat' box. +// +// We optimize for this situation (with no loss of generality) by sorting track +// runs during iteration in order of their first data offset (either sample data +// or auxiliary data). +class CompareMinTrackRunDataOffset { + public: + bool operator()(const TrackRunInfo& a, const TrackRunInfo& b) { + int64_t a_aux = a.aux_info_total_size ? a.aux_info_start_offset : kint64max; + int64_t b_aux = b.aux_info_total_size ? b.aux_info_start_offset : kint64max; + + int64_t a_lesser = std::min(a_aux, a.sample_start_offset); + int64_t a_greater = std::max(a_aux, a.sample_start_offset); + int64_t b_lesser = std::min(b_aux, b.sample_start_offset); + int64_t b_greater = std::max(b_aux, b.sample_start_offset); + + if (a_lesser == b_lesser) return a_greater < b_greater; + return a_lesser < b_lesser; + } +}; + +bool TrackRunIterator::Init(const MovieFragment& moof) { + runs_.clear(); + + for (size_t i = 0; i < moof.tracks.size(); i++) { + const TrackFragment& traf = moof.tracks[i]; + + const Track* trak = NULL; + for (size_t t = 0; t < moov_->tracks.size(); t++) { + if (moov_->tracks[t].header.track_id == traf.header.track_id) + trak = &moov_->tracks[t]; + } + RCHECK(trak); + + const TrackExtends* trex = NULL; + for (size_t t = 0; t < moov_->extends.tracks.size(); t++) { + if (moov_->extends.tracks[t].track_id == traf.header.track_id) + trex = &moov_->extends.tracks[t]; + } + RCHECK(trex); + + const SampleDescription& stsd = + trak->media.information.sample_table.description; + if (stsd.type != kAudio && stsd.type != kVideo) { + DMX_LOG("Skipping unhandled track type\n"); + continue; + } + size_t desc_idx = traf.header.sample_description_index; + if (!desc_idx) desc_idx = trex->default_sample_description_index; + RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file + desc_idx -= 1; + + // Process edit list to remove CTS offset introduced in the presence of + // B-frames (those that contain a single edit with a nonnegative media + // time). Other uses of edit lists are not supported, as they are + // both uncommon and better served by higher-level protocols. + int64_t edit_list_offset = 0; + const std::vector& edits = trak->edit.list.edits; + if (!edits.empty()) { + if (edits.size() > 1) + DMX_LOG("Multi-entry edit box detected; some components ignored.\n"); + + if (edits[0].media_time < 0) { + DMX_LOG("Empty edit list entry ignored.\n"); + } else { + edit_list_offset = -edits[0].media_time; + } + } + + int64_t run_start_dts = traf.decode_time.decode_time; + int sample_count_sum = 0; + for (size_t j = 0; j < traf.runs.size(); j++) { + const TrackFragmentRun& trun = traf.runs[j]; + TrackRunInfo tri; + tri.track_id = traf.header.track_id; + tri.timescale = trak->media.header.timescale; + tri.start_dts = run_start_dts; + tri.sample_start_offset = trun.data_offset; + + tri.is_audio = (stsd.type == kAudio); + if (tri.is_audio) { + RCHECK(!stsd.audio_entries.empty()); + if (desc_idx > stsd.audio_entries.size()) + desc_idx = 0; + tri.audio_description = &stsd.audio_entries[desc_idx]; + } else { + RCHECK(!stsd.video_entries.empty()); + if (desc_idx > stsd.video_entries.size()) + desc_idx = 0; + tri.video_description = &stsd.video_entries[desc_idx]; + } + + // Collect information from the auxiliary_offset entry with the same index + // in the 'saiz' container as the current run's index in the 'trun' + // container, if it is present. + if (traf.auxiliary_offset.offsets.size() > j) { + // There should be an auxiliary info entry corresponding to each sample + // in the auxiliary offset entry's corresponding track run. + RCHECK(traf.auxiliary_size.sample_count >= + sample_count_sum + trun.sample_count); + tri.aux_info_start_offset = traf.auxiliary_offset.offsets[j]; + tri.aux_info_default_size = + traf.auxiliary_size.default_sample_info_size; + if (tri.aux_info_default_size == 0) { + const std::vector& sizes = + traf.auxiliary_size.sample_info_sizes; + tri.aux_info_sizes.insert(tri.aux_info_sizes.begin(), + sizes.begin() + sample_count_sum, + sizes.begin() + sample_count_sum + trun.sample_count); + } + + // If the default info size is positive, find the total size of the aux + // info block from it, otherwise sum over the individual sizes of each + // aux info entry in the aux_offset entry. + if (tri.aux_info_default_size) { + tri.aux_info_total_size = + tri.aux_info_default_size * trun.sample_count; + } else { + tri.aux_info_total_size = 0; + for (size_t k = 0; k < trun.sample_count; k++) { + tri.aux_info_total_size += tri.aux_info_sizes[k]; + } + } + } else { + tri.aux_info_start_offset = -1; + tri.aux_info_total_size = 0; + } + + tri.samples.resize(trun.sample_count); + for (size_t k = 0; k < trun.sample_count; k++) { + PopulateSampleInfo(*trex, traf.header, trun, edit_list_offset, + k, &tri.samples[k]); + run_start_dts += tri.samples[k].duration; + } + runs_.push_back(tri); + sample_count_sum += trun.sample_count; + } + } + + std::sort(runs_.begin(), runs_.end(), CompareMinTrackRunDataOffset()); + run_itr_ = runs_.begin(); + ResetRun(); + return true; +} + +void TrackRunIterator::AdvanceRun() { + ++run_itr_; + ResetRun(); +} + +void TrackRunIterator::ResetRun() { + if (!IsRunValid()) return; + sample_dts_ = run_itr_->start_dts; + sample_offset_ = run_itr_->sample_start_offset; + sample_itr_ = run_itr_->samples.begin(); + cenc_info_.clear(); +} + +void TrackRunIterator::AdvanceSample() { + DCHECK(IsSampleValid()); + sample_dts_ += sample_itr_->duration; + sample_offset_ += sample_itr_->size; + ++sample_itr_; +} + +// This implementation only indicates a need for caching if CENC auxiliary +// info is available in the stream. +bool TrackRunIterator::AuxInfoNeedsToBeCached() { + DCHECK(IsRunValid()); + return is_encrypted() && aux_info_size() > 0 && cenc_info_.size() == 0; +} + +// This implementation currently only caches CENC auxiliary info. +bool TrackRunIterator::CacheAuxInfo(Stream* stream, int64_t moof_offset) { + RCHECK(AuxInfoNeedsToBeCached()); + + int64_t offset = aux_info_offset() + moof_offset; + if (stream->Length() - offset < aux_info_size()) { + return false; + } + + assert(run_itr_ == runs_.begin()); + cenc_info_.resize(run_itr_->samples.size()); + int64_t pos = 0; + for (size_t i = 0; i < run_itr_->samples.size(); i++) { + int info_size = run_itr_->aux_info_default_size; + if (!info_size) + info_size = run_itr_->aux_info_sizes[i]; + + StreamReader reader(stream, offset + pos, info_size); + RCHECK(cenc_info_[i].Parse(track_encryption().default_iv_size, &reader)); + pos += info_size; + } + + return true; +} + +bool TrackRunIterator::IsRunValid() const { + return run_itr_ != runs_.end(); +} + +bool TrackRunIterator::IsSampleValid() const { + return IsRunValid() && (sample_itr_ != run_itr_->samples.end()); +} + +// Because tracks are in sorted order and auxiliary information is cached when +// returning samples, it is guaranteed that no data will be required before the +// lesser of the minimum data offset of this track and the next in sequence. +// (The stronger condition - that no data is required before the minimum data +// offset of this track alone - is not guaranteed, because the BMFF spec does +// not have any inter-run ordering restrictions.) +int64_t TrackRunIterator::GetMaxClearOffset() { + int64_t offset = kint64max; + + if (IsSampleValid()) { + offset = std::min(offset, sample_offset_); + if (AuxInfoNeedsToBeCached()) + offset = std::min(offset, aux_info_offset()); + } + if (run_itr_ != runs_.end()) { + std::vector::const_iterator next_run = run_itr_ + 1; + if (next_run != runs_.end()) { + offset = std::min(offset, next_run->sample_start_offset); + if (next_run->aux_info_total_size) + offset = std::min(offset, next_run->aux_info_start_offset); + } + } + if (offset == kint64max) return 0; + return offset; +} + +Microseconds TrackRunIterator::GetMinDecodeTimestamp() { + Microseconds dts = -1; + for (size_t i = 0; i < runs_.size(); i++) { + dts = std::min(dts, MicrosecondsFromRational(runs_[i].start_dts, + runs_[i].timescale)); + } + return dts; +} + +uint32_t TrackRunIterator::track_id() const { + DCHECK(IsRunValid()); + return run_itr_->track_id; +} + +bool TrackRunIterator::is_encrypted() const { + DCHECK(IsRunValid()); + return track_encryption().is_encrypted; +} + +int64_t TrackRunIterator::aux_info_offset() const { + return run_itr_->aux_info_start_offset; +} + +int TrackRunIterator::aux_info_size() const { + return run_itr_->aux_info_total_size; +} + +bool TrackRunIterator::is_audio() const { + DCHECK(IsRunValid()); + return run_itr_->is_audio; +} + +const AudioSampleEntry& TrackRunIterator::audio_description() const { + DCHECK(is_audio()); + DCHECK(run_itr_->audio_description); + return *run_itr_->audio_description; +} + +const VideoSampleEntry& TrackRunIterator::video_description() const { + DCHECK(!is_audio()); + DCHECK(run_itr_->video_description); + return *run_itr_->video_description; +} + +int64_t TrackRunIterator::sample_offset() const { + DCHECK(IsSampleValid()); + return sample_offset_; +} + +int TrackRunIterator::sample_size() const { + DCHECK(IsSampleValid()); + return sample_itr_->size; +} + +Microseconds TrackRunIterator::dts() const { + DCHECK(IsSampleValid()); + return MicrosecondsFromRational(sample_dts_, run_itr_->timescale); +} + +Microseconds TrackRunIterator::cts() const { + DCHECK(IsSampleValid()); + return MicrosecondsFromRational(sample_dts_ + sample_itr_->cts_offset, + run_itr_->timescale); +} + +Microseconds TrackRunIterator::duration() const { + DCHECK(IsSampleValid()); + return MicrosecondsFromRational(sample_itr_->duration, run_itr_->timescale); +} + +bool TrackRunIterator::is_keyframe() const { + DCHECK(IsSampleValid()); + return sample_itr_->is_keyframe; +} + +const TrackEncryption& TrackRunIterator::track_encryption() const { + if (is_audio()) + return audio_description().sinf.info.track_encryption; + return video_description().sinf.info.track_encryption; +} + +void TrackRunIterator::GetDecryptConfig(nsAutoPtr& config) { + size_t sample_idx = sample_itr_ - run_itr_->samples.begin(); + DCHECK(sample_idx < cenc_info_.size()); + const FrameCENCInfo& cenc_info = cenc_info_[sample_idx]; + DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached()); + + if (!cenc_info.subsamples.empty() && + (cenc_info.GetTotalSizeOfSubsamples() != + static_cast(sample_size()))) { + DMX_LOG("Incorrect CENC subsample size.\n"); + return; + } + + const std::vector& kid = track_encryption().default_kid; + config = new DecryptConfig( + std::string(reinterpret_cast(&kid[0]), kid.size()), + std::string(reinterpret_cast(cenc_info.iv), + arraysize(cenc_info.iv)), + 0, // No offset to start of media data in MP4 using CENC. + cenc_info.subsamples); +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/track_run_iterator.h b/content/media/fmp4/demuxer/track_run_iterator.h new file mode 100644 index 000000000000..2d7f0fdeca12 --- /dev/null +++ b/content/media/fmp4/demuxer/track_run_iterator.h @@ -0,0 +1,107 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_TRACK_RUN_ITERATOR_H_ +#define MEDIA_MP4_TRACK_RUN_ITERATOR_H_ + +#include +#include + +#include "mp4_demuxer/box_definitions.h" +#include "mp4_demuxer/cenc.h" +#include "nsAutoPtr.h" + +namespace mp4_demuxer { + +class DecryptConfig; + +Microseconds MicrosecondsFromRational(int64_t numer, int64_t denom); + +struct SampleInfo; +struct TrackRunInfo; + +class TrackRunIterator { + public: + // Create a new TrackRunIterator. A reference to |moov| will be retained for + // the lifetime of this object. + TrackRunIterator(const Movie* moov); + ~TrackRunIterator(); + + void Reset(); + + // Sets up the iterator to handle all the runs from the current fragment. + bool Init(const MovieFragment& moof); + + // Returns true if the properties of the current run or sample are valid. + bool IsRunValid() const; + bool IsSampleValid() const; + + // Advance the properties to refer to the next run or sample. Requires that + // the current sample be valid. + void AdvanceRun(); + void AdvanceSample(); + + // Returns true if this track run has auxiliary information and has not yet + // been cached. Only valid if IsRunValid(). + bool AuxInfoNeedsToBeCached(); + + // Caches the CENC data from the given buffer. |buf| must be a buffer starting + // at the offset given by cenc_offset(), with a |size| of at least + // cenc_size(). Returns true on success, false on error. + //bool CacheAuxInfo(const uint8_t* buf, int size); + bool CacheAuxInfo(Stream* stream, int64_t moof_offset); + + // Returns the maximum buffer location at which no data earlier in the stream + // will be required in order to read the current or any subsequent sample. You + // may clear all data up to this offset before reading the current sample + // safely. Result is in the same units as offset() (for Media Source this is + // in bytes past the the head of the MOOF box). + int64_t GetMaxClearOffset(); + + // Returns the minimum timestamp (or kInfiniteDuration if no runs present). + Microseconds GetMinDecodeTimestamp(); + + // Property of the current run. Only valid if IsRunValid(). + uint32_t track_id() const; + int64_t aux_info_offset() const; + int aux_info_size() const; + bool is_encrypted() const; + bool is_audio() const; + // Only one is valid, based on the value of is_audio(). + const AudioSampleEntry& audio_description() const; + const VideoSampleEntry& video_description() const; + + // Properties of the current sample. Only valid if IsSampleValid(). + int64_t sample_offset() const; + int sample_size() const; + Microseconds dts() const; + Microseconds cts() const; + Microseconds duration() const; + bool is_keyframe() const; + + // Only call when is_encrypted() is true and AuxInfoNeedsToBeCached() is + // false. Result is owned by caller. + void GetDecryptConfig(nsAutoPtr& config); + + private: + void ResetRun(); + const TrackEncryption& track_encryption() const; + + const Movie* moov_; + + std::vector runs_; + std::vector::const_iterator run_itr_; + std::vector::const_iterator sample_itr_; + + std::vector cenc_info_; + + int64_t sample_dts_; + int64_t sample_offset_; + + DISALLOW_COPY_AND_ASSIGN(TrackRunIterator); +}; + +} // namespace mp4_demuxer + +#endif // MEDIA_MP4_TRACK_RUN_ITERATOR_H_ diff --git a/content/media/fmp4/demuxer/video_decoder_config.cc b/content/media/fmp4/demuxer/video_decoder_config.cc new file mode 100644 index 000000000000..3abf12ac1ac0 --- /dev/null +++ b/content/media/fmp4/demuxer/video_decoder_config.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/video_decoder_config.h" + +#include + +namespace mp4_demuxer { + +VideoDecoderConfig::VideoDecoderConfig() + : codec_(kUnknownVideoCodec), + profile_(VIDEO_CODEC_PROFILE_UNKNOWN), + format_(VideoFrameFormat::INVALID), + is_encrypted_(false) { +} + +VideoDecoderConfig::VideoDecoderConfig(VideoCodec codec, + VideoCodecProfile profile, + VideoFrameFormat format, + const IntSize& coded_size, + const IntRect& visible_rect, + const IntSize& natural_size, + const uint8_t* extra_data, + size_t extra_data_size, + bool is_encrypted) { + Initialize(codec, profile, format, coded_size, visible_rect, natural_size, + extra_data, extra_data_size, is_encrypted, true); +} + +VideoDecoderConfig::~VideoDecoderConfig() {} + +// Some videos just want to watch the world burn, with a height of 0; cap the +// "infinite" aspect ratio resulting. +static const int kInfiniteRatio = 99999; + +// Common aspect ratios (multiplied by 100 and truncated) used for histogramming +// video sizes. These were taken on 20111103 from +// http://wikipedia.org/wiki/Aspect_ratio_(image)#Previous_and_currently_used_aspect_ratios +static const int kCommonAspectRatios100[] = { + 100, 115, 133, 137, 143, 150, 155, 160, 166, 175, 177, 185, 200, 210, 220, + 221, 235, 237, 240, 255, 259, 266, 276, 293, 400, 1200, kInfiniteRatio, +}; + +void VideoDecoderConfig::Initialize(VideoCodec codec, + VideoCodecProfile profile, + VideoFrameFormat format, + const IntSize& coded_size, + const IntRect& visible_rect, + const IntSize& natural_size, + const uint8_t* extra_data, + size_t extra_data_size, + bool is_encrypted, + bool record_stats) { + CHECK((extra_data_size != 0) == (extra_data != NULL)); + + codec_ = codec; + profile_ = profile; + format_ = format; + coded_size_ = coded_size; + visible_rect_ = visible_rect; + natural_size_ = natural_size; + extra_data_.assign(extra_data, extra_data + extra_data_size); + is_encrypted_ = is_encrypted; +} + +bool VideoDecoderConfig::IsValidConfig() const { + return codec_ != kUnknownVideoCodec && + natural_size_.width() > 0 && + natural_size_.height() > 0 && + + // Copied from: + // VideoFrame::IsValidConfig(format_, coded_size_, visible_rect_, natural_size_) + format_ != VideoFrameFormat::INVALID && + !coded_size_.IsEmpty() && + coded_size_.GetArea() <= kMaxCanvas && + coded_size_.width() <= kMaxDimension && + coded_size_.height() <= kMaxDimension && + !visible_rect_.IsEmpty() && + visible_rect_.x() >= 0 && visible_rect_.y() >= 0 && + visible_rect_.right() <= coded_size_.width() && + visible_rect_.bottom() <= coded_size_.height() && + !natural_size_.IsEmpty() && + natural_size_.GetArea() <= kMaxCanvas && + natural_size_.width() <= kMaxDimension && + natural_size_.height() <= kMaxDimension; +} + +bool VideoDecoderConfig::Matches(const VideoDecoderConfig& config) const { + return ((codec() == config.codec()) && + (format() == config.format()) && + (profile() == config.profile()) && + (coded_size() == config.coded_size()) && + (visible_rect() == config.visible_rect()) && + (natural_size() == config.natural_size()) && + (extra_data_size() == config.extra_data_size()) && + (!extra_data() || !memcmp(extra_data(), config.extra_data(), + extra_data_size())) && + (is_encrypted() == config.is_encrypted())); +} + +std::string VideoDecoderConfig::AsHumanReadableString() const { + std::ostringstream s; + s << "codec: " << codec() + << " format: " << format() + << " profile: " << profile() + << " coded size: [" << coded_size().width() + << "," << coded_size().height() << "]" + << " visible rect: [" << visible_rect().x() + << "," << visible_rect().y() + << "," << visible_rect().width() + << "," << visible_rect().height() << "]" + << " natural size: [" << natural_size().width() + << "," << natural_size().height() << "]" + << " has extra data? " << (extra_data() ? "true" : "false") + << " encrypted? " << (is_encrypted() ? "true" : "false"); + return s.str(); +} + +VideoCodec VideoDecoderConfig::codec() const { + return codec_; +} + +VideoCodecProfile VideoDecoderConfig::profile() const { + return profile_; +} + +VideoFrameFormat VideoDecoderConfig::format() const { + return format_; +} + +IntSize VideoDecoderConfig::coded_size() const { + return coded_size_; +} + +IntRect VideoDecoderConfig::visible_rect() const { + return visible_rect_; +} + +IntSize VideoDecoderConfig::natural_size() const { + return natural_size_; +} + +const uint8_t* VideoDecoderConfig::extra_data() const { + if (extra_data_.empty()) + return NULL; + return &extra_data_[0]; +} + +size_t VideoDecoderConfig::extra_data_size() const { + return extra_data_.size(); +} + +bool VideoDecoderConfig::is_encrypted() const { + return is_encrypted_; +} + +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/video_decoder_config.h b/content/media/fmp4/demuxer/video_decoder_config.h new file mode 100644 index 000000000000..0751f9250104 --- /dev/null +++ b/content/media/fmp4/demuxer/video_decoder_config.h @@ -0,0 +1,171 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_VIDEO_DECODER_CONFIG_H_ +#define MEDIA_BASE_VIDEO_DECODER_CONFIG_H_ + +#include +#include + +#include "mp4_demuxer/basictypes.h" + +namespace mp4_demuxer { + +enum VideoCodec { + // These values are histogrammed over time; do not change their ordinal + // values. When deleting a codec replace it with a dummy value; when adding a + // codec, do so at the bottom (and update kVideoCodecMax). + kUnknownVideoCodec = 0, + kCodecH264, + kCodecVC1, + kCodecMPEG2, + kCodecMPEG4, + kCodecTheora, + kCodecVP8, + kCodecVP9, + // DO NOT ADD RANDOM VIDEO CODECS! + // + // The only acceptable time to add a new codec is if there is production code + // that uses said codec in the same CL. + + kVideoCodecMax = kCodecVP9 // Must equal the last "real" codec above. +}; + +// Video stream profile. This *must* match PP_VideoDecoder_Profile. +// (enforced in webkit/plugins/ppapi/ppb_video_decoder_impl.cc) +enum VideoCodecProfile { + // Keep the values in this enum unique, as they imply format (h.264 vs. VP8, + // for example), and keep the values for a particular format grouped + // together for clarity. + VIDEO_CODEC_PROFILE_UNKNOWN = -1, + H264PROFILE_MIN = 0, + H264PROFILE_BASELINE = H264PROFILE_MIN, + H264PROFILE_MAIN = 1, + H264PROFILE_EXTENDED = 2, + H264PROFILE_HIGH = 3, + H264PROFILE_HIGH10PROFILE = 4, + H264PROFILE_HIGH422PROFILE = 5, + H264PROFILE_HIGH444PREDICTIVEPROFILE = 6, + H264PROFILE_SCALABLEBASELINE = 7, + H264PROFILE_SCALABLEHIGH = 8, + H264PROFILE_STEREOHIGH = 9, + H264PROFILE_MULTIVIEWHIGH = 10, + H264PROFILE_MAX = H264PROFILE_MULTIVIEWHIGH, + VP8PROFILE_MIN = 11, + VP8PROFILE_MAIN = VP8PROFILE_MIN, + VP8PROFILE_MAX = VP8PROFILE_MAIN, + VP9PROFILE_MIN = 12, + VP9PROFILE_MAIN = VP9PROFILE_MIN, + VP9PROFILE_MAX = VP9PROFILE_MAIN, + VIDEO_CODEC_PROFILE_MAX = VP9PROFILE_MAX, +}; + +// Surface formats roughly based on FOURCC labels, see: +// http://www.fourcc.org/rgb.php +// http://www.fourcc.org/yuv.php +enum VideoFrameFormat { // VideoFrame::Format + INVALID = 0, // Invalid format value. Used for error reporting. + RGB32 = 4, // 32bpp RGB packed with extra byte 8:8:8 + YV12 = 6, // 12bpp YVU planar 1x1 Y, 2x2 VU samples + YV16 = 7, // 16bpp YVU planar 1x1 Y, 2x1 VU samples + EMPTY = 9, // An empty frame. + I420 = 11, // 12bpp YVU planar 1x1 Y, 2x2 UV samples. + NATIVE_TEXTURE = 12, // Native texture. Pixel-format agnostic. +#if defined(GOOGLE_TV) + HOLE = 13, // Hole frame. +#endif + YV12A = 14, // 20bpp YUVA planar 1x1 Y, 2x2 VU, 1x1 A samples. +}; + +class VideoDecoderConfig { + public: + // Constructs an uninitialized object. Clients should call Initialize() with + // appropriate values before using. + VideoDecoderConfig(); + + // Constructs an initialized object. It is acceptable to pass in NULL for + // |extra_data|, otherwise the memory is copied. + VideoDecoderConfig(VideoCodec codec, + VideoCodecProfile profile, + VideoFrameFormat format, + const IntSize& coded_size, + const IntRect& visible_rect, + const IntSize& natural_size, + const uint8_t* extra_data, size_t extra_data_size, + bool is_encrypted); + + ~VideoDecoderConfig(); + + // Resets the internal state of this object. + void Initialize(VideoCodec codec, + VideoCodecProfile profile, + VideoFrameFormat format, + const IntSize& coded_size, + const IntRect& visible_rect, + const IntSize& natural_size, + const uint8_t* extra_data, size_t extra_data_size, + bool is_encrypted, + bool record_stats); + + // Returns true if this object has appropriate configuration values, false + // otherwise. + bool IsValidConfig() const; + + // Returns true if all fields in |config| match this config. + // Note: The contents of |extra_data_| are compared not the raw pointers. + bool Matches(const VideoDecoderConfig& config) const; + + // Returns a human-readable string describing |*this|. For debugging & test + // output only. + std::string AsHumanReadableString() const; + + VideoCodec codec() const; + VideoCodecProfile profile() const; + + // Video format used to determine YUV buffer sizes. + VideoFrameFormat format() const; + + // Width and height of video frame immediately post-decode. Not all pixels + // in this region are valid. + IntSize coded_size() const; + + // Region of |coded_size_| that is visible. + IntRect visible_rect() const; + + // Final visible width and height of a video frame with aspect ratio taken + // into account. + IntSize natural_size() const; + + // Optional byte data required to initialize video decoders, such as H.264 + // AAVC data. + const uint8_t* extra_data() const; + size_t extra_data_size() const; + + // Whether the video stream is potentially encrypted. + // Note that in a potentially encrypted video stream, individual buffers + // can be encrypted or not encrypted. + bool is_encrypted() const; + + private: + VideoCodec codec_; + VideoCodecProfile profile_; + + VideoFrameFormat format_; + + IntSize coded_size_; + IntRect visible_rect_; + IntSize natural_size_; + + std::vector extra_data_; + + bool is_encrypted_; + + // Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler + // generated copy constructor and assignment operator. Since the extra data is + // typically small, the performance impact is minimal. +}; + +} // namespace mp4_demuxer + +#endif // MEDIA_BASE_VIDEO_DECODER_CONFIG_H_ diff --git a/content/media/fmp4/demuxer/video_util.cc b/content/media/fmp4/demuxer/video_util.cc new file mode 100644 index 000000000000..15eae7f0e72b --- /dev/null +++ b/content/media/fmp4/demuxer/video_util.cc @@ -0,0 +1,299 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mp4_demuxer/video_util.h" + +#include + +namespace mp4_demuxer { + +IntSize GetNaturalSize(const IntSize& visible_size, + int aspect_ratio_numerator, + int aspect_ratio_denominator) { + if (aspect_ratio_denominator == 0 || + aspect_ratio_numerator < 0 || + aspect_ratio_denominator < 0) + return IntSize(); + + double aspect_ratio = aspect_ratio_numerator / + static_cast(aspect_ratio_denominator); + + int width = floor(visible_size.width() * aspect_ratio + 0.5); + int height = visible_size.height(); + + // An even width makes things easier for YV12 and appears to be the behavior + // expected by WebKit layout tests. + return IntSize(width & ~1, height); +} + +/* +void CopyPlane(size_t plane, const uint8_t* source, int stride, int rows, + VideoFrame* frame) { + uint8_t* dest = frame->data(plane); + int dest_stride = frame->stride(plane); + + // Clamp in case source frame has smaller stride. + int bytes_to_copy_per_row = std::min(frame->row_bytes(plane), stride); + + // Clamp in case source frame has smaller height. + int rows_to_copy = std::min(frame->rows(plane), rows); + + // Copy! + for (int row = 0; row < rows_to_copy; ++row) { + memcpy(dest, source, bytes_to_copy_per_row); + source += stride; + dest += dest_stride; + } +} + +void CopyYPlane(const uint8_t* source, int stride, int rows, VideoFrame* frame) { + CopyPlane(VideoFrame::kYPlane, source, stride, rows, frame); +} + +void CopyUPlane(const uint8_t* source, int stride, int rows, VideoFrame* frame) { + CopyPlane(VideoFrame::kUPlane, source, stride, rows, frame); +} + +void CopyVPlane(const uint8_t* source, int stride, int rows, VideoFrame* frame) { + CopyPlane(VideoFrame::kVPlane, source, stride, rows, frame); +} + +void CopyAPlane(const uint8_t* source, int stride, int rows, VideoFrame* frame) { + CopyPlane(VideoFrame::kAPlane, source, stride, rows, frame); +} + +void MakeOpaqueAPlane(int stride, int rows, VideoFrame* frame) { + int rows_to_clear = std::min(frame->rows(VideoFrame::kAPlane), rows); + memset(frame->data(VideoFrame::kAPlane), 255, + frame->stride(VideoFrame::kAPlane) * rows_to_clear); +} + +void FillYUV(VideoFrame* frame, uint8_t y, uint8_t u, uint8_t v) { + // Fill the Y plane. + uint8_t* y_plane = frame->data(VideoFrame::kYPlane); + int y_rows = frame->rows(VideoFrame::kYPlane); + int y_row_bytes = frame->row_bytes(VideoFrame::kYPlane); + for (int i = 0; i < y_rows; ++i) { + memset(y_plane, y, y_row_bytes); + y_plane += frame->stride(VideoFrame::kYPlane); + } + + // Fill the U and V planes. + uint8_t* u_plane = frame->data(VideoFrame::kUPlane); + uint8_t* v_plane = frame->data(VideoFrame::kVPlane); + int uv_rows = frame->rows(VideoFrame::kUPlane); + int u_row_bytes = frame->row_bytes(VideoFrame::kUPlane); + int v_row_bytes = frame->row_bytes(VideoFrame::kVPlane); + for (int i = 0; i < uv_rows; ++i) { + memset(u_plane, u, u_row_bytes); + memset(v_plane, v, v_row_bytes); + u_plane += frame->stride(VideoFrame::kUPlane); + v_plane += frame->stride(VideoFrame::kVPlane); + } +} + +static void LetterboxPlane(VideoFrame* frame, + int plane, + const gfx::Rect& view_area, + uint8_t fill_byte) { + uint8_t* ptr = frame->data(plane); + const int rows = frame->rows(plane); + const int row_bytes = frame->row_bytes(plane); + const int stride = frame->stride(plane); + + CHECK_GE(stride, row_bytes); + CHECK_GE(view_area.x(), 0); + CHECK_GE(view_area.y(), 0); + CHECK_LE(view_area.right(), row_bytes); + CHECK_LE(view_area.bottom(), rows); + + int y = 0; + for (; y < view_area.y(); y++) { + memset(ptr, fill_byte, row_bytes); + ptr += stride; + } + if (view_area.width() < row_bytes) { + for (; y < view_area.bottom(); y++) { + if (view_area.x() > 0) { + memset(ptr, fill_byte, view_area.x()); + } + if (view_area.right() < row_bytes) { + memset(ptr + view_area.right(), + fill_byte, + row_bytes - view_area.right()); + } + ptr += stride; + } + } else { + y += view_area.height(); + ptr += stride * view_area.height(); + } + for (; y < rows; y++) { + memset(ptr, fill_byte, row_bytes); + ptr += stride; + } +} + +void LetterboxYUV(VideoFrame* frame, const gfx::Rect& view_area) { + DCHECK(!(view_area.x() & 1)); + DCHECK(!(view_area.y() & 1)); + DCHECK(!(view_area.width() & 1)); + DCHECK(!(view_area.height() & 1)); + DCHECK_EQ(frame->format(), VideoFrame::YV12); + LetterboxPlane(frame, VideoFrame::kYPlane, view_area, 0x00); + gfx::Rect half_view_area(view_area.x() / 2, + view_area.y() / 2, + view_area.width() / 2, + view_area.height() / 2); + LetterboxPlane(frame, VideoFrame::kUPlane, half_view_area, 0x80); + LetterboxPlane(frame, VideoFrame::kVPlane, half_view_area, 0x80); +} + +void RotatePlaneByPixels( + const uint8_t* src, + uint8_t* dest, + int width, + int height, + int rotation, // Clockwise. + bool flip_vert, + bool flip_horiz) { + DCHECK((width > 0) && (height > 0) && + ((width & 1) == 0) && ((height & 1) == 0) && + (rotation >= 0) && (rotation < 360) && (rotation % 90 == 0)); + + // Consolidate cases. Only 0 and 90 are left. + if (rotation == 180 || rotation == 270) { + rotation -= 180; + flip_vert = !flip_vert; + flip_horiz = !flip_horiz; + } + + int num_rows = height; + int num_cols = width; + int src_stride = width; + // During pixel copying, the corresponding incremental of dest pointer + // when src pointer moves to next row. + int dest_row_step = width; + // During pixel copying, the corresponding incremental of dest pointer + // when src pointer moves to next column. + int dest_col_step = 1; + + if (rotation == 0) { + if (flip_horiz) { + // Use pixel copying. + dest_col_step = -1; + if (flip_vert) { + // Rotation 180. + dest_row_step = -width; + dest += height * width - 1; + } else { + dest += width - 1; + } + } else { + if (flip_vert) { + // Fast copy by rows. + dest += width * (height - 1); + for (int row = 0; row < height; ++row) { + memcpy(dest, src, width); + src += width; + dest -= width; + } + } else { + memcpy(dest, src, width * height); + } + return; + } + } else if (rotation == 90) { + int offset; + if (width > height) { + offset = (width - height) / 2; + src += offset; + num_rows = num_cols = height; + } else { + offset = (height - width) / 2; + src += width * offset; + num_rows = num_cols = width; + } + + dest_col_step = (flip_vert ? -width : width); + dest_row_step = (flip_horiz ? 1 : -1); + if (flip_horiz) { + if (flip_vert) { + dest += (width > height ? width * (height - 1) + offset : + width * (height - offset - 1)); + } else { + dest += (width > height ? offset : width * offset); + } + } else { + if (flip_vert) { + dest += (width > height ? width * height - offset - 1 : + width * (height - offset) - 1); + } else { + dest += (width > height ? width - offset - 1 : + width * (offset + 1) - 1); + } + } + } else { + NOTREACHED(); + } + + // Copy pixels. + for (int row = 0; row < num_rows; ++row) { + const uint8_t* src_ptr = src; + uint8_t* dest_ptr = dest; + for (int col = 0; col < num_cols; ++col) { + *dest_ptr = *src_ptr++; + dest_ptr += dest_col_step; + } + src += src_stride; + dest += dest_row_step; + } +} + +gfx::Rect ComputeLetterboxRegion(const gfx::Rect& bounds, + const IntSize& content) { + int64_t x = static_cast(content.width()) * bounds.height(); + int64_t y = static_cast(content.height()) * bounds.width(); + + IntSize letterbox(bounds.width(), bounds.height()); + if (y < x) + letterbox.set_height(static_cast(y / content.width())); + else + letterbox.set_width(static_cast(x / content.height())); + gfx::Rect result = bounds; + result.ClampToCenteredSize(letterbox); + return result; +} + +void CopyRGBToVideoFrame(const uint8_t* source, + int stride, + const gfx::Rect& region_in_frame, + VideoFrame* frame) { + const int kY = VideoFrame::kYPlane; + const int kU = VideoFrame::kUPlane; + const int kV = VideoFrame::kVPlane; + CHECK_EQ(frame->stride(kU), frame->stride(kV)); + const int uv_stride = frame->stride(kU); + + if (region_in_frame != gfx::Rect(frame->coded_size())) { + LetterboxYUV(frame, region_in_frame); + } + + const int y_offset = region_in_frame.x() + + (region_in_frame.y() * frame->stride(kY)); + const int uv_offset = region_in_frame.x() / 2 + + (region_in_frame.y() / 2 * uv_stride); + + ConvertRGB32ToYUV(source, + frame->data(kY) + y_offset, + frame->data(kU) + uv_offset, + frame->data(kV) + uv_offset, + region_in_frame.width(), + region_in_frame.height(), + stride, + frame->stride(kY), + uv_stride); +} +*/ +} // namespace mp4_demuxer diff --git a/content/media/fmp4/demuxer/video_util.h b/content/media/fmp4/demuxer/video_util.h new file mode 100644 index 000000000000..2c568788ee80 --- /dev/null +++ b/content/media/fmp4/demuxer/video_util.h @@ -0,0 +1,86 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_VIDEO_UTIL_H_ +#define MEDIA_BASE_VIDEO_UTIL_H_ + +#include "mp4_demuxer/basictypes.h" + +namespace mp4_demuxer { + +class VideoFrame; + +// Computes the size of |visible_size| for a given aspect ratio. +IntSize GetNaturalSize(const IntSize& visible_size, + int aspect_ratio_numerator, + int aspect_ratio_denominator); +/* +// Copies a plane of YUV(A) source into a VideoFrame object, taking into account +// source and destinations dimensions. +// +// NOTE: rows is *not* the same as height! +void CopyYPlane(const uint8_t* source, int stride, int rows, + VideoFrame* frame); +void CopyUPlane(const uint8_t* source, int stride, int rows, + VideoFrame* frame); +void CopyVPlane(const uint8_t* source, int stride, int rows, + VideoFrame* frame); +void CopyAPlane(const uint8_t* source, int stride, int rows, + VideoFrame* frame); + +// Sets alpha plane values to be completely opaque (all 255's). +void MakeOpaqueAPlane(int stride, int rows, VideoFrame* frame); + +// |plane| is one of VideoFrame::kYPlane, VideoFrame::kUPlane, +// VideoFrame::kVPlane or VideoFrame::kAPlane +void CopyPlane(size_t plane, const uint8_t* source, int stride, + int rows, VideoFrame* frame); + + +// Fills |frame| containing YUV data to the given color values. +void FillYUV(VideoFrame* frame, uint8_t y, uint8_t u, uint8_t v); + +// Creates a border in |frame| such that all pixels outside of +// |view_area| are black. The size and position of |view_area| +// must be even to align correctly with the color planes. +// Only YV12 format video frames are currently supported. +void LetterboxYUV(VideoFrame* frame, + const gfx::Rect& view_area); + +// Rotates |src| plane by |rotation| degree with possible flipping vertically +// and horizontally. +// |rotation| is limited to {0, 90, 180, 270}. +// |width| and |height| are expected to be even numbers. +// Both |src| and |dest| planes are packed and have same |width| and |height|. +// When |width| != |height| and rotated by 90/270, only the maximum square +// portion located in the center is rotated. For example, for width=640 and +// height=480, the rotated area is 480x480 located from row 0 through 479 and +// from column 80 through 559. The leftmost and rightmost 80 columns are +// ignored for both |src| and |dest|. +// The caller is responsible for blanking out the margin area. +void RotatePlaneByPixels( + const uint8_t* src, + uint8_t* dest, + int width, + int height, + int rotation, // Clockwise. + bool flip_vert, + bool flip_horiz); + +// Return the largest centered rectangle with the same aspect ratio of |content| +// that fits entirely inside of |bounds|. +gfx::Rect ComputeLetterboxRegion(const gfx::Rect& bounds, + const IntSize& content); + +// Copy an RGB bitmap into the specified |region_in_frame| of a YUV video frame. +// Fills the regions outside |region_in_frame| with black. +void CopyRGBToVideoFrame(const uint8_t* source, + int stride, + const gfx::Rect& region_in_frame, + VideoFrame* frame); +*/ + +} // namespace mp4_demuxer + +#endif // MEDIA_BASE_VIDEO_UTIL_H_ diff --git a/content/media/fmp4/moz.build b/content/media/fmp4/moz.build new file mode 100644 index 000000000000..01394cb1c9eb --- /dev/null +++ b/content/media/fmp4/moz.build @@ -0,0 +1,64 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'MP4Decoder.h', + 'MP4Reader.h', + 'PlatformDecoderModule.h', + 'wmf/MFTDecoder.h', + 'wmf/WMFAudioDecoder.h', + 'wmf/WMFDecoderModule.h', + 'wmf/WMFVideoDecoder.h', +] + +EXPORTS.mp4_demuxer += [ + 'demuxer/aac.h', + 'demuxer/audio_decoder_config.h', + 'demuxer/avc.h', + 'demuxer/basictypes.h', + 'demuxer/bit_reader.h', + 'demuxer/box_definitions.h', + 'demuxer/box_reader.h', + 'demuxer/cenc.h', + 'demuxer/channel_layout.h', + 'demuxer/decrypt_config.h', + 'demuxer/es_descriptor.h', + 'demuxer/fourccs.h', + 'demuxer/mp4_demuxer.h', + 'demuxer/Streams.h', + 'demuxer/track_run_iterator.h', + 'demuxer/video_decoder_config.h', + 'demuxer/video_util.h', +] + +SOURCES += [ + 'BlankDecoderModule.cpp', + 'demuxer/aac.cc', + 'demuxer/audio_decoder_config.cc', + 'demuxer/avc.cc', + 'demuxer/bit_reader.cc', + 'demuxer/box_definitions.cc', + 'demuxer/box_reader.cc', + 'demuxer/cenc.cc', + 'demuxer/channel_layout.cc', + 'demuxer/decrypt_config.cc', + 'demuxer/es_descriptor.cc', + 'demuxer/mp4_demuxer.cc', + 'demuxer/track_run_iterator.cc', + 'demuxer/video_decoder_config.cc', + 'demuxer/video_util.cc', + 'MP4Decoder.cpp', + 'MP4Reader.cpp', + 'PlatformDecoderModule.cpp', + 'wmf/MFTDecoder.cpp', + 'wmf/WMFAudioDecoder.cpp', + 'wmf/WMFDecoderModule.cpp', + 'wmf/WMFVideoDecoder.cpp', +] + +FINAL_LIBRARY = 'gklayout' + +FAIL_ON_WARNINGS = True diff --git a/content/media/fmp4/wmf/MFTDecoder.cpp b/content/media/fmp4/wmf/MFTDecoder.cpp new file mode 100644 index 000000000000..65fe6224597a --- /dev/null +++ b/content/media/fmp4/wmf/MFTDecoder.cpp @@ -0,0 +1,290 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MFTDecoder.h" +#include "nsThreadUtils.h" +#include "WMFUtils.h" +#include "prlog.h" + +#ifdef PR_LOGGING +PRLogModuleInfo* GetDemuxerLog(); +#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__)) +#else +#define LOG(...) +#endif + + +namespace mozilla { + +MFTDecoder::MFTDecoder() + : mMFTProvidesOutputSamples(false) +{ + memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO)); + memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO)); +} + +MFTDecoder::~MFTDecoder() +{ +} + +HRESULT +MFTDecoder::Create(const GUID& aMFTClsID) +{ + // Create the IMFTransform to do the decoding. + HRESULT hr; + hr = CoCreateInstance(aMFTClsID, + NULL, + CLSCTX_INPROC_SERVER, + IID_IMFTransform, + reinterpret_cast(static_cast(byRef(mDecoder)))); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + return S_OK; +} + +HRESULT +MFTDecoder::SetMediaTypes(IMFMediaType* aInputType, + const GUID& aOutputSubType) +{ + mOutputSubtype = aOutputSubType; + + // Set the input type to the one the caller gave us... + HRESULT hr = mDecoder->SetInputType(0, aInputType, 0); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + hr = SetDecoderOutputType(); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + mMFTProvidesOutputSamples = IsFlagSet(mOutputStreamInfo.dwFlags, MFT_OUTPUT_STREAM_PROVIDES_SAMPLES); + + hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + return S_OK; +} + +TemporaryRef +MFTDecoder::GetAttributes() +{ + RefPtr attr; + HRESULT hr = mDecoder->GetAttributes(byRef(attr)); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + return attr; +} + +HRESULT +MFTDecoder::SetDecoderOutputType() +{ + NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); + + // Iterate the enumerate the output types, until we find one compatible + // with what we need. + HRESULT hr; + RefPtr outputType; + UINT32 typeIndex = 0; + while (SUCCEEDED(mDecoder->GetOutputAvailableType(0, typeIndex++, byRef(outputType)))) { + GUID subtype; + hr = outputType->GetGUID(MF_MT_SUBTYPE, &subtype); + if (FAILED(hr)) { + continue; + } + if (subtype == mOutputSubtype) { + hr = mDecoder->SetOutputType(0, outputType, 0); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + return S_OK; + } + } + return E_FAIL; +} + +HRESULT +MFTDecoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData) +{ + NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); + HRESULT hr = mDecoder->ProcessMessage(aMsg, aData); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + return S_OK; +} + +HRESULT +MFTDecoder::CreateInputSample(const uint8_t* aData, + uint32_t aDataSize, + int64_t aTimestamp, + RefPtr* aOutSample) +{ + NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); + + HRESULT hr; + RefPtr sample; + hr = wmf::MFCreateSample(byRef(sample)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + RefPtr buffer; + int32_t bufferSize = std::max(uint32_t(mInputStreamInfo.cbSize), aDataSize); + UINT32 alignment = (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0; + hr = wmf::MFCreateAlignedMemoryBuffer(bufferSize, alignment, byRef(buffer)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + DWORD maxLength = 0; + DWORD currentLength = 0; + BYTE* dst = nullptr; + hr = buffer->Lock(&dst, &maxLength, ¤tLength); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + // Copy data into sample's buffer. + memcpy(dst, aData, aDataSize); + + hr = buffer->Unlock(); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + hr = buffer->SetCurrentLength(aDataSize); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + hr = sample->AddBuffer(buffer); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + hr = sample->SetSampleTime(UsecsToHNs(aTimestamp)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + *aOutSample = sample.forget(); + + return S_OK; +} + +HRESULT +MFTDecoder::CreateOutputSample(RefPtr* aOutSample) +{ + NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); + + HRESULT hr; + RefPtr sample; + hr = wmf::MFCreateSample(byRef(sample)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + RefPtr buffer; + int32_t bufferSize = mOutputStreamInfo.cbSize; + UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1) ? mOutputStreamInfo.cbAlignment - 1 : 0; + hr = wmf::MFCreateAlignedMemoryBuffer(bufferSize, alignment, byRef(buffer)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + DWORD maxLength = 0; + DWORD currentLength = 0; + BYTE* dst = nullptr; + + hr = sample->AddBuffer(buffer); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + *aOutSample = sample.forget(); + + return S_OK; +} + +HRESULT +MFTDecoder::Output(RefPtr* aOutput) +{ + NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); + + HRESULT hr; + + MFT_OUTPUT_DATA_BUFFER output = {0}; + + RefPtr sample; + if (!mMFTProvidesOutputSamples) { + hr = CreateOutputSample(&sample); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + output.pSample = sample; + } + + DWORD status = 0; + hr = mDecoder->ProcessOutput(0, 1, &output, &status); + if (output.pEvents) { + // We must release this, as per the IMFTransform::ProcessOutput() + // MSDN documentation. + output.pEvents->Release(); + output.pEvents = nullptr; + } + + if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { + // Type change, probably geometric aperature change. + // Reconfigure decoder output type, so that GetOutputMediaType() + // returns the new type, and return the error code to caller. + // This is an expected failure, so don't warn on encountering it. + hr = SetDecoderOutputType(); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + // Return the error, so that the caller knows to retry. + return MF_E_TRANSFORM_STREAM_CHANGE; + } + + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + // Not enough input to produce output. This is an expected failure, + // so don't warn on encountering it. + return hr; + } + // Treat other errors as unexpected, and warn. + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + MOZ_ASSERT(output.pSample); + + *aOutput = output.pSample; // AddRefs + if (mMFTProvidesOutputSamples) { + // If the MFT is providing samples, we must release the sample here. + // Typically only the H.264 MFT provides samples when using DXVA, + // and it always re-uses the same sample, so if we don't release it + // MFT::ProcessOutput() deadlocks waiting for the sample to be released. + output.pSample->Release(); + output.pSample = nullptr; + } + + return S_OK; +} + +HRESULT +MFTDecoder::Input(const uint8_t* aData, + uint32_t aDataSize, + int64_t aTimestamp) +{ + NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); + + RefPtr input; + HRESULT hr = CreateInputSample(aData, aDataSize, aTimestamp, &input); + NS_ENSURE_TRUE(SUCCEEDED(hr) && input!=nullptr, hr); + + hr = mDecoder->ProcessInput(0, input, 0); + if (hr == MF_E_NOTACCEPTING) { + // MFT *already* has enough data to produce a sample. Retrieve it. + return MF_E_NOTACCEPTING; + } + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + return S_OK; +} + +HRESULT +MFTDecoder::Flush() +{ + HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + return S_OK; +} + +HRESULT +MFTDecoder::GetOutputMediaType(RefPtr& aMediaType) +{ + NS_ENSURE_TRUE(mDecoder, E_POINTER); + return mDecoder->GetOutputCurrentType(0, byRef(aMediaType)); +} + +} // namespace mozilla diff --git a/content/media/fmp4/wmf/MFTDecoder.h b/content/media/fmp4/wmf/MFTDecoder.h new file mode 100644 index 000000000000..d1a4c444107e --- /dev/null +++ b/content/media/fmp4/wmf/MFTDecoder.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(MFTDecoder_h_) +#define MFTDecoder_h_ + +#include "wmf.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ReentrantMonitor.h" +#include "nsIThread.h" + +namespace mozilla { + +class MFTDecoder { +public: + MFTDecoder(); + ~MFTDecoder(); + + // Creates the MFT. First thing to do as part of setup. + // + // Params: + // - aMFTClsID the clsid used by CoCreateInstance to instantiate the + // decoder MFT. + HRESULT Create(const GUID& aMFTClsID); + + // Sets the input and output media types. Call after Init(). + // + // Params: + // - aInputType needs at least major and minor types set. + // - aOutputSubType is the minor type of the same major type e.g. + // MFVideoFormat_H264. This is used to select the output type out + // of all the available output types of the MFT. + HRESULT SetMediaTypes(IMFMediaType* aInputType, + const GUID& aOutputSubType); + + // Returns the MFT's IMFAttributes object. + TemporaryRef GetAttributes(); + + // Retrieves the media type being output. This may not be valid until + // the first sample is decoded. + HRESULT GetOutputMediaType(RefPtr& aMediaType); + + // Submits data into the MFT for processing. + // + // Returns: + // - MF_E_NOTACCEPTING if the decoder can't accept input. The data + // must be resubmitted after Output() stops producing output. + HRESULT Input(const uint8_t* aData, + uint32_t aDataSize, + int64_t aTimestampUsecs); + + // Retrieves output from the MFT. Call this once Input() returns + // MF_E_NOTACCEPTING. Some MFTs with hardware acceleration (the H.264 + // decoder MFT in particular) can't handle it if clients hold onto + // references to the output IMFSample, so don't do that. + // + // Returns: + // - MF_E_TRANSFORM_STREAM_CHANGE if the underlying stream output + // type changed. Retrieve the output media type and reconfig client, + // else you may misinterpret the MFT's output. + // - MF_E_TRANSFORM_NEED_MORE_INPUT if no output can be produced + // due to lack of input. + // - S_OK if an output frame is produced. + HRESULT Output(RefPtr* aOutput); + + // Sends a flush message to the MFT. This causes it to discard all + // input data. Use before seeking. + HRESULT Flush(); + + // Sends a message to the MFT. + HRESULT SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData); + +private: + + HRESULT SetDecoderOutputType(); + + HRESULT CreateInputSample(const uint8_t* aData, + uint32_t aDataSize, + int64_t aTimestampUsecs, + RefPtr* aOutSample); + + HRESULT CreateOutputSample(RefPtr* aOutSample); + + MFT_INPUT_STREAM_INFO mInputStreamInfo; + MFT_OUTPUT_STREAM_INFO mOutputStreamInfo; + + RefPtr mDecoder; + + GUID mOutputSubtype; + + // True if the IMFTransform allocates the samples that it returns. + bool mMFTProvidesOutputSamples; +}; + +} // namespace mozilla + +#endif diff --git a/content/media/fmp4/wmf/WMFAudioDecoder.cpp b/content/media/fmp4/wmf/WMFAudioDecoder.cpp new file mode 100644 index 000000000000..1dda4eb08835 --- /dev/null +++ b/content/media/fmp4/wmf/WMFAudioDecoder.cpp @@ -0,0 +1,283 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WMFAudioDecoder.h" +#include "VideoUtils.h" +#include "WMFUtils.h" +#include "nsTArray.h" + +#include "prlog.h" + +#ifdef PR_LOGGING +PRLogModuleInfo* GetDemuxerLog(); +#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__)) +#else +#define LOG(...) +#endif + + +namespace mozilla { + +WMFAudioDecoder::WMFAudioDecoder() + : mAudioChannels(0), + mAudioBytesPerSample(0), + mAudioRate(0), + mLastStreamOffset(0), + mAudioFrameOffset(0), + mAudioFrameSum(0), + mMustRecaptureAudioPosition(true) +{ +} + +static void +AACAudioSpecificConfigToUserData(const uint8_t* aAudioSpecConfig, + uint32_t aConfigLength, + nsTArray& aOutUserData) +{ + MOZ_ASSERT(aOutUserData.IsEmpty()); + + // The MF_MT_USER_DATA for AAC is defined here: + // http://msdn.microsoft.com/en-us/library/windows/desktop/dd742784%28v=vs.85%29.aspx + // + // For MFAudioFormat_AAC, MF_MT_USER_DATA contains the portion of + // the HEAACWAVEINFO structure that appears after the WAVEFORMATEX + // structure (that is, after the wfx member). This is followed by + // the AudioSpecificConfig() data, as defined by ISO/IEC 14496-3. + // [...] + // The length of the AudioSpecificConfig() data is 2 bytes for AAC-LC + // or HE-AAC with implicit signaling of SBR/PS. It is more than 2 bytes + // for HE-AAC with explicit signaling of SBR/PS. + // + // The value of audioObjectType as defined in AudioSpecificConfig() + // must be 2, indicating AAC-LC. The value of extensionAudioObjectType + // must be 5 for SBR or 29 for PS. + // + // HEAACWAVEINFO structure: + // typedef struct heaacwaveinfo_tag { + // WAVEFORMATEX wfx; + // WORD wPayloadType; + // WORD wAudioProfileLevelIndication; + // WORD wStructType; + // WORD wReserved1; + // DWORD dwReserved2; + // } + const UINT32 heeInfoLen = 4 * sizeof(WORD) + sizeof(DWORD); + + // The HEAACWAVEINFO must have payload and profile set, + // the rest can be all 0x00. + BYTE heeInfo[heeInfoLen] = {0}; + WORD* w = (WORD*)heeInfo; + w[0] = 0x1; // Payload type ADTS + w[1] = 0xFE; // Profile level indication, none specified. + + aOutUserData.AppendElements(heeInfo, heeInfoLen); + aOutUserData.AppendElements(aAudioSpecConfig, aConfigLength); +} + +nsresult +WMFAudioDecoder::Init(uint32_t aChannelCount, + uint32_t aSampleRate, + uint16_t aBitsPerSample, + const uint8_t* aAudioSpecConfig, + uint32_t aConfigLength) +{ + mDecoder = new MFTDecoder(); + + HRESULT hr = mDecoder->Create(CLSID_CMSAACDecMFT); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + // Setup input/output media types + RefPtr type; + + hr = wmf::MFCreateMediaType(byRef(type)); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = type->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_AAC); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = type->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, aSampleRate); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = type->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, aChannelCount); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = type->SetUINT32(MF_MT_AAC_PAYLOAD_TYPE, 0x1); // ADTS + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + nsTArray userData; + AACAudioSpecificConfigToUserData(aAudioSpecConfig, + aConfigLength, + userData); + + hr = type->SetBlob(MF_MT_USER_DATA, + userData.Elements(), + userData.Length()); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = mDecoder->SetMediaTypes(type, MFAudioFormat_PCM); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + mAudioChannels = aChannelCount; + mAudioBytesPerSample = aBitsPerSample / 8; + mAudioRate = aSampleRate; + return NS_OK; +} + +nsresult +WMFAudioDecoder::Shutdown() +{ + return NS_OK; +} + +DecoderStatus +WMFAudioDecoder::Input(const uint8_t* aData, + uint32_t aLength, + Microseconds aDTS, + Microseconds aPTS, + int64_t aOffsetInStream) +{ + mLastStreamOffset = aOffsetInStream; + HRESULT hr = mDecoder->Input(aData, aLength, aPTS); + if (hr == MF_E_NOTACCEPTING) { + return DECODE_STATUS_NOT_ACCEPTING; + } + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + + return DECODE_STATUS_OK; +} + +DecoderStatus +WMFAudioDecoder::Output(nsAutoPtr& aOutData) +{ + DecoderStatus status; + do { + status = OutputNonNegativeTimeSamples(aOutData); + } while (status == DECODE_STATUS_OK && !aOutData); + return status; +} + +DecoderStatus +WMFAudioDecoder::OutputNonNegativeTimeSamples(nsAutoPtr& aOutData) +{ + + aOutData = nullptr; + RefPtr sample; + HRESULT hr = mDecoder->Output(&sample); + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + return DECODE_STATUS_NEED_MORE_INPUT; + } + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + + RefPtr buffer; + hr = sample->ConvertToContiguousBuffer(byRef(buffer)); + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + + BYTE* data = nullptr; // Note: *data will be owned by the IMFMediaBuffer, we don't need to free it. + DWORD maxLength = 0, currentLength = 0; + hr = buffer->Lock(&data, &maxLength, ¤tLength); + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + + int32_t numSamples = currentLength / mAudioBytesPerSample; + int32_t numFrames = numSamples / mAudioChannels; + + // Sometimes when starting decoding, the AAC decoder gives us samples + // with a negative timestamp. AAC does usually have preroll (or encoder + // delay) encoded into its bitstream, but the amount encoded to the stream + // is variable, and it not signalled in-bitstream. There is sometimes + // signalling in the MP4 container what the preroll amount, but it's + // inconsistent. It looks like WMF's AAC encoder may take this into + // account, so strip off samples with a negative timestamp to get us + // to a 0-timestamp start. This seems to maintain A/V sync, so we can run + // with this until someone complains... + + // We calculate the timestamp and the duration based on the number of audio + // frames we've already played. We don't trust the timestamp stored on the + // IMFSample, as sometimes it's wrong, possibly due to buggy encoders? + + // If this sample block comes after a discontinuity (i.e. a gap or seek) + // reset the frame counters, and capture the timestamp. Future timestamps + // will be offset from this block's timestamp. + UINT32 discontinuity = false; + int32_t numFramesToStrip = 0; + sample->GetUINT32(MFSampleExtension_Discontinuity, &discontinuity); + if (mMustRecaptureAudioPosition || discontinuity) { + mAudioFrameSum = 0; + LONGLONG timestampHns = 0; + hr = sample->GetSampleTime(×tampHns); + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + hr = HNsToFrames(timestampHns, mAudioRate, &mAudioFrameOffset); + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + if (mAudioFrameOffset < 0) { + // First sample has a negative timestamp. Strip off the samples until + // we reach positive territory. + numFramesToStrip = -mAudioFrameOffset; + mAudioFrameOffset = 0; + } + mMustRecaptureAudioPosition = false; + } + MOZ_ASSERT(numFramesToStrip >= 0); + int32_t offset = std::min(numFramesToStrip, numFrames); + numFrames -= offset; + numSamples -= offset * mAudioChannels; + MOZ_ASSERT(numFrames >= 0); + MOZ_ASSERT(numSamples >= 0); + if (numFrames == 0) { + // All data from this chunk stripped, loop back and try to output the next + // frame, if possible. + return DECODE_STATUS_OK; + } + + nsAutoArrayPtr audioData(new AudioDataValue[numSamples]); + + // Just assume PCM output for now... + MOZ_ASSERT(mAudioBytesPerSample == 2); + int16_t* pcm = ((int16_t*)data) + (offset * mAudioChannels); + MOZ_ASSERT(pcm >= (int16_t*)data); + MOZ_ASSERT(pcm <= (int16_t*)(data + currentLength)); + MOZ_ASSERT(pcm+numSamples <= (int16_t*)(data + currentLength)); + for (int32_t i = 0; i < numSamples; ++i) { + audioData[i] = AudioSampleToFloat(pcm[i]); + } + + buffer->Unlock(); + int64_t timestamp; + hr = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, mAudioRate, ×tamp); + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + + mAudioFrameSum += numFrames; + + int64_t duration; + hr = FramesToUsecs(numFrames, mAudioRate, &duration); + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + + aOutData = new AudioData(mLastStreamOffset, + timestamp, + duration, + numFrames, + audioData.forget(), + mAudioChannels); + + #ifdef LOG_SAMPLE_DECODE + LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u", + timestamp, duration, currentLength); + #endif + + return DECODE_STATUS_OK; +} + +DecoderStatus +WMFAudioDecoder::Flush() +{ + NS_ENSURE_TRUE(mDecoder, DECODE_STATUS_ERROR); + HRESULT hr = mDecoder->Flush(); + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + return DECODE_STATUS_OK; +} + +} // namespace mozilla diff --git a/content/media/fmp4/wmf/WMFAudioDecoder.h b/content/media/fmp4/wmf/WMFAudioDecoder.h new file mode 100644 index 000000000000..89afdbf80cdd --- /dev/null +++ b/content/media/fmp4/wmf/WMFAudioDecoder.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(WMFAudioDecoder_h_) +#define WMFAudioDecoder_h_ + +#include "wmf.h" +#include "MP4Reader.h" +#include "MFTDecoder.h" + +namespace mozilla { + +class WMFAudioDecoder : public MediaDataDecoder { +public: + WMFAudioDecoder(); + + nsresult Init(uint32_t aChannelCount, + uint32_t aSampleRate, + uint16_t aBitsPerSample, + const uint8_t* aUserData, + uint32_t aUserDataLength); + + virtual nsresult Shutdown() MOZ_OVERRIDE; + + // Inserts data into the decoder's pipeline. + virtual DecoderStatus Input(const uint8_t* aData, + uint32_t aLength, + Microseconds aDTS, + Microseconds aPTS, + int64_t aOffsetInStream); + + // Blocks until a decoded sample is produced by the decoder. + virtual DecoderStatus Output(nsAutoPtr& aOutData); + + virtual DecoderStatus Flush() MOZ_OVERRIDE; + +private: + + + // A helper for Output() above. This has the same interface as Output() + // above, except that it returns DECODE_STATUS_OK and sets aOutData to + // nullptr when all the output samples have been stripped due to having + // negative timestamps. WMF's AAC decoder sometimes output negatively + // timestamped samples, presumably they're the preroll samples, and we + // strip them. + DecoderStatus OutputNonNegativeTimeSamples(nsAutoPtr& aOutData); + + nsAutoPtr mDecoder; + + uint32_t mAudioChannels; + uint32_t mAudioBytesPerSample; + uint32_t mAudioRate; + + // The last offset into the media resource that was passed into Input(). + // This is used to approximate the decoder's position in the media resource. + int64_t mLastStreamOffset; + + // The offset, in audio frames, at which playback started since the + // last discontinuity. + int64_t mAudioFrameOffset; + // The number of audio frames that we've played since the last + // discontinuity. + int64_t mAudioFrameSum; + // True if we need to re-initialize mAudioFrameOffset and mAudioFrameSum + // from the next audio packet we decode. This happens after a seek, since + // WMF doesn't mark a stream as having a discontinuity after a seek(0). + bool mMustRecaptureAudioPosition; +}; + + + +} // namespace mozilla + +#endif diff --git a/content/media/fmp4/wmf/WMFDecoderModule.cpp b/content/media/fmp4/wmf/WMFDecoderModule.cpp new file mode 100644 index 000000000000..1a7b509f85f4 --- /dev/null +++ b/content/media/fmp4/wmf/WMFDecoderModule.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "wmf.h" +#include "WMFDecoderModule.h" +#include "WMFDecoder.h" +#include "WMFVideoDecoder.h" +#include "WMFAudioDecoder.h" +#include "mozilla/Preferences.h" +#include "mozilla/DebugOnly.h" + +namespace mozilla { + +WMFDecoderModule::WMFDecoderModule() + : mDXVAEnabled(Preferences::GetBool("media.windows-media-foundation.use-dxva", false)) +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); +} + +WMFDecoderModule::~WMFDecoderModule() +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); +} + +nsresult +WMFDecoderModule::Init() +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + if (!Preferences::GetBool("media.windows-media-foundation.enabled", false)) { + return NS_ERROR_FAILURE; + } + + nsresult rv = WMFDecoder::LoadDLLs(); + NS_ENSURE_SUCCESS(rv, rv); + + if (FAILED(wmf::MFStartup())) { + NS_WARNING("Failed to initialize Windows Media Foundation"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +WMFDecoderModule::Shutdown() +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + + DebugOnly hr = wmf::MFShutdown(); + NS_ASSERTION(SUCCEEDED(hr), "MFShutdown failed"); + + return NS_OK; +} + +MediaDataDecoder* +WMFDecoderModule::CreateVideoDecoder(mozilla::layers::LayersBackend aLayersBackend, + mozilla::layers::ImageContainer* aImageContainer) +{ + nsAutoPtr decoder(new WMFVideoDecoder(mDXVAEnabled)); + nsresult rv = decoder->Init(aLayersBackend, aImageContainer); + NS_ENSURE_SUCCESS(rv, nullptr); + return decoder.forget(); +} + +MediaDataDecoder* +WMFDecoderModule::CreateAudioDecoder(uint32_t aChannelCount, + uint32_t aSampleRate, + uint16_t aBitsPerSample, + const uint8_t* aUserData, + uint32_t aUserDataLength) +{ + nsAutoPtr decoder(new WMFAudioDecoder()); + nsresult rv = decoder->Init(aChannelCount, + aSampleRate, + aBitsPerSample, + aUserData, + aUserDataLength); + NS_ENSURE_SUCCESS(rv, nullptr); + return decoder.forget(); +} + +void +WMFDecoderModule::OnDecodeThreadStart() +{ + MOZ_ASSERT(!NS_IsMainThread(), "Must not be on main thread."); + // XXX WebAudio can call this on the main thread when using deprecated APIs. + // That should not happen. You cannot change the concurrency model once already set. + // The main thread will continue to be STA, which seems to work, but MSDN + // recommends that MTA be used. + // TODO: enforce that WebAudio stops doing that! + CoInitializeEx(0, COINIT_MULTITHREADED); +} + +void +WMFDecoderModule::OnDecodeThreadFinish() +{ + MOZ_ASSERT(!NS_IsMainThread(), "Must be on main thread."); + CoUninitialize(); +} + +} // namespace mozilla diff --git a/content/media/fmp4/wmf/WMFDecoderModule.h b/content/media/fmp4/wmf/WMFDecoderModule.h new file mode 100644 index 000000000000..983d807c5cc2 --- /dev/null +++ b/content/media/fmp4/wmf/WMFDecoderModule.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(WMFPlatformDecoderModule_h_) +#define WMFPlatformDecoderModule_h_ + +#include "PlatformDecoderModule.h" + +namespace mozilla { + +class WMFDecoderModule : public PlatformDecoderModule { +public: + WMFDecoderModule(); + virtual ~WMFDecoderModule(); + + // Initializes the module, loads required dynamic libraries, etc. + // Main thread only. + nsresult Init(); + + // Called when the decoders have shutdown. Main thread only. + // Does this really need to be main thread only???? + virtual nsresult Shutdown() MOZ_OVERRIDE; + + // Decode thread. + virtual MediaDataDecoder* + CreateVideoDecoder(mozilla::layers::LayersBackend aLayersBackend, + mozilla::layers::ImageContainer* aImageContainer) MOZ_OVERRIDE; + + // Decode thread. + virtual MediaDataDecoder* CreateAudioDecoder(uint32_t aChannelCount, + uint32_t aSampleRate, + uint16_t aBitsPerSample, + const uint8_t* aUserData, + uint32_t aUserDataLength) MOZ_OVERRIDE; + + // Platform decoders can override these. Base implementation does nothing. + virtual void OnDecodeThreadStart() MOZ_OVERRIDE; + virtual void OnDecodeThreadFinish() MOZ_OVERRIDE; +private: + const bool mDXVAEnabled; +}; + +} // namespace mozilla + +#endif diff --git a/content/media/fmp4/wmf/WMFVideoDecoder.cpp b/content/media/fmp4/wmf/WMFVideoDecoder.cpp new file mode 100644 index 000000000000..e610a440d957 --- /dev/null +++ b/content/media/fmp4/wmf/WMFVideoDecoder.cpp @@ -0,0 +1,412 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WMFVideoDecoder.h" +#include "MediaDecoderReader.h" +#include "WMFUtils.h" +#include "ImageContainer.h" +#include "VideoUtils.h" +#include "DXVA2Manager.h" +#include "nsThreadUtils.h" +#include "WinUtils.h" +#include "Layers.h" +#include "mozilla/layers/LayersTypes.h" +#include "prlog.h" + +#ifdef PR_LOGGING +PRLogModuleInfo* GetDemuxerLog(); +#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__)) +#else +#define LOG(...) +#endif + +using mozilla::layers::Image; +using mozilla::layers::LayerManager; +using mozilla::layers::LayersBackend; + +namespace mozilla { + +WMFVideoDecoder::WMFVideoDecoder(bool aDXVAEnabled) + : mVideoStride(0), + mVideoWidth(0), + mVideoHeight(0), + mLastStreamOffset(0), + mDXVAEnabled(aDXVAEnabled), + mIsRunningOnVista(widget::WinUtils::GetWindowsVersion() == widget::WinUtils::WIN7_VERSION), + mUseHwAccel(false) +{ + NS_ASSERTION(!NS_IsMainThread(), "Must be on main thread."); + MOZ_COUNT_CTOR(WMFVideoDecoder); +} + +WMFVideoDecoder::~WMFVideoDecoder() +{ + MOZ_COUNT_DTOR(WMFVideoDecoder); +} + +class CreateDXVAManagerEvent : public nsRunnable { +public: + NS_IMETHOD Run() { + NS_ASSERTION(NS_IsMainThread(), "Must be on main thread."); + mDXVA2Manager = DXVA2Manager::Create(); + return NS_OK; + } + nsAutoPtr mDXVA2Manager; +}; + +bool +WMFVideoDecoder::InitializeDXVA(mozilla::layers::LayersBackend aLayersBackend) +{ + // If we use DXVA but aren't running with a D3D layer manager then the + // readback of decoded video frames from GPU to CPU memory grinds painting + // to a halt, and makes playback performance *worse*. + if (!mDXVAEnabled || + (aLayersBackend != LayersBackend::LAYERS_D3D9 && + aLayersBackend != LayersBackend::LAYERS_D3D10)) { + return false; + } + + // The DXVA manager must be created on the main thread. + nsRefPtr event(new CreateDXVAManagerEvent()); + NS_DispatchToMainThread(event, NS_DISPATCH_SYNC); + mDXVA2Manager = event->mDXVA2Manager; + + return mDXVA2Manager != nullptr; +} + +nsresult +WMFVideoDecoder::Init(mozilla::layers::LayersBackend aLayersBackend, + mozilla::layers::ImageContainer* aImageContainer) +{ + NS_ENSURE_ARG_POINTER(aImageContainer); + + bool useDxva= InitializeDXVA(aLayersBackend); + + mDecoder = new MFTDecoder(); + + HRESULT hr = mDecoder->Create(CLSID_CMSH264DecoderMFT); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + if (useDxva) { + RefPtr attr(mDecoder->GetAttributes()); + + UINT32 aware = 0; + if (attr) { + attr->GetUINT32(MF_SA_D3D_AWARE, &aware); + } + if (aware) { + // TODO: Test if I need this anywhere... Maybe on Vista? + //hr = attr->SetUINT32(CODECAPI_AVDecVideoAcceleration_H264, TRUE); + //NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + MOZ_ASSERT(mDXVA2Manager); + ULONG_PTR manager = ULONG_PTR(mDXVA2Manager->GetDXVADeviceManager()); + hr = mDecoder->SendMFTMessage(MFT_MESSAGE_SET_D3D_MANAGER, manager); + if (SUCCEEDED(hr)) { + mUseHwAccel = true; + } + } + } + + // Setup the input/output media types. + RefPtr type; + hr = wmf::MFCreateMediaType(byRef(type)); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + hr = type->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_MixedInterlaceOrProgressive); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + GUID outputType = mUseHwAccel ? MFVideoFormat_NV12 : MFVideoFormat_YV12; + hr = mDecoder->SetMediaTypes(type, outputType); + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); + + mImageContainer = aImageContainer; + + LOG("Video Decoder initialized, Using DXVA: %s", (mUseHwAccel ? "Yes" : "No")); + + return NS_OK; +} + +HRESULT +WMFVideoDecoder::ConfigureVideoFrameGeometry() +{ + RefPtr mediaType; + HRESULT hr = mDecoder->GetOutputMediaType(mediaType); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + // Verify that the video subtype is what we expect it to be. + // When using hardware acceleration/DXVA2 the video format should + // be NV12, which is DXVA2's preferred format. For software decoding + // we use YV12, as that's easier for us to stick into our rendering + // pipeline than NV12. NV12 has interleaved UV samples, whereas YV12 + // is a planar format. + GUID videoFormat; + hr = mediaType->GetGUID(MF_MT_SUBTYPE, &videoFormat); + NS_ENSURE_TRUE(videoFormat == MFVideoFormat_NV12 || !mUseHwAccel, E_FAIL); + NS_ENSURE_TRUE(videoFormat == MFVideoFormat_YV12 || mUseHwAccel, E_FAIL); + + nsIntRect pictureRegion; + hr = GetPictureRegion(mediaType, pictureRegion); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + UINT32 width = 0, height = 0; + hr = MFGetAttributeSize(mediaType, MF_MT_FRAME_SIZE, &width, &height); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + uint32_t aspectNum = 0, aspectDenom = 0; + hr = MFGetAttributeRatio(mediaType, + MF_MT_PIXEL_ASPECT_RATIO, + &aspectNum, + &aspectDenom); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + // Calculate and validate the picture region and frame dimensions after + // scaling by the pixel aspect ratio. + nsIntSize frameSize = nsIntSize(width, height); + nsIntSize displaySize = nsIntSize(pictureRegion.width, pictureRegion.height); + ScaleDisplayByAspectRatio(displaySize, float(aspectNum) / float(aspectDenom)); + if (!VideoInfo::ValidateVideoRegion(frameSize, pictureRegion, displaySize)) { + // Video track's frame sizes will overflow. Ignore the video track. + return E_FAIL; + } + + // Success! Save state. + mVideoInfo.mDisplay = displaySize; + mVideoInfo.mHasVideo = true; + GetDefaultStride(mediaType, &mVideoStride); + mVideoWidth = width; + mVideoHeight = height; + mPictureRegion = pictureRegion; + + LOG("WMFReader frame geometry frame=(%u,%u) stride=%u picture=(%d, %d, %d, %d) display=(%d,%d) PAR=%d:%d", + width, height, + mVideoStride, + mPictureRegion.x, mPictureRegion.y, mPictureRegion.width, mPictureRegion.height, + displaySize.width, displaySize.height, + aspectNum, aspectDenom); + + return S_OK; +} + +nsresult +WMFVideoDecoder::Shutdown() +{ + return NS_OK; +} + +// Inserts data into the decoder's pipeline. +DecoderStatus +WMFVideoDecoder::Input(const uint8_t* aData, + uint32_t aLength, + Microseconds aDTS, + Microseconds aPTS, + int64_t aOffsetInStream) +{ + mLastStreamOffset = aOffsetInStream; + HRESULT hr = mDecoder->Input(aData, aLength, aPTS); + if (hr == MF_E_NOTACCEPTING) { + return DECODE_STATUS_NOT_ACCEPTING; + } + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + + return DECODE_STATUS_OK; +} + +HRESULT +WMFVideoDecoder::CreateBasicVideoFrame(IMFSample* aSample, + VideoData** aOutVideoData) +{ + NS_ENSURE_TRUE(aSample, E_POINTER); + NS_ENSURE_TRUE(aOutVideoData, E_POINTER); + + *aOutVideoData = nullptr; + + HRESULT hr; + RefPtr buffer; + + // Must convert to contiguous buffer to use IMD2DBuffer interface. + hr = aSample->ConvertToContiguousBuffer(byRef(buffer)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + // Try and use the IMF2DBuffer interface if available, otherwise fallback + // to the IMFMediaBuffer interface. Apparently IMF2DBuffer is more efficient, + // but only some systems (Windows 8?) support it. + BYTE* data = nullptr; + LONG stride = 0; + RefPtr twoDBuffer; + hr = buffer->QueryInterface(static_cast(byRef(twoDBuffer))); + if (SUCCEEDED(hr)) { + hr = twoDBuffer->Lock2D(&data, &stride); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + } else { + hr = buffer->Lock(&data, NULL, NULL); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + stride = mVideoStride; + } + + // YV12, planar format: [YYYY....][VVVV....][UUUU....] + // i.e., Y, then V, then U. + VideoData::YCbCrBuffer b; + + // Y (Y') plane + b.mPlanes[0].mData = data; + b.mPlanes[0].mStride = stride; + b.mPlanes[0].mHeight = mVideoHeight; + b.mPlanes[0].mWidth = mVideoWidth; + b.mPlanes[0].mOffset = 0; + b.mPlanes[0].mSkip = 0; + + // The V and U planes are stored 16-row-aligned, so we need to add padding + // to the row heights to ensure the Y'CbCr planes are referenced properly. + uint32_t padding = 0; + if (mVideoHeight % 16 != 0) { + padding = 16 - (mVideoHeight % 16); + } + uint32_t y_size = stride * (mVideoHeight + padding); + uint32_t v_size = stride * (mVideoHeight + padding) / 4; + uint32_t halfStride = (stride + 1) / 2; + uint32_t halfHeight = (mVideoHeight + 1) / 2; + uint32_t halfWidth = (mVideoWidth + 1) / 2; + + // U plane (Cb) + b.mPlanes[1].mData = data + y_size + v_size; + b.mPlanes[1].mStride = halfStride; + b.mPlanes[1].mHeight = halfHeight; + b.mPlanes[1].mWidth = halfWidth; + b.mPlanes[1].mOffset = 0; + b.mPlanes[1].mSkip = 0; + + // V plane (Cr) + b.mPlanes[2].mData = data + y_size; + b.mPlanes[2].mStride = halfStride; + b.mPlanes[2].mHeight = halfHeight; + b.mPlanes[2].mWidth = halfWidth; + b.mPlanes[2].mOffset = 0; + b.mPlanes[2].mSkip = 0; + + Microseconds pts = GetSampleTime(aSample); + Microseconds duration = GetSampleDuration(aSample); + VideoData *v = VideoData::Create(mVideoInfo, + mImageContainer, + mLastStreamOffset, + pts, + duration, + b, + false, + -1, + mPictureRegion); + if (twoDBuffer) { + twoDBuffer->Unlock2D(); + } else { + buffer->Unlock(); + } + + *aOutVideoData = v; + + return S_OK; +} + +HRESULT +WMFVideoDecoder::CreateD3DVideoFrame(IMFSample* aSample, + VideoData** aOutVideoData) +{ + NS_ENSURE_TRUE(aSample, E_POINTER); + NS_ENSURE_TRUE(aOutVideoData, E_POINTER); + NS_ENSURE_TRUE(mDXVA2Manager, E_ABORT); + NS_ENSURE_TRUE(mUseHwAccel, E_ABORT); + + *aOutVideoData = nullptr; + HRESULT hr; + + nsRefPtr image; + hr = mDXVA2Manager->CopyToImage(aSample, + mPictureRegion, + mImageContainer, + getter_AddRefs(image)); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + NS_ENSURE_TRUE(image, E_FAIL); + + Microseconds pts = GetSampleTime(aSample); + Microseconds duration = GetSampleDuration(aSample); + VideoData *v = VideoData::CreateFromImage(mVideoInfo, + mImageContainer, + mLastStreamOffset, + pts, + duration, + image.forget(), + false, + -1, + mPictureRegion); + + NS_ENSURE_TRUE(v, E_FAIL); + *aOutVideoData = v; + + return S_OK; +} + +// Blocks until decoded sample is produced by the deoder. +DecoderStatus +WMFVideoDecoder::Output(nsAutoPtr& aOutData) +{ + RefPtr sample; + HRESULT hr; + + // Loop until we decode a sample, or an unexpected error that we can't + // handle occurs. + while (true) { + hr = mDecoder->Output(&sample); + if (SUCCEEDED(hr)) { + NS_ENSURE_TRUE(sample, DECODE_STATUS_ERROR); + break; + } + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + return DECODE_STATUS_NEED_MORE_INPUT; + } + if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { + // Video stream output type change. Probably a geometric apperature + // change. Reconfigure the video geometry, so that we output the + // correct size frames. + MOZ_ASSERT(!sample); + hr = ConfigureVideoFrameGeometry(); + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + // Loop back and try decoding again... + continue; + } + // Else unexpected error, assert, and bail. + NS_WARNING("WMFVideoDecoder::Output() unexpected error"); + return DECODE_STATUS_ERROR; + } + + VideoData* frame = nullptr; + if (mUseHwAccel) { + hr = CreateD3DVideoFrame(sample, &frame); + } else { + hr = CreateBasicVideoFrame(sample, &frame); + } + // Frame should be non null only when we succeeded. + MOZ_ASSERT((frame != nullptr) == SUCCEEDED(hr)); + NS_ENSURE_TRUE(SUCCEEDED(hr) && frame, DECODE_STATUS_ERROR); + + aOutData = frame; + + return DECODE_STATUS_OK; +} + +DecoderStatus +WMFVideoDecoder::Flush() +{ + NS_ENSURE_TRUE(mDecoder, DECODE_STATUS_ERROR); + HRESULT hr = mDecoder->Flush(); + NS_ENSURE_TRUE(SUCCEEDED(hr), DECODE_STATUS_ERROR); + return DECODE_STATUS_OK; +} + +} // namespace mozilla diff --git a/content/media/fmp4/wmf/WMFVideoDecoder.h b/content/media/fmp4/wmf/WMFVideoDecoder.h new file mode 100644 index 000000000000..8aa59bdcd00f --- /dev/null +++ b/content/media/fmp4/wmf/WMFVideoDecoder.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(WMFVideoDecoder_h_) +#define WMFVideoDecoder_h_ + +#include "wmf.h" +#include "MP4Reader.h" +#include "MFTDecoder.h" +#include "nsRect.h" + +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class DXVA2Manager; + +class WMFVideoDecoder : public MediaDataDecoder { +public: + WMFVideoDecoder(bool aDXVAEnabled); + ~WMFVideoDecoder(); + + // Decode thread. + nsresult Init(mozilla::layers::LayersBackend aLayersBackend, + mozilla::layers::ImageContainer* aImageContainer); + + virtual nsresult Shutdown() MOZ_OVERRIDE; + + // Inserts data into the decoder's pipeline. + virtual DecoderStatus Input(const uint8_t* aData, + uint32_t aLength, + Microseconds aDTS, + Microseconds aPTS, + int64_t aOffsetInStream) MOZ_OVERRIDE; + + // Blocks until a decoded sample is produced by the decoder. + virtual DecoderStatus Output(nsAutoPtr& aOutData) MOZ_OVERRIDE; + + virtual DecoderStatus Flush() MOZ_OVERRIDE; + +private: + + bool InitializeDXVA(mozilla::layers::LayersBackend aLayersBackend); + + HRESULT ConfigureVideoFrameGeometry(); + + HRESULT CreateBasicVideoFrame(IMFSample* aSample, + VideoData** aOutVideoData); + + HRESULT CreateD3DVideoFrame(IMFSample* aSample, + VideoData** aOutVideoData); + // Video frame geometry. + VideoInfo mVideoInfo; + uint32_t mVideoStride; + uint32_t mVideoWidth; + uint32_t mVideoHeight; + nsIntRect mPictureRegion; + + // The last offset into the media resource that was passed into Input(). + // This is used to approximate the decoder's position in the media resource. + int64_t mLastStreamOffset; + + nsAutoPtr mDecoder; + RefPtr mImageContainer; + nsAutoPtr mDXVA2Manager; + + const bool mDXVAEnabled; + const bool mIsRunningOnVista; + bool mUseHwAccel; +}; + + + +} // namespace mozilla + +#endif \ No newline at end of file diff --git a/content/media/moz.build b/content/media/moz.build index 31bd33881f54..bd2363c269a9 100644 --- a/content/media/moz.build +++ b/content/media/moz.build @@ -35,6 +35,9 @@ if CONFIG['MOZ_MEDIA_PLUGINS']: if CONFIG['MOZ_WMF']: PARALLEL_DIRS += ['wmf'] +if CONFIG['MOZ_FMP4']: + PARALLEL_DIRS += ['fmp4'] + if CONFIG['MOZ_APPLEMEDIA']: PARALLEL_DIRS += ['apple'] @@ -101,7 +104,7 @@ EXPORTS.mozilla.dom += [ 'VideoStreamTrack.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'AudioAvailableEventManager.cpp', 'AudioChannelFormat.cpp', 'AudioNodeEngine.cpp', @@ -111,7 +114,6 @@ SOURCES += [ 'AudioStream.cpp', 'AudioStreamTrack.cpp', 'BufferDecoder.cpp', - 'DecoderTraits.cpp', 'DOMMediaStream.cpp', 'EncodedBufferCache.cpp', 'FileBlockCache.cpp', @@ -141,6 +143,11 @@ SOURCES += [ 'WebVTTListener.cpp', ] +# DecoderTraits.cpp needs to be built separately because of Mac OS X headers. +SOURCES += [ + 'DecoderTraits.cpp', +] + FAIL_ON_WARNINGS = True if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['BUILD_ARM_NEON']: diff --git a/content/media/omx/RtspOmxReader.cpp b/content/media/omx/RtspOmxReader.cpp index fa443f87df34..d19d6800a9ad 100644 --- a/content/media/omx/RtspOmxReader.cpp +++ b/content/media/omx/RtspOmxReader.cpp @@ -124,7 +124,16 @@ status_t RtspMediaSource::read(MediaBuffer **out, const ReadOptions *options) rv = mRtspResource->ReadFrameFromTrack((uint8_t *)mBuffer->data(), mFrameMaxSize, mTrackIdx, readCount, time, actualFrameSize); - NS_ENSURE_SUCCESS(rv, ERROR_IO); + if (NS_FAILED(rv)) { + // Release mGroup and mBuffer. + stop(); + // Since RtspMediaSource is an implementation of Android media source, + // it's held by OMXCodec and isn't released yet. So we have to re-construct + // mGroup and mBuffer. + start(); + NS_WARNING("ReadFrameFromTrack failed; releasing buffers and returning."); + return ERROR_CONNECTION_LOST; + } if (actualFrameSize > mFrameMaxSize) { // release mGroup and mBuffer stop(); diff --git a/content/media/wmf/WMFReader.cpp b/content/media/wmf/WMFReader.cpp index 48ba03be7d6a..98b0927f4f13 100644 --- a/content/media/wmf/WMFReader.cpp +++ b/content/media/wmf/WMFReader.cpp @@ -385,9 +385,12 @@ WMFReader::GetSupportedAudioCodecs(const GUID** aCodecs, uint32_t* aNumCodecs) MOZ_ASSERT(aNumCodecs); if (mIsMP3Enabled) { + GUID aacOrMp3 = MFMPEG4Format_Base; + aacOrMp3.Data1 = 0x6D703461;// FOURCC('m','p','4','a'); static const GUID codecs[] = { MFAudioFormat_AAC, - MFAudioFormat_MP3 + MFAudioFormat_MP3, + aacOrMp3 }; *aCodecs = codecs; *aNumCodecs = NS_ARRAY_LENGTH(codecs); diff --git a/content/media/wmf/WMFUtils.h b/content/media/wmf/WMFUtils.h index c3ef4d9d5027..0e39f9aabe82 100644 --- a/content/media/wmf/WMFUtils.h +++ b/content/media/wmf/WMFUtils.h @@ -93,4 +93,9 @@ GetSampleDuration(IMFSample* aSample); int64_t GetSampleTime(IMFSample* aSample); +inline bool +IsFlagSet(DWORD flags, DWORD pattern) { + return (flags & pattern) == pattern; +} + } // namespace mozilla diff --git a/content/media/wmf/moz.build b/content/media/wmf/moz.build index fe178c112712..48baf30f9cbb 100644 --- a/content/media/wmf/moz.build +++ b/content/media/wmf/moz.build @@ -5,6 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. EXPORTS += [ + 'DXVA2Manager.h', 'WMF.h', 'WMFDecoder.h', 'WMFReader.h', diff --git a/content/smil/moz.build b/content/smil/moz.build index edf016dbe776..df5109727bda 100644 --- a/content/smil/moz.build +++ b/content/smil/moz.build @@ -32,7 +32,7 @@ EXPORTS += [ 'nsSMILValue.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'nsDOMTimeEvent.cpp', 'nsSMILAnimationController.cpp', 'nsSMILAnimationFunction.cpp', diff --git a/content/smil/nsSMILRepeatCount.h b/content/smil/nsSMILRepeatCount.h index d57f06db4639..18c6d66db618 100644 --- a/content/smil/nsSMILRepeatCount.h +++ b/content/smil/nsSMILRepeatCount.h @@ -3,6 +3,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsSMILRepeatCount_h +#define nsSMILRepeatCount_h + #include "nsDebug.h" #include @@ -50,3 +53,5 @@ private: double mCount; }; + +#endif diff --git a/content/svg/content/src/SVGAElement.cpp b/content/svg/content/src/SVGAElement.cpp index 1c0500a08862..49d870980141 100644 --- a/content/svg/content/src/SVGAElement.cpp +++ b/content/svg/content/src/SVGAElement.cpp @@ -161,7 +161,7 @@ SVGAElement::IsAttributeMapped(const nsIAtom* name) const } bool -SVGAElement::IsFocusable(int32_t *aTabIndex, bool aWithMouse) +SVGAElement::IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse) { nsCOMPtr uri; if (IsLink(getter_AddRefs(uri))) { diff --git a/content/svg/content/src/SVGAElement.h b/content/svg/content/src/SVGAElement.h index 3984d9f9d5e3..29728f0c21f5 100644 --- a/content/svg/content/src/SVGAElement.h +++ b/content/svg/content/src/SVGAElement.h @@ -43,7 +43,7 @@ public: virtual void UnbindFromTree(bool aDeep = true, bool aNullParent = true) MOZ_OVERRIDE; NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const MOZ_OVERRIDE; - virtual bool IsFocusable(int32_t *aTabIndex = nullptr, bool aWithMouse = false) MOZ_OVERRIDE; + virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) MOZ_OVERRIDE; virtual bool IsLink(nsIURI** aURI) const MOZ_OVERRIDE; virtual void GetLinkTarget(nsAString& aTarget) MOZ_OVERRIDE; virtual already_AddRefed GetHrefURI() const MOZ_OVERRIDE; diff --git a/content/xbl/src/moz.build b/content/xbl/src/moz.build index 1d7e0e900ef5..e8a93d589343 100644 --- a/content/xbl/src/moz.build +++ b/content/xbl/src/moz.build @@ -14,7 +14,7 @@ EXPORTS.mozilla.dom += [ 'XBLChildrenElement.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'nsBindingManager.cpp', 'nsXBLBinding.cpp', 'nsXBLContentSink.cpp', diff --git a/content/xbl/src/nsXBLDocumentInfo.cpp b/content/xbl/src/nsXBLDocumentInfo.cpp index b5932f9ce89b..5c304c26566e 100644 --- a/content/xbl/src/nsXBLDocumentInfo.cpp +++ b/content/xbl/src/nsXBLDocumentInfo.cpp @@ -33,9 +33,11 @@ #include "mozilla/scache/StartupCacheUtils.h" #include "nsCCUncollectableMarker.h" #include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/URL.h" -using namespace mozilla::scache; using namespace mozilla; +using namespace mozilla::scache; +using namespace mozilla::dom; static const char kXBLCachePrefix[] = "xblcache"; @@ -282,14 +284,6 @@ nsXBLDocGlobalObject::GetPrincipal() return document->NodePrincipal(); } -static bool IsChromeURI(nsIURI* aURI) -{ - bool isChrome = false; - if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome))) - return isChrome; - return false; -} - /* Implementation file */ static bool diff --git a/content/xbl/src/nsXBLPrototypeResources.cpp b/content/xbl/src/nsXBLPrototypeResources.cpp index 51ff4235b81c..d68d21ecba33 100644 --- a/content/xbl/src/nsXBLPrototypeResources.cpp +++ b/content/xbl/src/nsXBLPrototypeResources.cpp @@ -16,6 +16,9 @@ #include "nsLayoutCID.h" #include "nsCSSRuleProcessor.h" #include "nsStyleSet.h" +#include "mozilla/dom/URL.h" + +using mozilla::dom::IsChromeURI; nsXBLPrototypeResources::nsXBLPrototypeResources(nsXBLPrototypeBinding* aBinding) { @@ -57,14 +60,6 @@ nsXBLPrototypeResources::AddResourceListener(nsIContent* aBoundElement) mLoader->AddResourceListener(aBoundElement); } -static bool IsChromeURI(nsIURI* aURI) -{ - bool isChrome=false; - if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) - return true; - return false; -} - nsresult nsXBLPrototypeResources::FlushSkinSheets() { diff --git a/content/xbl/src/nsXBLResourceLoader.h b/content/xbl/src/nsXBLResourceLoader.h index 114fcd2fab03..ab7fe285232f 100644 --- a/content/xbl/src/nsXBLResourceLoader.h +++ b/content/xbl/src/nsXBLResourceLoader.h @@ -3,6 +3,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsXBLResourceLoader_h +#define nsXBLResourceLoader_h + #include "mozilla/Attributes.h" #include "nsCOMPtr.h" #include "nsICSSLoaderObserver.h" @@ -62,3 +65,4 @@ public: nsCOMArray mBoundElements; }; +#endif diff --git a/content/xul/content/src/nsXULElement.cpp b/content/xul/content/src/nsXULElement.cpp index edb0e1cd3331..4f158f2944ba 100644 --- a/content/xul/content/src/nsXULElement.cpp +++ b/content/xul/content/src/nsXULElement.cpp @@ -511,7 +511,7 @@ static bool IsNonList(nsINodeInfo* aNodeInfo) } bool -nsXULElement::IsFocusable(int32_t *aTabIndex, bool aWithMouse) +nsXULElement::IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse) { /* * Returns true if an element may be focused, and false otherwise. The inout diff --git a/content/xul/content/src/nsXULElement.h b/content/xul/content/src/nsXULElement.h index de91e633dd2e..f9a6cc20d895 100644 --- a/content/xul/content/src/nsXULElement.h +++ b/content/xul/content/src/nsXULElement.h @@ -396,7 +396,7 @@ public: virtual nsIContent *GetBindingParent() const MOZ_OVERRIDE; virtual bool IsNodeOfType(uint32_t aFlags) const MOZ_OVERRIDE; - virtual bool IsFocusable(int32_t *aTabIndex = nullptr, bool aWithMouse = false) MOZ_OVERRIDE; + virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) MOZ_OVERRIDE; NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) MOZ_OVERRIDE; virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute, diff --git a/content/xul/document/src/XULDocument.cpp b/content/xul/document/src/XULDocument.cpp index 88b7d9d5588c..0cd64b03ca58 100644 --- a/content/xul/document/src/XULDocument.cpp +++ b/content/xul/document/src/XULDocument.cpp @@ -38,7 +38,7 @@ #include "nsIRDFService.h" #include "nsIStreamListener.h" #include "nsITimer.h" -#include "nsIDocShell.h" +#include "nsDocShell.h" #include "nsGkAtoms.h" #include "nsXMLContentSink.h" #include "nsXULContentSink.h" @@ -90,6 +90,7 @@ #include "mozilla/Preferences.h" #include "nsTextNode.h" #include "nsJSUtils.h" +#include "mozilla/dom/URL.h" using namespace mozilla; using namespace mozilla::dom; @@ -101,14 +102,6 @@ using namespace mozilla::dom; static NS_DEFINE_CID(kParserCID, NS_PARSER_CID); -static bool IsChromeURI(nsIURI* aURI) -{ - bool isChrome = false; - if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) - return true; - return false; -} - static bool IsOverlayAllowed(nsIURI* aURI) { bool canOverlay = false; @@ -3187,8 +3180,7 @@ XULDocument::DoneWalking() // Before starting layout, check whether we're a toplevel chrome // window. If we are, set our chrome flags now, so that we don't have // to restyle the whole frame tree after StartLayout. - nsCOMPtr container = GetContainer(); - nsCOMPtr item = do_QueryInterface(container); + nsCOMPtr item = GetDocShell(); if (item) { nsCOMPtr owner; item->GetTreeOwner(getter_AddRefs(owner)); @@ -3196,7 +3188,7 @@ XULDocument::DoneWalking() if (xulWin) { nsCOMPtr xulWinShell; xulWin->GetDocShell(getter_AddRefs(xulWinShell)); - if (SameCOMIdentity(xulWinShell, container)) { + if (SameCOMIdentity(xulWinShell, item)) { // We're the chrome document! Apply our chrome flags now. xulWin->ApplyChromeFlags(); } @@ -4667,7 +4659,7 @@ XULDocument::ParserObserver::OnStopRequest(nsIRequest *request, already_AddRefed XULDocument::GetWindowRoot() { - nsCOMPtr ir = do_QueryReferent(mDocumentContainer); + nsCOMPtr ir(mDocumentContainer); nsCOMPtr window(do_GetInterface(ir)); nsCOMPtr piWin(do_QueryInterface(window)); return piWin ? piWin->GetTopWindowRoot() : nullptr; diff --git a/content/xul/document/src/moz.build b/content/xul/document/src/moz.build index 58485904db5f..4c1945575ea8 100644 --- a/content/xul/document/src/moz.build +++ b/content/xul/document/src/moz.build @@ -28,6 +28,7 @@ LOCAL_INCLUDES += [ '/content/xml/document/src', '/content/xul/content/src', '/content/xul/templates/src', + '/docshell/base', '/dom/base', '/layout/base', '/layout/generic', diff --git a/content/xul/templates/src/moz.build b/content/xul/templates/src/moz.build index 672f61178562..722d3a812ccd 100644 --- a/content/xul/templates/src/moz.build +++ b/content/xul/templates/src/moz.build @@ -4,7 +4,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -SOURCES += [ +UNIFIED_SOURCES += [ 'nsContentSupportMap.cpp', 'nsContentTestNode.cpp', 'nsInstantiationNode.cpp', diff --git a/content/xul/templates/src/nsXULSortService.h b/content/xul/templates/src/nsXULSortService.h index 18ca34e656a1..c003974ead8b 100644 --- a/content/xul/templates/src/nsXULSortService.h +++ b/content/xul/templates/src/nsXULSortService.h @@ -20,6 +20,9 @@ This sort service is used to sort template built content or content by attribute. */ +#ifndef nsXULTemplateResultSetRDF_h +#define nsXULTemplateResultSetRDF_h + #include "nsCOMPtr.h" #include "nsCOMArray.h" #include "nsTArray.h" @@ -179,3 +182,5 @@ public: const nsAString& aRight, uint32_t aSortHints); }; + +#endif diff --git a/content/xul/templates/src/nsXULTemplateBuilder.cpp b/content/xul/templates/src/nsXULTemplateBuilder.cpp index ff7ff8ee505b..f21d84073971 100644 --- a/content/xul/templates/src/nsXULTemplateBuilder.cpp +++ b/content/xul/templates/src/nsXULTemplateBuilder.cpp @@ -73,11 +73,6 @@ using namespace mozilla::dom; using namespace mozilla; -//---------------------------------------------------------------------- - -static NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); -static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); - //---------------------------------------------------------------------- // // nsXULTemplateBuilder @@ -107,6 +102,7 @@ nsXULTemplateBuilder::nsXULTemplateBuilder(void) mTop(nullptr), mObservedDocument(nullptr) { + MOZ_COUNT_CTOR(nsXULTemplateBuilder); } static PLDHashOperator @@ -133,6 +129,8 @@ nsXULTemplateBuilder::~nsXULTemplateBuilder(void) NS_IF_RELEASE(gScriptSecurityManager); NS_IF_RELEASE(gObserverService); } + + MOZ_COUNT_DTOR(nsXULTemplateBuilder); } @@ -144,10 +142,12 @@ nsXULTemplateBuilder::InitGlobals() if (gRefCnt++ == 0) { // Initialize the global shared reference to the service // manager and get some shared resource objects. + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); rv = CallGetService(kRDFServiceCID, &gRDFService); if (NS_FAILED(rv)) return rv; + NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils); if (NS_FAILED(rv)) return rv; @@ -174,6 +174,23 @@ nsXULTemplateBuilder::InitGlobals() return NS_OK; } +void +nsXULTemplateBuilder::StartObserving(nsIDocument* aDocument) +{ + aDocument->AddObserver(this); + mObservedDocument = aDocument; + gObserverService->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, false); +} + +void +nsXULTemplateBuilder::StopObserving() +{ + MOZ_ASSERT(mObservedDocument); + mObservedDocument->RemoveObserver(this); + mObservedDocument = nullptr; + gObserverService->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); +} + void nsXULTemplateBuilder::CleanUp(bool aIsFinal) { @@ -196,10 +213,7 @@ void nsXULTemplateBuilder::Uninit(bool aIsFinal) { if (mObservedDocument && aIsFinal) { - gObserverService->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); - gObserverService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); - mObservedDocument->RemoveObserver(this); - mObservedDocument = nullptr; + StopObserving(); } if (mQueryProcessor) @@ -431,14 +445,7 @@ nsXULTemplateBuilder::Init(nsIContent* aElement) nsresult rv = LoadDataSources(doc, &shouldDelay); if (NS_SUCCEEDED(rv)) { - // Add ourselves as a document observer - doc->AddObserver(this); - - mObservedDocument = doc; - gObserverService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, - false); - gObserverService->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, - false); + StartObserving(doc); } return rv; @@ -1076,8 +1083,6 @@ nsXULTemplateBuilder::Observe(nsISupports* aSubject, if (doc && doc == mObservedDocument) NodeWillBeDestroyed(doc); } - } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { - UninitTrue(); } return NS_OK; } @@ -1127,7 +1132,8 @@ nsXULTemplateBuilder::ContentRemoved(nsIDocument* aDocument, nsContentUtils::AddScriptRunner( NS_NewRunnableMethod(this, &nsXULTemplateBuilder::UninitFalse)); - aDocument->RemoveObserver(this); + MOZ_ASSERT(aDocument == mObservedDocument); + StopObserving(); nsCOMPtr xuldoc = do_QueryInterface(aDocument); if (xuldoc) diff --git a/content/xul/templates/src/nsXULTemplateBuilder.h b/content/xul/templates/src/nsXULTemplateBuilder.h index f72b190f7b0d..9f3d839b98f6 100644 --- a/content/xul/templates/src/nsXULTemplateBuilder.h +++ b/content/xul/templates/src/nsXULTemplateBuilder.h @@ -480,6 +480,20 @@ protected: { } + /** + * Start observing events from the observer service and the given + * document. + * + * @param aDocument the document to observe + */ + void StartObserving(nsIDocument* aDocument); + + /** + * Stop observing events from the observer service and any associated + * document. + */ + void StopObserving(); + /** * Document that we're observing. Weak ref! */ diff --git a/content/xul/templates/src/nsXULTemplateQueryProcessorRDF.cpp b/content/xul/templates/src/nsXULTemplateQueryProcessorRDF.cpp index 3909840aebad..6f02e0c3c5ec 100644 --- a/content/xul/templates/src/nsXULTemplateQueryProcessorRDF.cpp +++ b/content/xul/templates/src/nsXULTemplateQueryProcessorRDF.cpp @@ -37,9 +37,6 @@ //---------------------------------------------------------------------- -static NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); -static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); - #define PARSE_TYPE_INTEGER "Integer" nsrefcnt nsXULTemplateQueryProcessorRDF::gRefCnt = 0; @@ -145,12 +142,14 @@ nsXULTemplateQueryProcessorRDF::InitGlobals() // Initialize the global shared reference to the service // manager and get some shared resource objects. if (!gRDFService) { + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); rv = CallGetService(kRDFServiceCID, &gRDFService); if (NS_FAILED(rv)) return rv; } if (!gRDFContainerUtils) { + NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils); if (NS_FAILED(rv)) return rv; diff --git a/docshell/base/moz.build b/docshell/base/moz.build index 48f17f95e9f6..194cebba8d8c 100644 --- a/docshell/base/moz.build +++ b/docshell/base/moz.build @@ -47,7 +47,7 @@ EXPORTS.mozilla += [ 'LoadContext.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'LoadContext.cpp', 'nsAboutRedirector.cpp', 'nsDefaultURIFixup.cpp', diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 6f18217b93b7..4300f0e314b1 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -7259,7 +7259,7 @@ nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, // got Reset() with a channel. blankDoc->SetBaseURI(aBaseURI); - blankDoc->SetContainer(static_cast(this)); + blankDoc->SetContainer(this); // Copy our sandbox flags to the document. These are immutable // after being set here. @@ -7271,7 +7271,7 @@ nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, // hook 'em up if (viewer) { - viewer->SetContainer(static_cast(this)); + viewer->SetContainer(this); Embed(viewer, "", 0); SetCurrentURI(blankDoc->GetDocumentURI(), nullptr, true, 0); @@ -7607,7 +7607,7 @@ nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, bool *aRestoring) // different one. We don't currently support restoring the presentation // in that case. - nsCOMPtr container; + nsCOMPtr container; viewer->GetContainer(getter_AddRefs(container)); if (!::SameCOMIdentity(container, GetAsSupports(this))) { #ifdef DEBUG_PAGE_CACHE @@ -8333,13 +8333,13 @@ nsDocShell::NewContentViewerObj(const char *aContentType, nsresult rv = docLoaderFactory->CreateInstance("view", aOpenedChannel, aLoadGroup, aContentType, - static_cast(this), + this, nullptr, aContentHandler, aViewer); NS_ENSURE_SUCCESS(rv, rv); - (*aViewer)->SetContainer(static_cast(this)); + (*aViewer)->SetContainer(this); return NS_OK; } diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index 650011cc2325..32a59c5a82c3 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -18,6 +18,7 @@ #include "nsIContentViewerContainer.h" #include "nsIDOMStorageManager.h" #include "nsDocLoader.h" +#include "mozilla/WeakPtr.h" // Helper Classes #include "nsCOMPtr.h" @@ -137,7 +138,8 @@ class nsDocShell : public nsDocLoader, public nsIWebShellServices, public nsILinkHandler, public nsIClipboardCommands, - public nsIDOMStorageManager + public nsIDOMStorageManager, + public mozilla::SupportsWeakPtr { friend class nsDSURIContentListener; diff --git a/docshell/base/nsDocShellEnumerator.h b/docshell/base/nsDocShellEnumerator.h index d3c3d9040619..523050218248 100644 --- a/docshell/base/nsDocShellEnumerator.h +++ b/docshell/base/nsDocShellEnumerator.h @@ -4,13 +4,15 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsDocShellEnumerator_h___ +#define nsDocShellEnumerator_h___ + #include "nsISimpleEnumerator.h" #include "nsTArray.h" #include "nsIWeakReferenceUtils.h" class nsIDocShellTreeItem; - /* // {13cbc281-35ae-11d5-be5b-bde0edece43c} #define NS_DOCSHELL_FORWARDS_ENUMERATOR_CID \ @@ -105,5 +107,6 @@ public: protected: virtual nsresult BuildArrayRecursive(nsIDocShellTreeItem* inItem, nsTArray& inItemArray); - }; + +#endif // nsDocShellEnumerator_h___ diff --git a/docshell/base/nsIContentViewer.idl b/docshell/base/nsIContentViewer.idl index 6610ab66e5f0..1fba74fbbc18 100644 --- a/docshell/base/nsIContentViewer.idl +++ b/docshell/base/nsIContentViewer.idl @@ -4,6 +4,7 @@ #include "nsISupports.idl" +interface nsIDocShell; interface nsIDOMDocument; interface nsISHEntry; interface nsIPrintSettings; @@ -34,7 +35,7 @@ interface nsIContentViewer : nsISupports [noscript] void init(in nsIWidgetPtr aParentWidget, [const] in nsIntRectRef aBounds); - attribute nsISupports container; + attribute nsIDocShell container; void loadStart(in nsISupports aDoc); void loadComplete(in nsresult aStatus); diff --git a/docshell/base/nsIDocumentLoaderFactory.idl b/docshell/base/nsIDocumentLoaderFactory.idl index caacfcd58436..904882c836cf 100644 --- a/docshell/base/nsIDocumentLoaderFactory.idl +++ b/docshell/base/nsIDocumentLoaderFactory.idl @@ -9,6 +9,7 @@ interface nsIChannel; interface nsIContentViewer; interface nsIStreamListener; +interface nsIDocShell; interface nsIDocument; interface nsILoadGroup; interface nsIPrincipal; @@ -21,13 +22,13 @@ interface nsIPrincipal; * The component is a service, so use GetService, not CreateInstance to get it. */ -[scriptable, uuid(5e7d2967-5a07-444f-95d5-25b533252d38)] +[scriptable, uuid(70905274-8494-4e39-b011-d559adde3733)] interface nsIDocumentLoaderFactory : nsISupports { nsIContentViewer createInstance(in string aCommand, in nsIChannel aChannel, in nsILoadGroup aLoadGroup, in string aContentType, - in nsISupports aContainer, + in nsIDocShell aContainer, in nsISupports aExtraInfo, out nsIStreamListener aDocListenerResult); diff --git a/docshell/shistory/src/moz.build b/docshell/shistory/src/moz.build index ce07450b34bd..462fce8f84c4 100644 --- a/docshell/shistory/src/moz.build +++ b/docshell/shistory/src/moz.build @@ -8,7 +8,7 @@ EXPORTS += [ 'nsSHEntryShared.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'nsSHEntry.cpp', 'nsSHEntryShared.cpp', 'nsSHistory.cpp', diff --git a/docshell/shistory/src/nsSHEntryShared.cpp b/docshell/shistory/src/nsSHEntryShared.cpp index 5d44492fc8a3..e24a6010d5c4 100644 --- a/docshell/shistory/src/nsSHEntryShared.cpp +++ b/docshell/shistory/src/nsSHEntryShared.cpp @@ -10,6 +10,7 @@ #include "nsIDocument.h" #include "nsIWebNavigation.h" #include "nsIContentViewer.h" +#include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsDocShellEditorData.h" #include "nsThreadUtils.h" @@ -167,7 +168,7 @@ nsSHEntryShared::Expire() if (!mContentViewer) { return; } - nsCOMPtr container; + nsCOMPtr container; mContentViewer->GetContainer(getter_AddRefs(container)); nsCOMPtr treeItem = do_QueryInterface(container); if (!treeItem) { diff --git a/dom/alarm/AlarmsManager.js b/dom/alarm/AlarmsManager.js index 38a9aea73a02..fe1ebd3e1be2 100644 --- a/dom/alarm/AlarmsManager.js +++ b/dom/alarm/AlarmsManager.js @@ -37,7 +37,8 @@ AlarmsManager.prototype = { QueryInterface : XPCOMUtils.generateQI([nsIDOMMozAlarmsManager, Ci.nsIDOMGlobalPropertyInitializer, - Ci.nsISupportsWeakReference]), + Ci.nsISupportsWeakReference, + Ci.nsIObserver]), classInfo : XPCOMUtils.generateCI({ classID: ALARMSMANAGER_CID, contractID: ALARMSMANAGER_CONTRACTID, diff --git a/dom/apps/src/InterAppConnection.js b/dom/apps/src/InterAppConnection.js index 6edb91029680..aa6bf55315ad 100644 --- a/dom/apps/src/InterAppConnection.js +++ b/dom/apps/src/InterAppConnection.js @@ -44,7 +44,8 @@ InterAppConnection.prototype = { contractID: "@mozilla.org/dom/inter-app-connection;1", QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer, - Ci.nsISupportsWeakReference]), + Ci.nsISupportsWeakReference, + Ci.nsIObserver]), __init: function(aKeyword, aPublisher, aSubscriber) { if (DEBUG) { diff --git a/dom/apps/src/InterAppMessagePort.js b/dom/apps/src/InterAppMessagePort.js index 377aac439997..9364f998a162 100644 --- a/dom/apps/src/InterAppMessagePort.js +++ b/dom/apps/src/InterAppMessagePort.js @@ -47,7 +47,8 @@ InterAppMessagePort.prototype = { contractID: "@mozilla.org/dom/inter-app-message-port;1", QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer, - Ci.nsISupportsWeakReference]), + Ci.nsISupportsWeakReference, + Ci.nsIObserver]), // Ci.nsIDOMGlobalPropertyInitializer implementation. init: function(aWindow) { diff --git a/dom/apps/src/Webapps.js b/dom/apps/src/Webapps.js index f007b5f81696..a57c0490499f 100644 --- a/dom/apps/src/Webapps.js +++ b/dom/apps/src/Webapps.js @@ -248,6 +248,7 @@ WebappsRegistry.prototype = { classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, + Ci.nsIObserver, Ci.mozIDOMApplicationRegistry, Ci.mozIDOMApplicationRegistry2, Ci.nsIDOMGlobalPropertyInitializer]), @@ -634,7 +635,8 @@ WebappsApplication.prototype = { classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"), QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplication, - Ci.nsISupportsWeakReference]), + Ci.nsISupportsWeakReference, + Ci.nsIObserver]), classInfo: XPCOMUtils.generateCI({classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"), contractID: "@mozilla.org/webapps/application;1", @@ -789,7 +791,9 @@ WebappsApplicationMgmt.prototype = { classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"), - QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationMgmt, Ci.nsISupportsWeakReference]), + QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationMgmt, + Ci.nsISupportsWeakReference, + Ci.nsIObserver]), classInfo: XPCOMUtils.generateCI({classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"), contractID: "@mozilla.org/webapps/application-mgmt;1", diff --git a/dom/base/DOMError.cpp b/dom/base/DOMError.cpp index e1b101d39460..8da1b9ab7918 100644 --- a/dom/base/DOMError.cpp +++ b/dom/base/DOMError.cpp @@ -6,14 +6,9 @@ #include "mozilla/dom/DOMError.h" #include "mozilla/dom/DOMErrorBinding.h" +#include "mozilla/dom/DOMException.h" #include "nsPIDOMWindow.h" -// Implemented in DOMException.cpp -nsresult -NS_GetNameAndMessageForDOMNSResult(nsresult aNSResult, const char** aName, - const char** aMessage, - uint16_t* aCode = nullptr); - namespace mozilla { namespace dom { diff --git a/dom/base/DOMRequestHelper.jsm b/dom/base/DOMRequestHelper.jsm index 0184999b9419..ec8f239fda0b 100644 --- a/dom/base/DOMRequestHelper.jsm +++ b/dom/base/DOMRequestHelper.jsm @@ -141,6 +141,11 @@ DOMRequestIpcHelper.prototype = { * will be added as a weak referred one by default. */ initDOMRequestHelper: function(aWindow, aMessages) { + // Query our required interfaces to force a fast fail if they are not + // provided. These calls will throw if the interface is not available. + this.QueryInterface(Ci.nsISupportsWeakReference); + this.QueryInterface(Ci.nsIObserver); + if (aMessages) { this.addMessageListeners(aMessages); } @@ -157,7 +162,8 @@ DOMRequestIpcHelper.prototype = { this._destroyed = false; - Services.obs.addObserver(this, "inner-window-destroyed", false); + Services.obs.addObserver(this, "inner-window-destroyed", + /* weak-ref */ true); }, destroyDOMRequestHelper: function() { diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index 81263a7bd02e..a74da5d17a38 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -1236,8 +1236,7 @@ Navigator::GetMozIccManager(ErrorResult& aRv) } NS_ENSURE_TRUE(mWindow->GetDocShell(), nullptr); - mIccManager = new IccManager(); - mIccManager->Init(mWindow); + mIccManager = new IccManager(mWindow); } return mIccManager; diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index 5b21ba15e33b..3278de16e49a 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -142,23 +142,6 @@ public: // Helper to initialize mMessagesManager. nsresult EnsureMessagesManager(); - // WebIDL API - void GetAppName(nsString& aAppName) - { - NS_GetNavigatorAppName(aAppName); - } - void GetAppVersion(nsString& aAppVersion, ErrorResult& aRv) - { - aRv = GetAppVersion(aAppVersion); - } - void GetPlatform(nsString& aPlatform, ErrorResult& aRv) - { - aRv = GetPlatform(aPlatform); - } - void GetUserAgent(nsString& aUserAgent, ErrorResult& aRv) - { - aRv = GetUserAgent(aUserAgent); - } // The XPCOM GetProduct is OK // The XPCOM GetLanguage is OK bool OnLine(); diff --git a/dom/base/URL.cpp b/dom/base/URL.cpp index 3dbafcaf9f4b..f9f521e98942 100644 --- a/dom/base/URL.cpp +++ b/dom/base/URL.cpp @@ -409,5 +409,13 @@ URL::SetHash(const nsAString& aHash) mURI->SetRef(NS_ConvertUTF16toUTF8(aHash)); } +bool IsChromeURI(nsIURI* aURI) +{ + bool isChrome = false; + if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome))) + return isChrome; + return false; +} + } } diff --git a/dom/base/URL.h b/dom/base/URL.h index 1c80d6f6f184..762f4e292e67 100644 --- a/dom/base/URL.h +++ b/dom/base/URL.h @@ -130,6 +130,8 @@ private: friend class mozilla::dom::workers::URLProxy; }; +bool IsChromeURI(nsIURI* aURI); + } } diff --git a/dom/base/moz.build b/dom/base/moz.build index 333a95984830..2f4a58e0fead 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -63,7 +63,7 @@ EXPORTS.mozilla.dom += [ 'URL.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'BarProps.cpp', 'CompositionStringSynthesizer.cpp', 'Crypto.cpp', @@ -72,7 +72,6 @@ SOURCES += [ 'DOMException.cpp', 'DOMRequest.cpp', 'MessageChannel.cpp', - 'MessagePort.cpp', 'MessagePortList.cpp', 'Navigator.cpp', 'nsContentPermissionHelper.cpp', @@ -80,9 +79,7 @@ SOURCES += [ 'nsDOMNavigationTiming.cpp', 'nsDOMScriptObjectFactory.cpp', 'nsDOMWindowList.cpp', - 'nsDOMWindowUtils.cpp', 'nsFocusManager.cpp', - 'nsGlobalWindow.cpp', 'nsGlobalWindowCommands.cpp', 'nsHistory.cpp', 'nsJSEnvironment.cpp', @@ -91,7 +88,6 @@ SOURCES += [ 'nsLocation.cpp', 'nsMimeTypeArray.cpp', 'nsPerformance.cpp', - 'nsPluginArray.cpp', 'nsQueryContentEventResult.cpp', 'nsScreen.cpp', 'nsScriptNameSpaceManager.cpp', @@ -103,6 +99,18 @@ SOURCES += [ 'WindowNamedPropertiesHandler.cpp', ] +# these files couldn't be in UNIFIED_SOURCES for now for reasons given below: +SOURCES += [ + # this file doesn't like windows.h + 'MessagePort.cpp', + # this file doesn't like windows.h + 'nsDOMWindowUtils.cpp', + # This file has a #error "Never include windows.h in this file!" + 'nsGlobalWindow.cpp', + # nsPluginArray.cpp includes npapi.h indirectly, and that includes a lot of system headers + 'nsPluginArray.cpp', +] + EXTRA_COMPONENTS += [ 'ConsoleAPI.js', 'ConsoleAPI.manifest', diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp index a3f7d1bccfa9..a71ac4ab401c 100644 --- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp @@ -1396,10 +1396,12 @@ nsFocusManager::IsNonFocusableRoot(nsIContent* aContent) // focusable. // Also, if aContent is not editable but it isn't in designMode, it's not // focusable. + // And in userfocusignored context nothing is focusable. nsIDocument* doc = aContent->GetCurrentDoc(); NS_ASSERTION(doc, "aContent must have current document"); return aContent == doc->GetRootElement() && - (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable()); + (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable() || + nsContentUtils::IsUserFocusIgnored(aContent)); } nsIContent* @@ -1428,9 +1430,10 @@ nsFocusManager::CheckIfFocusable(nsIContent* aContent, uint32_t aFlags) if (!shell) return nullptr; - // the root content can always be focused + // the root content can always be focused, + // except in userfocusignored context. if (aContent == doc->GetRootElement()) - return aContent; + return nsContentUtils::IsUserFocusIgnored(aContent) ? nullptr : aContent; // cannot focus content in print preview mode. Only the root can be focused. nsPresContext* presContext = shell->GetPresContext(); @@ -1881,10 +1884,18 @@ nsFocusManager::SendFocusOrBlurEvent(uint32_t aType, nsCOMPtr eventTarget = do_QueryInterface(aTarget); + nsCOMPtr n = do_QueryInterface(aTarget); + if (!n) { + nsCOMPtr win = do_QueryInterface(aTarget); + n = win ? win->GetExtantDoc() : nullptr; + } + bool dontDispatchEvent = n && nsContentUtils::IsUserFocusIgnored(n); + // for focus events, if this event was from a mouse or key and event // handling on the document is suppressed, queue the event and fire it // later. For blur events, a non-zero value would be set for aFocusMethod. - if (aFocusMethod && aDocument && aDocument->EventHandlingSuppressed()) { + if (aFocusMethod && !dontDispatchEvent && + aDocument && aDocument->EventHandlingSuppressed()) { // aFlags is always 0 when aWindowRaised is true so this won't be called // on a window raise. NS_ASSERTION(!aWindowRaised, "aWindowRaised should not be set"); @@ -1914,9 +1925,11 @@ nsFocusManager::SendFocusOrBlurEvent(uint32_t aType, } #endif - nsContentUtils::AddScriptRunner( - new FocusBlurEvent(aTarget, aType, aPresShell->GetPresContext(), - aWindowRaised, aIsRefocus)); + if (!dontDispatchEvent) { + nsContentUtils::AddScriptRunner( + new FocusBlurEvent(aTarget, aType, aPresShell->GetPresContext(), + aWindowRaised, aIsRefocus)); + } } void diff --git a/dom/base/nsHistory.cpp b/dom/base/nsHistory.cpp index 0d0f768fc48b..6660790e487e 100644 --- a/dom/base/nsHistory.cpp +++ b/dom/base/nsHistory.cpp @@ -70,7 +70,7 @@ uint32_t nsHistory::GetLength(ErrorResult& aRv) const { nsCOMPtr win(do_QueryReferent(mInnerWindow)); - if (!win || !nsContentUtils::CanCallerAccess(win->GetOuterWindow())) { + if (!win || !win->IsCurrentInnerWindow()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return 0; @@ -106,7 +106,7 @@ nsHistory::GetState(JSContext* aCx, ErrorResult& aRv) const return JS::UndefinedValue(); } - if (!nsContentUtils::CanCallerAccess(win->GetOuterWindow())) { + if (!win->IsCurrentInnerWindow()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return JS::UndefinedValue(); @@ -146,7 +146,7 @@ void nsHistory::Go(int32_t aDelta, ErrorResult& aRv) { nsCOMPtr win(do_QueryReferent(mInnerWindow)); - if (!win || !nsContentUtils::CanCallerAccess(win->GetOuterWindow())) { + if (!win || !win->IsCurrentInnerWindow()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; @@ -202,7 +202,7 @@ void nsHistory::Back(ErrorResult& aRv) { nsCOMPtr win(do_QueryReferent(mInnerWindow)); - if (!win || !nsContentUtils::CanCallerAccess(win->GetOuterWindow())) { + if (!win || !win->IsCurrentInnerWindow()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; @@ -223,7 +223,7 @@ void nsHistory::Forward(ErrorResult& aRv) { nsCOMPtr win(do_QueryReferent(mInnerWindow)); - if (!win || !nsContentUtils::CanCallerAccess(win->GetOuterWindow())) { + if (!win || !win->IsCurrentInnerWindow()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; @@ -268,7 +268,7 @@ nsHistory::PushOrReplaceState(JSContext* aCx, JS::Handle aData, return; } - if (!nsContentUtils::CanCallerAccess(win->GetOuterWindow())) { + if (!win->IsCurrentInnerWindow()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 031631179f17..a93df5fb2fde 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -55,6 +55,7 @@ #include "mozilla/dom/ImageData.h" #include "mozilla/dom/ImageDataBinding.h" #include "nsAXPCNativeCallContext.h" +#include "mozilla/CycleCollectedJSRuntime.h" #include "nsJSPrincipals.h" @@ -1977,53 +1978,131 @@ TimeBetween(PRTime start, PRTime end) return (uint32_t)(end - start) / PR_USEC_PER_MSEC; } +struct CycleCollectorStats +{ + void Clear() + { + mBeginSliceTime = 0; + mBeginTime = 0; + mMaxGCDuration = 0; + mRanSyncForgetSkippable = false; + mSuspected = 0; + mMaxSkippableDuration = 0; + mAnyLockedOut = false; + } + + // Time the current slice began, including any GC finishing. + PRTime mBeginSliceTime; + + // Time the current cycle collection began. + PRTime mBeginTime; + + // The longest GC finishing duration for any slice of the current CC. + uint32_t mMaxGCDuration; + + // True if we ran sync forget skippable in any slice of the current CC. + bool mRanSyncForgetSkippable; + + // Number of suspected objects at the start of the current CC. + uint32_t mSuspected; + + // The longest duration spent on sync forget skippable in any slice of the + // current CC + uint32_t mMaxSkippableDuration; + + // True if we were locked out by the GC in any slice of the current CC. + bool mAnyLockedOut; +}; + +CycleCollectorStats gCCStats; + + +static void +PrepareForCycleCollection(int32_t aExtraForgetSkippableCalls = 0) +{ + gCCStats.mBeginSliceTime = PR_Now(); + + // Before we begin the cycle collection, make sure there is no active GC. + PRTime endGCTime; + if (sCCLockedOut) { + gCCStats.mAnyLockedOut = true; + FinishAnyIncrementalGC(); + endGCTime = PR_Now(); + uint32_t gcTime = TimeBetween(gCCStats.mBeginSliceTime, endGCTime); + gCCStats.mMaxGCDuration = std::max(gCCStats.mMaxGCDuration, gcTime); + } else { + endGCTime = gCCStats.mBeginSliceTime; + } + + // Run forgetSkippable synchronously to reduce the size of the CC graph. This + // is particularly useful if we recently finished a GC. + if (aExtraForgetSkippableCalls >= 0) { + bool ranSyncForgetSkippable = false; + while (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS) { + FireForgetSkippable(nsCycleCollector_suspectedCount(), false); + ranSyncForgetSkippable = true; + } + + for (int32_t i = 0; i < aExtraForgetSkippableCalls; ++i) { + FireForgetSkippable(nsCycleCollector_suspectedCount(), false); + ranSyncForgetSkippable = true; + } + + if (ranSyncForgetSkippable) { + gCCStats.mMaxSkippableDuration = + std::max(gCCStats.mMaxSkippableDuration, TimeBetween(endGCTime, PR_Now())); + gCCStats.mRanSyncForgetSkippable = true; + } + + } +} + //static void nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener, - int32_t aExtraForgetSkippableCalls, - bool aManuallyTriggered) + int32_t aExtraForgetSkippableCalls) { if (!NS_IsMainThread()) { return; } PROFILER_LABEL("CC", "CycleCollectNow"); + PrepareForCycleCollection(aExtraForgetSkippableCalls); + nsCycleCollector_collect(aListener); +} - PRTime start = PR_Now(); +//static +void +nsJSContext::ScheduledCycleCollectNow() +{ + if (!NS_IsMainThread()) { + return; + } - // Before we begin the cycle collection, make sure there is no active GC. - bool finishedIGC = sCCLockedOut; - FinishAnyIncrementalGC(); - PRTime endGCTime = PR_Now(); - uint32_t gcDuration = TimeBetween(start, endGCTime); + PROFILER_LABEL("CC", "ScheduledCycleCollectNow"); + PrepareForCycleCollection(); + nsCycleCollector_scheduledCollect(); +} + +//static +void +nsJSContext::BeginCycleCollectionCallback() +{ + MOZ_ASSERT(NS_IsMainThread()); + + gCCStats.mBeginTime = gCCStats.mBeginSliceTime ? gCCStats.mBeginSliceTime : PR_Now(); + gCCStats.mSuspected = nsCycleCollector_suspectedCount(); KillCCTimer(); +} - uint32_t suspected = nsCycleCollector_suspectedCount(); - bool ranSyncForgetSkippable = false; +//static +void +nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults) +{ + MOZ_ASSERT(NS_IsMainThread()); - // Run forgetSkippable synchronously to reduce the size of the CC graph. This - // is particularly useful if we recently finished a GC. - if (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS && - aExtraForgetSkippableCalls >= 0) { - while (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS) { - FireForgetSkippable(nsCycleCollector_suspectedCount(), false); - ranSyncForgetSkippable = true; - } - } - - for (int32_t i = 0; i < aExtraForgetSkippableCalls; ++i) { - FireForgetSkippable(nsCycleCollector_suspectedCount(), false); - ranSyncForgetSkippable = true; - } - - PRTime endSkippableTime = PR_Now(); - uint32_t skippableDuration = TimeBetween(endGCTime, endSkippableTime); - - // Prepare to actually run the CC. - nsCycleCollectorResults ccResults; - nsCycleCollector_collect(aManuallyTriggered, &ccResults, aListener); - sCCollectedWaitingForGC += ccResults.mFreedRefCounted + ccResults.mFreedGCed; + sCCollectedWaitingForGC += aResults.mFreedRefCounted + aResults.mFreedGCed; // If we collected a substantial amount of cycles, poke the GC since more objects // might be unreachable now. @@ -2036,13 +2115,13 @@ nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener, PRTime endCCTime = PR_Now(); // Log information about the CC via telemetry, JSON and the console. - uint32_t ccNowDuration = TimeBetween(start, endCCTime); - Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, finishedIGC); - Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE, ranSyncForgetSkippable); + uint32_t ccNowDuration = TimeBetween(gCCStats.mBeginTime, endCCTime); + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, gCCStats.mAnyLockedOut); + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE, gCCStats.mRanSyncForgetSkippable); Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL, ccNowDuration); if (sLastCCEndTime) { - uint32_t timeBetween = (uint32_t)(start - sLastCCEndTime) / PR_USEC_PER_SEC; + uint32_t timeBetween = TimeBetween(sLastCCEndTime, gCCStats.mBeginTime); Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN, timeBetween); } sLastCCEndTime = endCCTime; @@ -2058,23 +2137,23 @@ nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener, if (sPostGCEventsToConsole) { nsCString mergeMsg; - if (ccResults.mMergedZones) { + if (aResults.mMergedZones) { mergeMsg.AssignLiteral(" merged"); } nsCString gcMsg; - if (ccResults.mForcedGC) { + if (aResults.mForcedGC) { gcMsg.AssignLiteral(", forced a GC"); } NS_NAMED_MULTILINE_LITERAL_STRING(kFmt, MOZ_UTF16("CC(T+%.1f) duration: %lums, suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu RCed and %lu GCed (%lu|%lu waiting for GC)%s\n") - MOZ_UTF16("ForgetSkippable %lu times before CC, min: %lu ms, max: %lu ms, avg: %lu ms, total: %lu ms, sync: %lu ms, removed: %lu")); + MOZ_UTF16("ForgetSkippable %lu times before CC, min: %lu ms, max: %lu ms, avg: %lu ms, total: %lu ms, max sync: %lu ms, removed: %lu")); nsString msg; msg.Adopt(nsTextFormatter::smprintf(kFmt.get(), double(delta) / PR_USEC_PER_SEC, - ccNowDuration, suspected, - ccResults.mVisitedRefCounted, ccResults.mVisitedGCed, mergeMsg.get(), - ccResults.mFreedRefCounted, ccResults.mFreedGCed, + ccNowDuration, gCCStats.mSuspected, + aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(), + aResults.mFreedRefCounted, aResults.mFreedGCed, sCCollectedWaitingForGC, sLikelyShortLivingObjectsNeedingGC, gcMsg.get(), sForgetSkippableBeforeCC, @@ -2083,7 +2162,7 @@ nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener, (sTotalForgetSkippableTime / cleanups) / PR_USEC_PER_MSEC, sTotalForgetSkippableTime / PR_USEC_PER_MSEC, - skippableDuration, sRemovedPurples)); + gCCStats.mMaxSkippableDuration, sRemovedPurples)); nsCOMPtr cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); if (cs) { @@ -2095,8 +2174,8 @@ nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener, NS_NAMED_MULTILINE_LITERAL_STRING(kJSONFmt, MOZ_UTF16("{ \"timestamp\": %llu, ") MOZ_UTF16("\"duration\": %llu, ") - MOZ_UTF16("\"finish_gc_duration\": %llu, ") - MOZ_UTF16("\"sync_skippable_duration\": %llu, ") + MOZ_UTF16("\"max_finish_gc_duration\": %llu, ") + MOZ_UTF16("\"max_sync_skippable_duration\": %llu, ") MOZ_UTF16("\"suspected\": %lu, ") MOZ_UTF16("\"visited\": { ") MOZ_UTF16("\"RCed\": %lu, ") @@ -2117,13 +2196,14 @@ nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener, MOZ_UTF16("}")); nsString json; json.Adopt(nsTextFormatter::smprintf(kJSONFmt.get(), endCCTime, - ccNowDuration, gcDuration, skippableDuration, - suspected, - ccResults.mVisitedRefCounted, ccResults.mVisitedGCed, - ccResults.mFreedRefCounted, ccResults.mFreedGCed, + ccNowDuration, gCCStats.mMaxGCDuration, + gCCStats.mMaxSkippableDuration, + gCCStats.mSuspected, + aResults.mVisitedRefCounted, aResults.mVisitedGCed, + aResults.mFreedRefCounted, aResults.mFreedGCed, sCCollectedWaitingForGC, sLikelyShortLivingObjectsNeedingGC, - ccResults.mForcedGC, + aResults.mForcedGC, sForgetSkippableBeforeCC, minForgetSkippableTime / PR_USEC_PER_MSEC, sMaxForgetSkippableTime / PR_USEC_PER_MSEC, @@ -2145,6 +2225,7 @@ nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener, sForgetSkippableBeforeCC = 0; sNeedsFullCC = false; sNeedsGCAfterCC = false; + gCCStats.Clear(); } // static @@ -2235,7 +2316,7 @@ CCTimerFired(nsITimer *aTimer, void *aClosure) // We are in the final timer fire and still meet the conditions for // triggering a CC. Let CycleCollectNow finish the current IGC, if any, // because that will allow us to include the GC time in the CC pause. - nsJSContext::CycleCollectNow(nullptr, 0, false); + nsJSContext::ScheduledCycleCollectNow(); } } else if ((sPreviousSuspectedCount + 100) <= suspected) { // Only do a forget skippable if there are more than a few new objects. @@ -2600,6 +2681,7 @@ mozilla::dom::StartupJSEnvironment() sShuttingDown = false; sContextCount = 0; sSecurityManager = nullptr; + gCCStats.Clear(); } static int diff --git a/dom/base/nsJSEnvironment.h b/dom/base/nsJSEnvironment.h index d24b87d2b545..40acd2813888 100644 --- a/dom/base/nsJSEnvironment.h +++ b/dom/base/nsJSEnvironment.h @@ -24,6 +24,7 @@ class nsCycleCollectionNoteRootCallback; namespace mozilla { template class Maybe; +struct CycleCollectorResults; } // The amount of time we wait between a request to GC (due to leaving @@ -103,8 +104,10 @@ public: // If aExtraForgetSkippableCalls is -1, forgetSkippable won't be // called even if the previous collection was GC. static void CycleCollectNow(nsICycleCollectorListener *aListener = nullptr, - int32_t aExtraForgetSkippableCalls = 0, - bool aManuallyTriggered = true); + int32_t aExtraForgetSkippableCalls = 0); + static void ScheduledCycleCollectNow(); + static void BeginCycleCollectionCallback(); + static void EndCycleCollectionCallback(mozilla::CycleCollectorResults &aResults); static void PokeGC(JS::gcreason::Reason aReason, int aDelay = 0); static void KillGCTimer(); diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp index abf4c8e8cb3d..ebdc7711f1b4 100644 --- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -26,6 +26,7 @@ #include "nsJSPrincipals.h" #include "xpcpublic.h" #include "nsContentUtils.h" +#include "nsGlobalWindow.h" bool nsJSUtils::GetCallingLocation(JSContext* aContext, const char* *aFilename, @@ -47,46 +48,9 @@ nsJSUtils::GetCallingLocation(JSContext* aContext, const char* *aFilename, nsIScriptGlobalObject * nsJSUtils::GetStaticScriptGlobal(JSObject* aObj) { - const JSClass* clazz; - JSObject* glob = aObj; // starting point for search - - if (!glob) + if (!aObj) return nullptr; - - glob = js::GetGlobalForObjectCrossCompartment(glob); - NS_ABORT_IF_FALSE(glob, "Infallible returns null"); - - clazz = JS_GetClass(glob); - - // Whenever we end up with globals that are JSCLASS_IS_DOMJSCLASS - // and have an nsISupports DOM object, we will need to modify this - // check here. - MOZ_ASSERT(!(clazz->flags & JSCLASS_IS_DOMJSCLASS)); - nsISupports* supports; - if (!(clazz->flags & JSCLASS_HAS_PRIVATE) || - !(clazz->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) || - !(supports = (nsISupports*)::JS_GetPrivate(glob))) { - return nullptr; - } - - // We might either have a window directly (e.g. if the global is a - // sandbox whose script object principal pointer is a window), or an - // XPCWrappedNative for a window. We could also have other - // sandbox-related script object principals, but we can't do much - // about those short of trying to walk the proto chain of |glob| - // looking for a window or something. - nsCOMPtr sgo(do_QueryInterface(supports)); - if (!sgo) { - nsCOMPtr wrapper(do_QueryInterface(supports)); - if (!wrapper) { - return nullptr; - } - sgo = do_QueryWrappedNative(wrapper); - } - - // We're returning a pointer to something that's about to be - // released, but that's ok here. - return sgo; + return xpc::WindowGlobalOrNull(aObj); } nsIScriptContext * diff --git a/dom/base/nsQueryContentEventResult.h b/dom/base/nsQueryContentEventResult.h index 26a494289ab9..a9b9ff1f79d5 100644 --- a/dom/base/nsQueryContentEventResult.h +++ b/dom/base/nsQueryContentEventResult.h @@ -3,6 +3,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_dom_nsQueryContentEventResult_h +#define mozilla_dom_nsQueryContentEventResult_h + #include "nsIQueryContentEventResult.h" #include "nsString.h" #include "nsRect.h" @@ -32,3 +35,5 @@ protected: bool mSucceeded; bool mReversed; }; + +#endif // mozilla_dom_nsQueryContentEventResult_h \ No newline at end of file diff --git a/dom/base/test/test_domrequesthelper.xul b/dom/base/test/test_domrequesthelper.xul index 3db3e774999b..09b33da3e541 100644 --- a/dom/base/test/test_domrequesthelper.xul +++ b/dom/base/test/test_domrequesthelper.xul @@ -16,12 +16,24 @@ diff --git a/dom/network/tests/unit_stats/test_networkstats_db.js b/dom/network/tests/unit_stats/test_networkstats_db.js index b478f881dd90..6de5c4d75b16 100644 --- a/dom/network/tests/unit_stats/test_networkstats_db.js +++ b/dom/network/tests/unit_stats/test_networkstats_db.js @@ -7,6 +7,12 @@ Cu.import("resource://gre/modules/NetworkStatsDB.jsm"); const netStatsDb = new NetworkStatsDB(); +function clearWholeDB(callback) { + netStatsDb.dbNewTxn("readwrite", function(aTxn, aStore) { + aStore.delete(); + }, callback); +} + function filterTimestamp(date) { var sampleRate = netStatsDb.sampleRate; var offset = date.getTimezoneOffset() * 60 * 1000; @@ -574,5 +580,9 @@ add_test(function test_saveMultipleAppStats () { function run_test() { do_get_profile(); - run_next_test(); + // Clear whole database to avoid start tests with unknown state + // due to previous tests. + clearWholeDB(function(){ + run_next_test(); + }); } diff --git a/dom/network/tests/unit_stats/test_networkstats_service.js b/dom/network/tests/unit_stats/test_networkstats_service.js index d38906802739..31516eb4d865 100644 --- a/dom/network/tests/unit_stats/test_networkstats_service.js +++ b/dom/network/tests/unit_stats/test_networkstats_service.js @@ -3,33 +3,51 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +function getNetworks(callback) { + NetworkStatsService._db.getAvailableNetworks(function onGetNetworks(aError, aResult) { + callback(aError, aResult); + }); +} + add_test(function test_clearDB() { - var networks = NetworkStatsService.availableNetworks(); - NetworkStatsService._db.clearStats(networks, function onDBCleared(error, result) { - do_check_eq(result, null); - run_next_test(); + getNetworks(function onGetNetworks(error, result) { + do_check_eq(error, null); + var networks = result; + NetworkStatsService._db.clearStats(networks, function onDBCleared(error, result) { + do_check_eq(result, null); + run_next_test(); + }); }); }); -function getNetworkId() { - var network = (NetworkStatsService.availableNetworks())[0]; - return NetworkStatsService.getNetworkId(network.id, network.type); +function getNetworkId(callback) { + getNetworks(function onGetNetworks(error, result) { + do_check_eq(error, null); + var netId = NetworkStatsService.getNetworkId(result[0].id, result[0].type); + callback(null, netId); + }); } add_test(function test_networkStatsAvailable_ok() { - var netId = getNetworkId(); - NetworkStatsService.networkStatsAvailable(function (success, msg) { - do_check_eq(success, true); - run_next_test(); - }, netId, true, 1234, 4321, new Date()); + getNetworkId(function onGetId(error, result) { + do_check_eq(error, null); + var netId = result; + NetworkStatsService.networkStatsAvailable(function (success, msg) { + do_check_eq(success, true); + run_next_test(); + }, netId, true, 1234, 4321, new Date()); + }); }); add_test(function test_networkStatsAvailable_failure() { - var netId = getNetworkId(); - NetworkStatsService.networkStatsAvailable(function (success, msg) { - do_check_eq(success, false); - run_next_test(); - }, netId, false, 1234, 4321, new Date()); + getNetworkId(function onGetId(error, result) { + do_check_eq(error, null); + var netId = result; + NetworkStatsService.networkStatsAvailable(function (success, msg) { + do_check_eq(success, false); + run_next_test(); + }, netId, false, 1234, 4321, new Date()); + }); }); add_test(function test_update_invalidNetwork() { @@ -41,10 +59,13 @@ add_test(function test_update_invalidNetwork() { }); add_test(function test_update() { - var netId = getNetworkId(); - NetworkStatsService.update(netId, function (success, msg) { - do_check_eq(success, true); - run_next_test(); + getNetworkId(function onGetId(error, result) { + do_check_eq(error, null); + var netId = result; + NetworkStatsService.update(netId, function (success, msg) { + do_check_eq(success, true); + run_next_test(); + }); }); }); @@ -71,10 +92,13 @@ add_test(function test_updateAllStats() { }); add_test(function test_updateStats_ok() { - var netId = getNetworkId(); - NetworkStatsService.updateStats(netId, function(success, msg){ - do_check_eq(success, true); - run_next_test(); + getNetworkId(function onGetId(error, result) { + do_check_eq(error, null); + var netId = result; + NetworkStatsService.updateStats(netId, function(success, msg){ + do_check_eq(success, true); + run_next_test(); + }); }); }); diff --git a/dom/payment/Payment.js b/dom/payment/Payment.js index 746999e183ac..100b9a8ff20f 100644 --- a/dom/payment/Payment.js +++ b/dom/payment/Payment.js @@ -30,7 +30,8 @@ PaymentContentHelper.prototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsINavigatorPayment, Ci.nsIDOMGlobalPropertyInitializer, - Ci.nsISupportsWeakReference]), + Ci.nsISupportsWeakReference, + Ci.nsIObserver]), classID: PAYMENTCONTENTHELPER_CID, classInfo: XPCOMUtils.generateCI({ classID: PAYMENTCONTENTHELPER_CID, diff --git a/dom/phonenumberutils/PhoneNumberService.js b/dom/phonenumberutils/PhoneNumberService.js index 5cfe7f3b715f..a91d8635442d 100644 --- a/dom/phonenumberutils/PhoneNumberService.js +++ b/dom/phonenumberutils/PhoneNumberService.js @@ -92,7 +92,8 @@ PhoneNumberService.prototype = { classID : Components.ID("{e2768710-eb17-11e2-91e2-0800200c9a66}"), contractID : "@mozilla.org/phoneNumberService;1", QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer, - Ci.nsISupportsWeakReference]), + Ci.nsISupportsWeakReference, + Ci.nsIObserver]), } this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PhoneNumberService]); diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp index 53b97e9a98f8..d602e91f9974 100644 --- a/dom/plugins/base/nsJSNPRuntime.cpp +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -23,8 +23,10 @@ #include "prmem.h" #include "nsIContent.h" #include "nsPluginInstanceOwner.h" -#include "mozilla/HashFunctions.h" #include "nsWrapperCacheInlines.h" +#include "js/HashTable.h" +#include "mozilla/HashFunctions.h" + #define NPRUNTIME_JSCLASS_NAME "NPObject JS wrapper class" @@ -35,6 +37,21 @@ using namespace mozilla; using mozilla::plugins::PluginScriptableObjectParent; using mozilla::plugins::ParentNPObject; +struct JSObjWrapperHasher : public js::DefaultHasher +{ + typedef nsJSObjWrapperKey Key; + typedef Key Lookup; + + static uint32_t hash(const Lookup &l) { + return HashGeneric(l.mJSObj, l.mNpp); + } + + static void rekey(Key &k, const Key& newKey) { + MOZ_ASSERT(k.mNpp == newKey.mNpp); + k.mJSObj = newKey.mJSObj; + } +}; + // Hash of JSObject wrappers that wraps JSObjects as NPObjects. There // will be one wrapper per JSObject per plugin instance, i.e. if two // plugins access the JSObject x, two wrappers for x will be @@ -42,7 +59,11 @@ using mozilla::plugins::ParentNPObject; // when a plugin is torn down in case there's a leak in the plugin (we // don't want to leak the world just because a plugin leaks an // NPObject). -static PLDHashTable sJSObjWrappers; +typedef js::HashMap JSObjWrapperTable; +static JSObjWrapperTable sJSObjWrappers; // Hash of NPObject wrappers that wrap NPObjects as JSObjects. static PLDHashTable sNPObjWrappers; @@ -237,18 +258,16 @@ OnWrapperDestroyed() NS_ASSERTION(sWrapperCount, "Whaaa, unbalanced created/destroyed calls!"); if (--sWrapperCount == 0) { - if (sJSObjWrappers.ops) { - NS_ASSERTION(sJSObjWrappers.entryCount == 0, "Uh, hash not empty?"); + if (sJSObjWrappers.initialized()) { + MOZ_ASSERT(sJSObjWrappers.count() == 0); // No more wrappers, and our hash was initialized. Finish the // hash to prevent leaking it. - PL_DHashTableFinish(&sJSObjWrappers); - - sJSObjWrappers.ops = nullptr; + sJSObjWrappers.finish(); } if (sNPObjWrappers.ops) { - NS_ASSERTION(sNPObjWrappers.entryCount == 0, "Uh, hash not empty?"); + MOZ_ASSERT(sNPObjWrappers.entryCount == 0); // No more wrappers, and our hash was initialized. Finish the // hash to prevent leaking it. @@ -480,9 +499,8 @@ ReportExceptionIfPending(JSContext *cx) return false; } - nsJSObjWrapper::nsJSObjWrapper(NPP npp) - : nsJSObjWrapperKey(nullptr, npp) + : mNpp(npp) { MOZ_COUNT_CTOR(nsJSObjWrapper); OnWrapperCreated(); @@ -498,6 +516,15 @@ nsJSObjWrapper::~nsJSObjWrapper() OnWrapperDestroyed(); } +void +nsJSObjWrapper::ClearJSObject() { + // Unroot the object's JSObject + JS_RemoveObjectRootRT(sJSRuntime, &mJSObj); + + // Forget our reference to the JSObject. + mJSObj = nullptr; +} + // static NPObject * nsJSObjWrapper::NP_Allocate(NPP npp, NPClass *aClass) @@ -523,18 +550,13 @@ nsJSObjWrapper::NP_Invalidate(NPObject *npobj) nsJSObjWrapper *jsnpobj = (nsJSObjWrapper *)npobj; if (jsnpobj && jsnpobj->mJSObj) { - // Unroot the object's JSObject - JS_RemoveObjectRootRT(sJSRuntime, &jsnpobj->mJSObj); - if (sJSObjWrappers.ops) { - // Remove the wrapper from the hash + // Remove the wrapper from the hash + MOZ_ASSERT(sJSObjWrappers.initialized()); + nsJSObjWrapperKey key(jsnpobj->mJSObj, jsnpobj->mNpp); + sJSObjWrappers.remove(key); - nsJSObjWrapperKey key(jsnpobj->mJSObj, jsnpobj->mNpp); - PL_DHashTableOperate(&sJSObjWrappers, &key, PL_DHASH_REMOVE); - } - - // Forget our reference to the JSObject. - jsnpobj->mJSObj = nullptr; + jsnpobj->ClearJSObject(); } } @@ -915,34 +937,23 @@ nsJSObjWrapper::NP_Construct(NPObject *npobj, const NPVariant *args, } -class JSObjWrapperHashEntry : public PLDHashEntryHdr -{ -public: - nsJSObjWrapper *mJSObjWrapper; -}; - -static PLDHashNumber -JSObjWrapperHash(PLDHashTable *table, const void *key) -{ - const nsJSObjWrapperKey *e = static_cast(key); - return HashGeneric(e->mJSObj, e->mNpp); +/* + * This function is called during minor GCs for each key in the sJSObjWrappers + * table that has been moved. + */ +static void +JSObjWrapperKeyMarkCallback(JSTracer *trc, void *key, void *data) { + JSObject *obj = static_cast(key); + nsJSObjWrapper* wrapper = static_cast(data); + JSObject *prior = obj; + JS_CallObjectTracer(trc, &obj, "sJSObjWrappers key object"); + NPP npp = wrapper->mNpp; + nsJSObjWrapperKey oldKey(prior, npp); + nsJSObjWrapperKey newKey(obj, npp); + sJSObjWrappers.rekeyIfMoved(oldKey, newKey); } -static bool -JSObjWrapperHashMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *entry, - const void *key) -{ - const nsJSObjWrapperKey *objWrapperKey = - static_cast(key); - const JSObjWrapperHashEntry *e = - static_cast(entry); - - return (e->mJSObjWrapper->mJSObj == objWrapperKey->mJSObj && - e->mJSObjWrapper->mNpp == objWrapperKey->mNpp); -} - - // Look up or create an NPObject that wraps the JSObject obj. // static @@ -990,22 +1001,9 @@ nsJSObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle obj) return _retainobject(npobj); } - if (!sJSObjWrappers.ops) { + if (!sJSObjWrappers.initialized()) { // No hash yet (or any more), initialize it. - - static const PLDHashTableOps ops = - { - PL_DHashAllocTable, - PL_DHashFreeTable, - JSObjWrapperHash, - JSObjWrapperHashMatchEntry, - PL_DHashMoveEntryStub, - PL_DHashClearEntryStub, - PL_DHashFinalizeStub - }; - - if (!PL_DHashTableInit(&sJSObjWrappers, &ops, nullptr, - sizeof(JSObjWrapperHashEntry), 16)) { + if (!sJSObjWrappers.init(16)) { NS_ERROR("Error initializing PLDHashTable!"); return nullptr; @@ -1014,18 +1012,13 @@ nsJSObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle obj) nsJSObjWrapperKey key(obj, npp); - JSObjWrapperHashEntry *entry = static_cast - (PL_DHashTableOperate(&sJSObjWrappers, &key, PL_DHASH_ADD)); + JSObjWrapperTable::AddPtr p = sJSObjWrappers.lookupForAdd(key); - if (!entry) { - // Out of memory. - return nullptr; - } - - if (PL_DHASH_ENTRY_IS_BUSY(entry) && entry->mJSObjWrapper) { + if (p/* && p->value*/) { + MOZ_ASSERT(p->value); // Found a live nsJSObjWrapper, return it. - return _retainobject(entry->mJSObjWrapper); + return _retainobject(p->value); } // No existing nsJSObjWrapper, create one. @@ -1034,16 +1027,17 @@ nsJSObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle obj) (nsJSObjWrapper *)_createobject(npp, &sJSObjWrapperNPClass); if (!wrapper) { - // OOM? Remove the stale entry from the hash. - - PL_DHashTableRawRemove(&sJSObjWrappers, entry); - + // Out of memory, entry not yet added to table. return nullptr; } wrapper->mJSObj = obj; - entry->mJSObjWrapper = wrapper; + if (!sJSObjWrappers.add(p, key, wrapper)) { + // Out of memory, free the wrapper we created. + _releaseobject(wrapper); + return nullptr; + } NS_ASSERTION(wrapper->mNpp == npp, "nsJSObjWrapper::mNpp not initialized!"); @@ -1052,13 +1046,15 @@ nsJSObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle obj) if (!::JS_AddNamedObjectRoot(cx, &wrapper->mJSObj, "nsJSObjWrapper::mJSObject")) { NS_ERROR("Failed to root JSObject!"); + sJSObjWrappers.remove(key); _releaseobject(wrapper); - PL_DHashTableRawRemove(&sJSObjWrappers, entry); - return nullptr; } + // Add postbarrier for the hashtable key + JS_StoreObjectPostBarrierCallback(cx, JSObjWrapperKeyMarkCallback, obj, wrapper); + return wrapper; } @@ -1812,35 +1808,6 @@ nsNPObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, NPObject *npobj) } -// PLDHashTable enumeration callbacks for destruction code. -static PLDHashOperator -JSObjWrapperPluginDestroyedCallback(PLDHashTable *table, PLDHashEntryHdr *hdr, - uint32_t number, void *arg) -{ - JSObjWrapperHashEntry *entry = (JSObjWrapperHashEntry *)hdr; - - nsJSObjWrapper *npobj = entry->mJSObjWrapper; - - if (npobj->mNpp == arg) { - // Prevent invalidate() and _releaseobject() from touching the hash - // we're enumerating. - const PLDHashTableOps *ops = table->ops; - table->ops = nullptr; - - if (npobj->_class && npobj->_class->invalidate) { - npobj->_class->invalidate(npobj); - } - - _releaseobject(npobj); - - table->ops = ops; - - return PL_DHASH_REMOVE; - } - - return PL_DHASH_NEXT; -} - // Struct for passing an NPP and a JSContext to // NPObjWrapperPluginDestroyedCallback struct NppAndCx @@ -1888,7 +1855,7 @@ NPObjWrapperPluginDestroyedCallback(PLDHashTable *table, PLDHashEntryHdr *hdr, ::JS_SetPrivate(entry->mJSObj, nullptr); - table->ops = ops; + table->ops = ops; if (sDelayedReleases && sDelayedReleases->RemoveElement(npobj)) { OnWrapperDestroyed(); @@ -1904,9 +1871,16 @@ NPObjWrapperPluginDestroyedCallback(PLDHashTable *table, PLDHashEntryHdr *hdr, void nsJSNPRuntime::OnPluginDestroy(NPP npp) { - if (sJSObjWrappers.ops) { - PL_DHashTableEnumerate(&sJSObjWrappers, - JSObjWrapperPluginDestroyedCallback, npp); + if (sJSObjWrappers.initialized()) { + for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) { + nsJSObjWrapper *npobj = e.front().value; + MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass); + if (npobj->mNpp == npp) { + npobj->ClearJSObject(); + _releaseobject(npobj); + e.removeFront(); + } + } } // Use the safe JSContext here as we're not always able to find the diff --git a/dom/plugins/base/nsJSNPRuntime.h b/dom/plugins/base/nsJSNPRuntime.h index ba20f30c25b7..30869dbbdcac 100644 --- a/dom/plugins/base/nsJSNPRuntime.h +++ b/dom/plugins/base/nsJSNPRuntime.h @@ -25,20 +25,30 @@ public: { } - JSObject *mJSObj; + bool operator==(const nsJSObjWrapperKey& other) const { + return mJSObj == other.mJSObj && mNpp == other.mNpp; + } + bool operator!=(const nsJSObjWrapperKey& other) const { + return !(*this == other); + } + JSObject * mJSObj; const NPP mNpp; }; extern const JSClass sNPObjectJSWrapperClass; -class nsJSObjWrapper : public NPObject, - public nsJSObjWrapperKey +class nsJSObjWrapper : public NPObject { public: + JSObject *mJSObj; /* Added as a GC root. */ + const NPP mNpp; + static NPObject *GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle obj); + void ClearJSObject(); + protected: nsJSObjWrapper(NPP npp); ~nsJSObjWrapper(); diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp index e715af35748e..4ec10453569d 100644 --- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -320,7 +320,7 @@ Promise::Constructor(const GlobalObject& aGlobal, /* static */ already_AddRefed Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx, - JS::Handle aValue, ErrorResult& aRv) + const Optional>& aValue, ErrorResult& aRv) { nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); if (!window) { @@ -330,13 +330,14 @@ Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx, nsRefPtr promise = new Promise(window); - promise->MaybeResolveInternal(aCx, aValue); + promise->MaybeResolveInternal(aCx, + aValue.WasPassed() ? aValue.Value() : JS::UndefinedHandleValue); return promise.forget(); } /* static */ already_AddRefed Promise::Reject(const GlobalObject& aGlobal, JSContext* aCx, - JS::Handle aValue, ErrorResult& aRv) + const Optional>& aValue, ErrorResult& aRv) { nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); if (!window) { @@ -346,27 +347,28 @@ Promise::Reject(const GlobalObject& aGlobal, JSContext* aCx, nsRefPtr promise = new Promise(window); - promise->MaybeRejectInternal(aCx, aValue); + promise->MaybeRejectInternal(aCx, + aValue.WasPassed() ? aValue.Value() : JS::UndefinedHandleValue); return promise.forget(); } already_AddRefed -Promise::Then(const Optional >& aResolveCallback, - const Optional >& aRejectCallback) +Promise::Then(const Optional>& aResolveCallback, + const Optional>& aRejectCallback) { nsRefPtr promise = new Promise(GetParentObject()); nsRefPtr resolveCb = PromiseCallback::Factory(promise, aResolveCallback.WasPassed() - ? &aResolveCallback.Value() + ? aResolveCallback.Value() : nullptr, PromiseCallback::Resolve); nsRefPtr rejectCb = PromiseCallback::Factory(promise, aRejectCallback.WasPassed() - ? &aRejectCallback.Value() + ? aRejectCallback.Value() : nullptr, PromiseCallback::Reject); @@ -376,9 +378,9 @@ Promise::Then(const Optional >& aResolveCallback, } already_AddRefed -Promise::Catch(const Optional >& aRejectCallback) +Promise::Catch(const Optional>& aRejectCallback) { - Optional > resolveCb; + Optional> resolveCb; return Then(resolveCb, aRejectCallback); } diff --git a/dom/promise/Promise.h b/dom/promise/Promise.h index f416e647e6bd..dea5ace85d47 100644 --- a/dom/promise/Promise.h +++ b/dom/promise/Promise.h @@ -66,19 +66,19 @@ public: static already_AddRefed Resolve(const GlobalObject& aGlobal, JSContext* aCx, - JS::Handle aValue, ErrorResult& aRv); + const Optional>& aValue, ErrorResult& aRv); static already_AddRefed Reject(const GlobalObject& aGlobal, JSContext* aCx, - JS::Handle aValue, ErrorResult& aRv); + const Optional>& aValue, ErrorResult& aRv); already_AddRefed - Then(const Optional >& aResolveCallback, - const Optional >& aRejectCallback); + Then(const Optional>& aResolveCallback, + const Optional>& aRejectCallback); already_AddRefed - Catch(const Optional >& aRejectCallback); + Catch(const Optional>& aRejectCallback); void AppendNativeHandler(PromiseNativeHandler* aRunnable); diff --git a/dom/promise/tests/test_promise.html b/dom/promise/tests/test_promise.html index 3e5b1f3cad6a..fceaa68cc4d8 100644 --- a/dom/promise/tests/test_promise.html +++ b/dom/promise/tests/test_promise.html @@ -34,6 +34,22 @@ function promiseResolve() { }); } +function promiseResolveNoArg() { + var promise = new Promise(function(resolve, reject) { + ok(resolve, "Promise.resolve exists"); + ok(reject, "Promise.reject exists"); + + resolve(); + }).then(function(what) { + ok(true, "Then - resolveCb has been called"); + is(what, undefined, "ResolveCb received undefined"); + runTest(); + }, function() { + ok(false, "Then - rejectCb has been called"); + runTest(); + }); +} + function promiseReject() { var promise = new Promise(function(resolve, reject) { reject(42); @@ -47,6 +63,19 @@ function promiseReject() { }); } +function promiseRejectNoArg() { + var promise = new Promise(function(resolve, reject) { + reject(); + }).then(function(what) { + ok(false, "Then - resolveCb has been called"); + runTest(); + }, function(what) { + ok(true, "Then - rejectCb has been called"); + is(what, undefined, "RejectCb received undefined"); + runTest(); + }); +} + function promiseException() { var promise = new Promise(function(resolve, reject) { throw 42; @@ -164,6 +193,51 @@ function promiseThenCatchThen() { }); } +function promiseThenNoArg() { + var promise = new Promise(function(resolve, reject) { + resolve(42); + }); + + var clone = promise.then(); + isnot(promise, clone, "These 2 promise objs are different"); + promise.then(function(v) { + clone.then(function(cv) { + is(v, cv, "Both resolve to the same value"); + runTest(); + }); + }); +} + +function promiseThenUndefinedResolveFunction() { + var promise = new Promise(function(resolve, reject) { + reject(42); + }); + + try { + promise.then(undefined, function(v) { + is(v, 42, "Promise rejected with 42"); + runTest(); + }); + } catch (e) { + ok(false, "then should not throw on undefined resolve function"); + } +} + +function promiseThenNullResolveFunction() { + var promise = new Promise(function(resolve, reject) { + reject(42); + }); + + try { + promise.then(null, function(v) { + is(v, 42, "Promise rejected with 42"); + runTest(); + }); + } catch (e) { + ok(false, "then should not throw on null resolve function"); + } +} + function promiseRejectThenCatchThen() { var promise = new Promise(function(resolve, reject) { reject(42); @@ -280,6 +354,21 @@ function promiseThenCatchOrderingReject() { }); } +function promiseCatchNoArg() { + var promise = new Promise(function(resolve, reject) { + reject(42); + }); + + var clone = promise.catch(); + isnot(promise, clone, "These 2 promise objs are different"); + promise.catch(function(v) { + clone.catch(function(cv) { + is(v, cv, "Both reject to the same value"); + runTest(); + }); + }); +} + function promiseNestedPromise() { new Promise(function(resolve, reject) { resolve(new Promise(function(resolve, reject) { @@ -380,6 +469,12 @@ var tests = [ promiseResolve, promiseReject, promiseWrongNestedPromise, promiseLoop, promiseStaticReject, promiseStaticResolve, promiseResolveNestedPromise, + promiseResolveNoArg, + promiseRejectNoArg, + promiseThenNoArg, + promiseThenUndefinedResolveFunction, + promiseThenNullResolveFunction, + promiseCatchNoArg ]; function runTest() { diff --git a/dom/push/src/Push.js b/dom/push/src/Push.js index 09d6da2b986e..81a253a25146 100644 --- a/dom/push/src/Push.js +++ b/dom/push/src/Push.js @@ -40,7 +40,8 @@ Push.prototype = { classID : PUSH_CID, QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer, - Ci.nsISupportsWeakReference]), + Ci.nsISupportsWeakReference, + Ci.nsIObserver]), init: function(aWindow) { // Set debug first so that all debugging actually works. diff --git a/dom/quota/moz.build b/dom/quota/moz.build index 2e88d564f760..94dc67cdd0b6 100644 --- a/dom/quota/moz.build +++ b/dom/quota/moz.build @@ -31,7 +31,7 @@ EXPORTS.mozilla.dom.quota += [ 'Utilities.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'CheckQuotaHelper.cpp', 'FileStreams.cpp', 'QuotaManager.cpp', diff --git a/dom/system/gonk/RILContentHelper.js b/dom/system/gonk/RILContentHelper.js index 13b206f02441..3e0df151a7f2 100644 --- a/dom/system/gonk/RILContentHelper.js +++ b/dom/system/gonk/RILContentHelper.js @@ -491,7 +491,8 @@ RILContentHelper.prototype = { Ci.nsIVoicemailProvider, Ci.nsIIccProvider, Ci.nsIObserver, - Ci.nsISupportsWeakReference]), + Ci.nsISupportsWeakReference, + Ci.nsIObserver]), classID: RILCONTENTHELPER_CID, classInfo: XPCOMUtils.generateCI({classID: RILCONTENTHELPER_CID, classDescription: "RILContentHelper", @@ -556,7 +557,7 @@ RILContentHelper.prototype = { let rilContext = this.rilContexts[clientId]; // Card is not detected, clear iccInfo to null. - if (!newInfo || !newInfo.iccType) { + if (!newInfo || !newInfo.iccType || !newInfo.iccid) { if (rilContext.iccInfo) { rilContext.iccInfo = null; this._deliverEvent(clientId, diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js index 22f01ad15b1e..3750851a2a75 100644 --- a/dom/system/gonk/RadioInterfaceLayer.js +++ b/dom/system/gonk/RadioInterfaceLayer.js @@ -1562,6 +1562,7 @@ RadioInterface.prototype = { } this._radioOffTimer.initWithCallback(this._fireRadioOffTimer.bind(this), RADIO_POWER_OFF_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT); + this._radioOffAfterDataDisconnected = true; return; } this.setRadioEnabled(false); @@ -2004,7 +2005,7 @@ RadioInterface.prototype = { // Process pending radio power off request after all data calls // are disconnected. if (datacall.state == RIL.GECKO_NETWORK_STATE_UNKNOWN && - this._changingRadioPower) { + this._radioOffAfterDataDisconnected) { let anyDataConnected = false; for each (let apnSetting in this.apnSettings.byApn) { for each (let type in apnSetting.types) { @@ -2019,6 +2020,7 @@ RadioInterface.prototype = { } if (!anyDataConnected) { if (DEBUG) this.debug("All data connections are disconnected, set radio off."); + this._radioOffAfterDataDisconnected = false; this._cancelRadioOffTimer(); this.setRadioEnabled(false); } @@ -2282,6 +2284,10 @@ RadioInterface.prototype = { // the radio power. _changingRadioPower: false, + // Flag to determine if we need to set radio off when we are notified a data + // call has been disconnected. + _radioOffAfterDataDisconnected: false, + // Data calls setting. dataCallSettings: null, diff --git a/dom/system/gonk/ril_consts.js b/dom/system/gonk/ril_consts.js index 07c2f7f34a47..ee09e3810dd6 100644 --- a/dom/system/gonk/ril_consts.js +++ b/dom/system/gonk/ril_consts.js @@ -2379,10 +2379,9 @@ this.GECKO_RADIOSTATE_UNAVAILABLE = null; this.GECKO_RADIOSTATE_OFF = "off"; this.GECKO_RADIOSTATE_READY = "ready"; -this.GECKO_CARDSTATE_NOT_READY = null; +this.GECKO_CARDSTATE_UNDETECTED = null; this.GECKO_CARDSTATE_ILLEGAL = "illegal"; this.GECKO_CARDSTATE_UNKNOWN = "unknown"; -this.GECKO_CARDSTATE_ABSENT = "absent"; this.GECKO_CARDSTATE_PIN_REQUIRED = "pinRequired"; this.GECKO_CARDSTATE_PUK_REQUIRED = "pukRequired"; this.GECKO_CARDSTATE_PERSONALIZATION_IN_PROGRESS = "personalizationInProgress"; diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index 8e309176724f..fa8f98f0eb55 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -278,7 +278,7 @@ let RIL = { /** * Card state */ - this.cardState = GECKO_CARDSTATE_UNKNOWN; + this.cardState = GECKO_CARDSTATE_UNDETECTED; /** * Strings @@ -2954,51 +2954,36 @@ let RIL = { _processICCStatus: function _processICCStatus(iccStatus) { this.iccStatus = iccStatus; let newCardState; - - if ((!iccStatus) || (iccStatus.cardState == CARD_STATE_ABSENT)) { - switch (this.radioState) { - case GECKO_RADIOSTATE_UNAVAILABLE: - newCardState = GECKO_CARDSTATE_UNKNOWN; - break; - case GECKO_RADIOSTATE_OFF: - newCardState = GECKO_CARDSTATE_NOT_READY; - break; - case GECKO_RADIOSTATE_READY: - if (DEBUG) { - debug("ICC absent"); - } - newCardState = GECKO_CARDSTATE_ABSENT; - break; - } - if (newCardState == this.cardState) { - return; - } - this.iccInfo = {iccType: null}; - ICCUtilsHelper.handleICCInfoChange(); - - this.cardState = newCardState; - this.sendChromeMessage({rilMessageType: "cardstatechange", - cardState: this.cardState}); - return; - } - let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex : iccStatus.gsmUmtsSubscriptionAppIndex; let app = iccStatus.apps[index]; - if (iccStatus.cardState == CARD_STATE_ERROR || !app) { - if (this.cardState == GECKO_CARDSTATE_UNKNOWN) { + + // When |iccStatus.cardState| is not CARD_STATE_PRESENT or have incorrect + // app information, we can not get iccId. So treat ICC as undetected. + if (iccStatus.cardState !== CARD_STATE_PRESENT || !app) { + if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) { this.operator = null; - return; + // We should send |cardstatechange| before |iccinfochange|, otherwise we + // may lost cardstatechange event when icc card becomes undetected. + this.cardState = GECKO_CARDSTATE_UNDETECTED; + this.sendChromeMessage({rilMessageType: "cardstatechange", + cardState: this.cardState}); + + this.iccInfo = {iccType: null}; + ICCUtilsHelper.handleICCInfoChange(); } - this.operator = null; - this.cardState = GECKO_CARDSTATE_UNKNOWN; - this.sendChromeMessage({rilMessageType: "cardstatechange", - cardState: this.cardState}); return; } + // fetchICCRecords will need to read aid, so read aid here. this.aid = app.aid; this.appType = app.app_type; + this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType]; + // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED. + if (this.cardState === GECKO_CARDSTATE_UNDETECTED && + iccStatus.cardState === CARD_STATE_PRESENT) { + ICCRecordHelper.readICCID(); + } switch (app.app_state) { case CARD_APPSTATE_ILLEGAL: @@ -3036,8 +3021,6 @@ let RIL = { // This was moved down from CARD_APPSTATE_READY this.requestNetworkInfo(); if (newCardState == GECKO_CARDSTATE_READY) { - this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType]; - // For type SIM, we need to check EF_phase first. // Other types of ICC we can send Terminal_Profile immediately. if (this.appType == CARD_APPTYPE_SIM) { @@ -11205,7 +11188,6 @@ let ICCRecordHelper = { * Fetch ICC records. */ fetchICCRecords: function fetchICCRecords() { - this.readICCID(); RIL.getIMSI(); this.readAD(); this.readSST(); @@ -13181,7 +13163,6 @@ let ICCContactHelper = { let RuimRecordHelper = { fetchRuimRecords: function fetchRuimRecords() { - ICCRecordHelper.readICCID(); this.getIMSI_M(); this.readCST(); this.readCDMAHome(); diff --git a/dom/system/gonk/tests/test_ril_worker_icc.js b/dom/system/gonk/tests/test_ril_worker_icc.js index 4aed5a3cb748..7201b84e2f3d 100644 --- a/dom/system/gonk/tests/test_ril_worker_icc.js +++ b/dom/system/gonk/tests/test_ril_worker_icc.js @@ -1719,8 +1719,11 @@ add_test(function test_personalization_state() { let worker = newUint8Worker(); let ril = worker.RIL; + worker.ICCRecordHelper.readICCID = function fakeReadICCID() {}; + function testPersonalization(cardPersoState, geckoCardState) { let iccStatus = { + cardState: CARD_STATE_PRESENT, gsmUmtsSubscriptionAppIndex: 0, apps: [ { @@ -1758,8 +1761,11 @@ add_test(function test_card_app_state() { let worker = newUint8Worker(); let ril = worker.RIL; + worker.ICCRecordHelper.readICCID = function fakeReadICCID() {}; + function testCardAppState(cardAppState, geckoCardState) { let iccStatus = { + cardState: CARD_STATE_PRESENT, gsmUmtsSubscriptionAppIndex: 0, apps: [ { @@ -1794,8 +1800,11 @@ add_test(function test_icc_permanent_blocked() { let worker = newUint8Worker(); let ril = worker.RIL; + worker.ICCRecordHelper.readICCID = function fakeReadICCID() {}; + function testPermanentBlocked(pin1_replaced, universalPINState, pin1) { let iccStatus = { + cardState: CARD_STATE_PRESENT, gsmUmtsSubscriptionAppIndex: 0, universalPINState: universalPINState, apps: [ diff --git a/dom/system/gonk/tests/test_ril_worker_ruim.js b/dom/system/gonk/tests/test_ril_worker_ruim.js index c80ed4014eac..47fb987a973e 100644 --- a/dom/system/gonk/tests/test_ril_worker_ruim.js +++ b/dom/system/gonk/tests/test_ril_worker_ruim.js @@ -78,10 +78,6 @@ add_test(function test_fetch_ruim_recodes() { function testFetchRuimRecordes(expectCalled) { let ifCalled = []; - iccHelper.readICCID = function () { - ifCalled.push("readICCID"); - }; - ruimHelper.getIMSI_M = function () { ifCalled.push("getIMSI_M"); }; @@ -108,7 +104,7 @@ add_test(function test_fetch_ruim_recodes() { } } - let expectCalled = ["readICCID", "getIMSI_M", "readCST", "readCDMAHome", + let expectCalled = ["getIMSI_M", "readCST", "readCDMAHome", "getCdmaSubscription"]; testFetchRuimRecordes(expectCalled); diff --git a/dom/telephony/test/marionette/test_outgoing_radio_off.js b/dom/telephony/test/marionette/test_outgoing_radio_off.js index 28bd699e1aec..35faa87e2e90 100644 --- a/dom/telephony/test/marionette/test_outgoing_radio_off.js +++ b/dom/telephony/test/marionette/test_outgoing_radio_off.js @@ -26,14 +26,13 @@ function changeSetting(key, value, callback) { function setRadioEnabled(enabled, callback) { changeSetting("ril.radio.disabled", !enabled, function() { - icc.addEventListener("cardstatechange", function handler() { - // Wait until card state changes to "ready" after turning on radio. - // Wait until card state changes to "not-ready" after turning off radio. - if ((enabled && icc.cardState == "ready") || - (!enabled && icc.cardState != "ready")) { - icc.removeEventListener("cardstatechange", handler); - callback(); - } + // Wait for iccdetected event after turning on radio. + // Wait for iccundetected event after turning off radio. + let event = (enabled) ? "iccdetected" : "iccundetected"; + icc.addEventListener(event, function handler(evt) { + log(event + ": " + evt.iccId); + icc.removeEventListener(event, handler); + callback(); }); }); } diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 57e13a6b4495..94509d340ccc 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -296,6 +296,7 @@ var interfaceNamesInGlobalScope = "HTMLUListElement", "HTMLUnknownElement", "HTMLVideoElement", + {name: "IccChangeEvent", b2g: true, pref: "dom.icc.enabled"}, {name: "IccCardLockError", b2g: true, pref: "dom.icc.enabled"}, "IDBCursor", "IDBCursorWithValue", @@ -345,6 +346,7 @@ var interfaceNamesInGlobalScope = "MozCSSKeyframeRule", "MozCSSKeyframesRule", {name: "MozEmergencyCbModeEvent", b2g: true, pref: "dom.mobileconnection.enabled"}, + {name: "MozIcc", b2g: true, pref: "dom.icc.enabled"}, {name: "MozIccManager", b2g: true, pref: "dom.icc.enabled"}, {name: "MozInputContext", b2g: true}, {name: "MozInputMethodManager", b2g: true}, diff --git a/dom/time/moz.build b/dom/time/moz.build index e7bdd99411a6..c7888cde018c 100644 --- a/dom/time/moz.build +++ b/dom/time/moz.build @@ -17,7 +17,7 @@ EXPORTS.mozilla.dom.time += [ 'TimeService.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'DateCacheCleaner.cpp', 'TimeChangeObserver.cpp', 'TimeManager.cpp', diff --git a/dom/webidl/EventHandler.webidl b/dom/webidl/EventHandler.webidl index 088ab3c3f344..fd1d811b16b0 100644 --- a/dom/webidl/EventHandler.webidl +++ b/dom/webidl/EventHandler.webidl @@ -88,6 +88,22 @@ interface GlobalEventHandlers { attribute EventHandler onvolumechange; attribute EventHandler onwaiting; + // Pointer events handlers + [Pref="dom.w3c_pointer_events.enabled"] + attribute EventHandler onpointerdown; + [Pref="dom.w3c_pointer_events.enabled"] + attribute EventHandler onpointerup; + [Pref="dom.w3c_pointer_events.enabled"] + attribute EventHandler onpointermove; + [Pref="dom.w3c_pointer_events.enabled"] + attribute EventHandler onpointerout; + [Pref="dom.w3c_pointer_events.enabled"] + attribute EventHandler onpointerover; + [Pref="dom.w3c_pointer_events.enabled"] + attribute EventHandler onpointerenter; + [Pref="dom.w3c_pointer_events.enabled"] + attribute EventHandler onpointerleave; + // Mozilla-specific handlers attribute EventHandler onmozfullscreenchange; attribute EventHandler onmozfullscreenerror; diff --git a/dom/webidl/HTMLInputElement.webidl b/dom/webidl/HTMLInputElement.webidl index ba1bb610f2ee..3c6f9c8c04d7 100644 --- a/dom/webidl/HTMLInputElement.webidl +++ b/dom/webidl/HTMLInputElement.webidl @@ -154,6 +154,18 @@ partial interface HTMLInputElement { [ChromeOnly] void mozSetFileNameArray(sequence fileNames); + // Number controls () have an anonymous text control + // () in the anonymous shadow tree that they contain. On + // such an anonymous text control this property provides access to the + // number control that owns the text control. This is useful, for example, + // in code that looks at the currently focused element to make decisions + // about which IME to bring up. Such code needs to be able to check for any + // owning number control since it probably wants to bring up a number pad + // instead of the standard keyboard, even when the anonymous text control has + // focus. + [ChromeOnly] + readonly attribute HTMLInputElement? ownerNumberControl; + boolean mozIsTextField(boolean aExcludePassword); }; diff --git a/dom/webidl/IccChangeEvent.webidl b/dom/webidl/IccChangeEvent.webidl new file mode 100644 index 000000000000..e3dab2f7db13 --- /dev/null +++ b/dom/webidl/IccChangeEvent.webidl @@ -0,0 +1,17 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +[Pref="dom.icc.enabled", + Constructor(DOMString type, optional IccChangeEventInit eventInitDict)] +interface IccChangeEvent : Event +{ + readonly attribute DOMString iccId; +}; + +dictionary IccChangeEventInit : EventInit +{ + DOMString iccId = ""; +}; diff --git a/dom/webidl/MozIcc.webidl b/dom/webidl/MozIcc.webidl new file mode 100644 index 000000000000..98b735c1b0e6 --- /dev/null +++ b/dom/webidl/MozIcc.webidl @@ -0,0 +1,368 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +interface MozIccInfo; + +[Pref="dom.icc.enabled"] +interface MozIcc : EventTarget +{ + // Integrated Circuit Card Information. + + /** + * Information stored in the device's ICC. + * + * Once the ICC becomes undetectable, iccinfochange event will be notified. + * Also, the attribute is set to null and this MozIcc object becomes invalid. + * Calling asynchronous functions raises exception then. + */ + readonly attribute MozIccInfo? iccInfo; + + /** + * The 'iccinfochange' event is notified whenever the icc info object + * changes. + */ + attribute EventHandler oniccinfochange; + + // Integrated Circuit Card State. + + /** + * Indicates the state of the device's ICC. + * + * Possible values: 'illegal', 'unknown', 'pinRequired', + * 'pukRequired', 'personalizationInProgress', 'networkLocked', + * 'corporateLocked', 'serviceProviderLocked', 'networkPukRequired', + * 'corporatePukRequired', 'serviceProviderPukRequired', + * 'personalizationReady', 'ready', 'permanentBlocked'. + * + * Once the ICC becomes undetectable, cardstatechange event will be notified. + * Also, the attribute is set to null and this MozIcc object becomes invalid. + * Calling asynchronous functions raises exception then. + */ + readonly attribute DOMString? cardState; + + /** + * The 'cardstatechange' event is notified when the 'cardState' attribute + * changes value. + */ + attribute EventHandler oncardstatechange; + + // Integrated Circuit Card STK. + + /** + * Send the response back to ICC after an attempt to execute STK proactive + * Command. + * + * @param command + * Command received from ICC. See MozStkCommand. + * @param response + * The response that will be sent to ICC. + * @see MozStkResponse for the detail of response. + */ + [Throws] + void sendStkResponse(any command, any response); + + /** + * Send the "Menu Selection" envelope command to ICC for menu selection. + * + * @param itemIdentifier + * The identifier of the item selected by user. + * @param helpRequested + * true if user requests to provide help information, false otherwise. + */ + [Throws] + void sendStkMenuSelection(unsigned short itemIdentifier, + boolean helpRequested); + + /** + * Send the "Timer Expiration" envelope command to ICC for TIMER MANAGEMENT. + * + * @param timer + * The identifier and value for a timer. + * timerId: Identifier of the timer that has expired. + * timerValue: Different between the time when this command is issued + * and when the timer was initially started. + * @see MozStkTimer + */ + [Throws] + void sendStkTimerExpiration(any timer); + + /** + * Send "Event Download" envelope command to ICC. + * ICC will not respond with any data for this command. + * + * @param event + * one of events below: + * - MozStkLocationEvent + * - MozStkCallEvent + * - MozStkLanguageSelectionEvent + * - MozStkGeneralEvent + * - MozStkBrowserTerminationEvent + */ + [Throws] + void sendStkEventDownload(any event); + + /** + * The 'stkcommand' event is notified whenever STK proactive command is + * issued from ICC. + */ + attribute EventHandler onstkcommand; + + /** + * 'stksessionend' event is notified whenever STK session is terminated by + * ICC. + */ + attribute EventHandler onstksessionend; + + // Integrated Circuit Card Lock interfaces. + + /** + * Find out about the status of an ICC lock (e.g. the PIN lock). + * + * @param lockType + * Identifies the lock type, e.g. "pin" for the PIN lock, "fdn" for + * the FDN lock. + * + * @return a DOMRequest. + * The request's result will be an object containing + * information about the specified lock's status, + * e.g. {lockType: "pin", enabled: true}. + */ + [Throws] + nsISupports getCardLock(DOMString lockType); + + /** + * Unlock a card lock. + * + * @param info + * An object containing the information necessary to unlock + * the given lock. At a minimum, this object must have a + * "lockType" attribute which specifies the type of lock, e.g. + * "pin" for the PIN lock. Other attributes are dependent on + * the lock type. + * + * Examples: + * + * (1) Unlocking the PIN: + * + * unlockCardLock({lockType: "pin", + * pin: "..."}); + * + * (2) Unlocking the PUK and supplying a new PIN: + * + * unlockCardLock({lockType: "puk", + * puk: "...", + * newPin: "..."}); + * + * (3) Network depersonalization. Unlocking the network control key (NCK). + * + * unlockCardLock({lockType: "nck", + * pin: "..."}); + * + * (4) Corporate depersonalization. Unlocking the corporate control key (CCK). + * + * unlockCardLock({lockType: "cck", + * pin: "..."}); + * + * (5) Service Provider depersonalization. Unlocking the service provider + * control key (SPCK). + * + * unlockCardLock({lockType: "spck", + * pin: "..."}); + * + * (6) Network PUK depersonalization. Unlocking the network control key (NCK). + * + * unlockCardLock({lockType: "nckPuk", + * puk: "..."}); + * + * (7) Corporate PUK depersonalization. Unlocking the corporate control key + * (CCK). + * + * unlockCardLock({lockType: "cckPuk", + * puk: "..."}); + * + * (8) Service Provider PUK depersonalization. Unlocking the service provider + * control key (SPCK). + * + * unlockCardLock({lockType: "spckPuk", + * puk: "..."}); + * + * @return a DOMRequest. + * The request's result will be an object containing + * information about the unlock operation. + * + * Examples: + * + * (1) Unlocking failed: + * + * { + * lockType: "pin", + * success: false, + * retryCount: 2 + * } + * + * (2) Unlocking succeeded: + * + * { + * lockType: "pin", + * success: true + * } + */ + [Throws] + nsISupports unlockCardLock(any info); + + /** + * Modify the state of a card lock. + * + * @param info + * An object containing information about the lock and + * how to modify its state. At a minimum, this object + * must have a "lockType" attribute which specifies the + * type of lock, e.g. "pin" for the PIN lock. Other + * attributes are dependent on the lock type. + * + * Examples: + * + * (1a) Disabling the PIN lock: + * + * setCardLock({lockType: "pin", + * pin: "...", + * enabled: false}); + * + * (1b) Disabling the FDN lock: + * + * setCardLock({lockType: "fdn", + * pin2: "...", + * enabled: false}); + * + * (2) Changing the PIN: + * + * setCardLock({lockType: "pin", + * pin: "...", + * newPin: "..."}); + * + * @return a DOMRequest. + * The request's result will be an object containing + * information about the operation. + * + * Examples: + * + * (1) Enabling/Disabling card lock failed or change card lock failed. + * + * { + * lockType: "pin", + * success: false, + * retryCount: 2 + * } + * + * (2) Enabling/Disabling card lock succeed or change card lock succeed. + * + * { + * lockType: "pin", + * success: true + * } + */ + [Throws] + nsISupports setCardLock(any info); + + /** + * Retrieve the number of remaining tries for unlocking the card. + * + * @param lockType + * Identifies the lock type, e.g. "pin" for the PIN lock, "puk" for + * the PUK lock. + * + * @return a DOMRequest. + * If the lock type is "pin", or "puk", the request's result will be + * an object containing the number of retries for the specified + * lock. For any other lock type, the result is undefined. + */ + [Throws] + nsISupports getCardLockRetryCount(DOMString lockType); + + // Integrated Circuit Card Phonebook Interfaces. + + /** + * Read ICC contacts. + * + * @param contactType + * One of type as below, + * - 'adn': Abbreviated Dialling Number. + * - 'fdn': Fixed Dialling Number. + * + * @return a DOMRequest. + */ + [Throws] + nsISupports readContacts(DOMString contactType); + + /** + * Update ICC Phonebook contact. + * + * @param contactType + * One of type as below, + * - 'adn': Abbreviated Dialling Number. + * - 'fdn': Fixed Dialling Number. + * @param contact + * The contact will be updated in ICC. + * @param [optional] pin2 + * PIN2 is only required for 'fdn'. + * + * @return a DOMRequest. + */ + [Throws] + nsISupports updateContact(DOMString contactType, + any contact, + optional DOMString? pin2 = null); + + // Integrated Circuit Card Secure Element Interfaces. + + /** + * A secure element is a smart card chip that can hold + * several different applications with the necessary security. + * The most known secure element is the Universal Integrated Circuit Card + * (UICC). + */ + + /** + * Send request to open a logical channel defined by its + * application identifier (AID). + * + * @param aid + * The application identifier of the applet to be selected on this + * channel. + * + * @return a DOMRequest. + * The request's result will be an instance of channel (channelID) + * if available or null. + */ + [Throws] + nsISupports iccOpenChannel(DOMString aid); + + /** + * Interface, used to communicate with an applet through the + * application data protocol units (APDUs) and is + * used for all data that is exchanged between the UICC and the terminal (ME). + * + * @param channel + * The application identifier of the applet to which APDU is directed. + * @param apdu + * Application protocol data unit. + * + * @return a DOMRequest. + * The request's result will be response APDU. + */ + [Throws] + nsISupports iccExchangeAPDU(long channel, any apdu); + + /** + * Send request to close the selected logical channel identified by its + * application identifier (AID). + * + * @param aid + * The application identifier of the applet, to be closed. + * + * @return a DOMRequest. + */ + [Throws] + nsISupports iccCloseChannel(long channel); +}; diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index eeb945e8a6fb..e838f4d88e43 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -32,18 +32,22 @@ Navigator implements NavigatorStorageUtils; [NoInterfaceObject] interface NavigatorID { + // WebKit/Blink/Trident/Presto support this (hardcoded "Mozilla"). + [Constant] + readonly attribute DOMString appCodeName; // constant "Mozilla" + [Constant] readonly attribute DOMString appName; - [Throws] + [Constant] readonly attribute DOMString appVersion; - [Throws] + [Constant] readonly attribute DOMString platform; - [Throws] + [Constant] readonly attribute DOMString userAgent; + [Constant] + readonly attribute DOMString product; // constant "Gecko" - // Spec has this as a const, but that's wrong because it should not - // be on the interface object. - //const DOMString product = "Gecko"; // for historical reasons - readonly attribute DOMString product; + // Everyone but WebKit/Blink supports this. See bug 679971. + boolean taintEnabled(); // constant false }; [NoInterfaceObject] @@ -136,9 +140,6 @@ callback interface MozIdleObserver { // nsIDOMNavigator partial interface Navigator { - // WebKit/Blink/Trident/Presto support this (hardcoded "Mozilla"). - [Throws] - readonly attribute DOMString appCodeName; [Throws] readonly attribute DOMString oscpu; // WebKit/Blink support this; Trident/Presto do not. @@ -157,8 +158,6 @@ partial interface Navigator { // WebKit/Blink/Trident/Presto support this. [Throws] boolean javaEnabled(); - // Everyone but WebKit/Blink supports this. See bug 679971. - boolean taintEnabled(); /** * Navigator requests to add an idle observer to the existing window. diff --git a/dom/webidl/NetDashboard.webidl b/dom/webidl/NetDashboard.webidl index 2e5e0c535470..12bbb01f084a 100644 --- a/dom/webidl/NetDashboard.webidl +++ b/dom/webidl/NetDashboard.webidl @@ -3,60 +3,75 @@ * 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/. */ -dictionary SocketsDict { - sequence host; - sequence port; - sequence active; - sequence tcp; - sequence socksent; - sequence sockreceived; +dictionary SocketElement { + DOMString host = ""; + unsigned long port = 0; + boolean active = false; + boolean tcp = false; double sent = 0; double received = 0; }; -dictionary HttpConnInfoDict { - sequence rtt; - sequence ttl; - sequence protocolVersion; +dictionary SocketsDict { + sequence sockets; + double sent = 0; + double received = 0; +}; + +dictionary HttpConnInfo { + unsigned long rtt = 0; + unsigned long ttl = 0; + DOMString protocolVersion = ""; }; dictionary HalfOpenInfoDict { - sequence speculative; + boolean speculative = false; +}; + +dictionary HttpConnectionElement { + DOMString host = ""; + unsigned long port = 0; + boolean spdy = false; + boolean ssl = false; + sequence active; + sequence idle; + sequence halfOpens; }; dictionary HttpConnDict { - sequence host; - sequence port; - sequence active; - sequence idle; - sequence halfOpens; - sequence spdy; - sequence ssl; + sequence connections; +}; + +dictionary WebSocketElement { + DOMString hostport = ""; + unsigned long msgsent = 0; + unsigned long msgreceived = 0; + double sentsize = 0; + double receivedsize = 0; + boolean encrypted = false; }; dictionary WebSocketDict { - sequence hostport; - sequence msgsent; - sequence msgreceived; - sequence sentsize; - sequence receivedsize; - sequence encrypted; + sequence websockets; +}; + +dictionary DnsCacheEntry { + DOMString hostname = ""; + sequence hostaddr; + DOMString family = ""; + double expiration = 0; }; dictionary DNSCacheDict { - sequence hostname; - sequence> hostaddr; - sequence family; - sequence expiration; + sequence entries; }; dictionary DNSLookupDict { sequence address; - DOMString error; - boolean answer; + DOMString error = ""; + boolean answer = false; }; dictionary ConnStatusDict { - DOMString status; + DOMString status = ""; }; - diff --git a/dom/webidl/PointerEvent.webidl b/dom/webidl/PointerEvent.webidl new file mode 100644 index 000000000000..935342ae8ea7 --- /dev/null +++ b/dom/webidl/PointerEvent.webidl @@ -0,0 +1,37 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * For more information see nsIPointerEvent.idl. + * + * Portions Copyright 2013 Microsoft Open Technologies, Inc. */ + +interface WindowProxy; + +[Pref="dom.w3c_pointer_events.enabled", + Constructor(DOMString type, optional PointerEventInit eventInitDict)] +interface PointerEvent : MouseEvent +{ + readonly attribute long pointerId; + readonly attribute long width; + readonly attribute long height; + readonly attribute float pressure; + readonly attribute long tiltX; + readonly attribute long tiltY; + readonly attribute DOMString pointerType; + readonly attribute boolean isPrimary; +}; + +dictionary PointerEventInit : MouseEventInit +{ + long pointerId = 0; + long width = 0; + long height = 0; + float pressure = 0; + long tiltX = 0; + long tiltY = 0; + DOMString pointerType = ""; + boolean isPrimary = false; +}; + diff --git a/dom/webidl/Promise.webidl b/dom/webidl/Promise.webidl index f2ca4c665eca..99383ac63942 100644 --- a/dom/webidl/Promise.webidl +++ b/dom/webidl/Promise.webidl @@ -23,14 +23,14 @@ interface Promise { // Promise object in this scope without having resolved the interface object // first. [NewObject, Throws, Func="mozilla::dom::Promise::EnabledForScope"] - static Promise resolve(any value); // same as any(value) + static Promise resolve(optional any value); [NewObject, Throws, Func="mozilla::dom::Promise::EnabledForScope"] - static Promise reject(any value); + static Promise reject(optional any value); [NewObject] - Promise then(optional AnyCallback fulfillCallback, - optional AnyCallback rejectCallback); + Promise then(optional AnyCallback? fulfillCallback, + optional AnyCallback? rejectCallback); [NewObject] - Promise catch(optional AnyCallback rejectCallback); + Promise catch(optional AnyCallback? rejectCallback); }; diff --git a/dom/webidl/WorkerNavigator.webidl b/dom/webidl/WorkerNavigator.webidl index 5ddc1f443185..a29b53a11e0c 100644 --- a/dom/webidl/WorkerNavigator.webidl +++ b/dom/webidl/WorkerNavigator.webidl @@ -3,12 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ interface WorkerNavigator { - [Constant] - readonly attribute DOMString appName; - [Constant] - readonly attribute DOMString appVersion; - [Constant] - readonly attribute DOMString platform; - [Constant] - readonly attribute DOMString userAgent; }; + +WorkerNavigator implements NavigatorID; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index e3db21266b89..67945b365e96 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -254,6 +254,7 @@ WEBIDL_FILES = [ 'PhoneNumberService.webidl', 'Plugin.webidl', 'PluginArray.webidl', + 'PointerEvent.webidl', 'Position.webidl', 'PositionError.webidl', 'ProcessingInstruction.webidl', @@ -513,6 +514,7 @@ if CONFIG['MOZ_B2G_RIL']: 'MozCellBroadcast.webidl', 'MozCellBroadcastEvent.webidl', 'MozEmergencyCbModeEvent.webidl', + 'MozIcc.webidl', 'MozMobileConnectionArray.webidl', 'MozOtaStatusEvent.webidl', 'MozVoicemail.webidl', @@ -564,6 +566,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [ 'DeviceLightEvent.webidl', 'DeviceProximityEvent.webidl', 'ErrorEvent.webidl', + 'IccChangeEvent.webidl', 'MediaStreamEvent.webidl', 'MozContactChangeEvent.webidl', 'MozInterAppMessageEvent.webidl', diff --git a/dom/wifi/DOMWifiManager.js b/dom/wifi/DOMWifiManager.js index c61692c9d839..806b4c6b5ed3 100644 --- a/dom/wifi/DOMWifiManager.js +++ b/dom/wifi/DOMWifiManager.js @@ -57,7 +57,8 @@ DOMWifiManager.prototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMWifiManager, Ci.nsIDOMGlobalPropertyInitializer, - Ci.nsISupportsWeakReference]), + Ci.nsISupportsWeakReference, + Ci.nsIObserver]), // nsIDOMGlobalPropertyInitializer implementation init: function(aWindow) { diff --git a/dom/workers/Navigator.h b/dom/workers/Navigator.h index ec143e4c20ae..80cd555fb712 100644 --- a/dom/workers/Navigator.h +++ b/dom/workers/Navigator.h @@ -52,6 +52,10 @@ public: MOZ_COUNT_DTOR(WorkerNavigator); } + void GetAppCodeName(nsString& aAppCodeName) const + { + aAppCodeName.AssignLiteral("Mozilla"); + } void GetAppName(nsString& aAppName) const { aAppName = mAppName; @@ -64,6 +68,14 @@ public: { aPlatform = mPlatform; } + void GetProduct(nsString& aProduct) const + { + aProduct.AssignLiteral("Gecko"); + } + bool TaintEnabled() const + { + return false; + } void GetUserAgent(nsString& aUserAgent) const { aUserAgent = mUserAgent; diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index 3e64dc70aa04..20d66c346715 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -880,6 +880,21 @@ public: mWorkerPrivate = nullptr; } + virtual void + PrepareForForgetSkippable() MOZ_OVERRIDE + { + } + + virtual void + BeginCycleCollectionCallback() MOZ_OVERRIDE + { + } + + virtual void + EndCycleCollectionCallback(CycleCollectorResults &aResults) MOZ_OVERRIDE + { + } + void DispatchDeferredDeletion(bool aContinuation) MOZ_OVERRIDE { @@ -899,7 +914,7 @@ public: mWorkerPrivate->AssertIsOnWorkerThread(); if (aStatus == JSGC_END) { - nsCycleCollector_collect(true, nullptr, nullptr); + nsCycleCollector_collect(nullptr); } } diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 33f945724630..f4dd03f2bdbc 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -2159,7 +2159,8 @@ WorkerPrivateParent::WrapObject(JSContext* aCx, AssertIsOnParentThread(); - JSObject* obj = WorkerBinding::Wrap(aCx, aScope, ParentAsWorkerPrivate()); + JS::Rooted obj(aCx, WorkerBinding::Wrap(aCx, aScope, + ParentAsWorkerPrivate())); if (mRooted) { PreserveWrapper(this); @@ -5259,16 +5260,23 @@ WorkerPrivate::ConnectMessagePort(JSContext* aCx, uint64_t aMessagePortSerial) return false; } - MessageEventInit init; - init.mBubbles = false; - init.mCancelable = false; - init.mSource = &jsPort.toObject(); + nsRefPtr event; + { + // Bug 940779 - MessageEventInit contains unrooted JS objects, and + // ~nsRefPtr can GC, so make sure 'init' is no longer live before ~nsRefPtr + // runs (or the nsRefPtr is even created) to avoid a rooting hazard. Note + // that 'init' is live until its destructor runs, not just until its final + // use. + MessageEventInit init; + init.mBubbles = false; + init.mCancelable = false; + init.mSource = &jsPort.toObject(); - ErrorResult rv; - - nsRefPtr event = - nsDOMMessageEvent::Constructor(globalObject, aCx, - NS_LITERAL_STRING("connect"), init, rv); + ErrorResult rv; + event = nsDOMMessageEvent::Constructor(globalObject, aCx, + NS_LITERAL_STRING("connect"), + init, rv); + } event->SetTrusted(true); diff --git a/dom/workers/XMLHttpRequest.cpp b/dom/workers/XMLHttpRequest.cpp index 36308377817a..2265be93af2a 100644 --- a/dom/workers/XMLHttpRequest.cpp +++ b/dom/workers/XMLHttpRequest.cpp @@ -682,19 +682,19 @@ public: } } - XMLHttpRequest::StateData state; - StateDataAutoRooter rooter(aCx, &state); + nsAutoPtr state(new XMLHttpRequest::StateData()); + StateDataAutoRooter rooter(aCx, state); - state.mResponseTextResult = mResponseTextResult; - state.mResponseText = mResponseText; + state->mResponseTextResult = mResponseTextResult; + state->mResponseText = mResponseText; if (NS_SUCCEEDED(mResponseTextResult)) { MOZ_ASSERT(JSVAL_IS_VOID(mResponse) || JSVAL_IS_NULL(mResponse)); - state.mResponseResult = mResponseTextResult; - state.mResponse = mResponse; + state->mResponseResult = mResponseTextResult; + state->mResponse = mResponse; } else { - state.mResponseResult = mResponseResult; + state->mResponseResult = mResponseResult; if (NS_SUCCEEDED(mResponseResult)) { if (mResponseBuffer.data()) { @@ -716,23 +716,23 @@ public: return false; } - state.mResponse = response; + state->mResponse = response; } else { - state.mResponse = mResponse; + state->mResponse = mResponse; } } } - state.mStatusResult = mStatusResult; - state.mStatus = mStatus; + state->mStatusResult = mStatusResult; + state->mStatus = mStatus; - state.mStatusText = mStatusText; + state->mStatusText = mStatusText; - state.mReadyState = mReadyState; + state->mReadyState = mReadyState; XMLHttpRequest* xhr = mProxy->mXMLHttpRequestPrivate; - xhr->UpdateState(state); + xhr->UpdateState(*state); if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) { return true; diff --git a/dom/workers/test/navigator_worker.js b/dom/workers/test/navigator_worker.js index 2ca3b6fdeadf..50934d92aef1 100644 --- a/dom/workers/test/navigator_worker.js +++ b/dom/workers/test/navigator_worker.js @@ -3,9 +3,12 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ var supportedProps = [ + "appCodeName", "appName", "appVersion", "platform", + "product", + "taintEnabled", "userAgent" ]; @@ -27,7 +30,7 @@ for (var index = 0; index < supportedProps.length; index++) { obj = { name: prop, - value: navigator[prop] + value: prop === "taintEnabled" ? navigator[prop]() : navigator[prop] }; postMessage(JSON.stringify(obj)); diff --git a/dom/workers/test/test_navigator.html b/dom/workers/test/test_navigator.html index ccace0e446d1..056f9bac0282 100644 --- a/dom/workers/test/test_navigator.html +++ b/dom/workers/test/test_navigator.html @@ -35,6 +35,11 @@ Tests of DOM Worker Navigator return; } + if (args.name === "taintEnabled") { + is(navigator[args.name](), args.value, args.name + "() returns false."); + return; + } + is(navigator[args.name], args.value, "Mismatched navigator string for " + args.name + "!"); }; diff --git a/editor/composer/src/nsEditorSpellCheck.cpp b/editor/composer/src/nsEditorSpellCheck.cpp index 7362c88da082..21f1d827d91d 100644 --- a/editor/composer/src/nsEditorSpellCheck.cpp +++ b/editor/composer/src/nsEditorSpellCheck.cpp @@ -97,8 +97,7 @@ GetLoadContext(nsIEditor* aEditor) nsCOMPtr doc = do_QueryInterface(domDoc); NS_ENSURE_TRUE(doc, nullptr); - nsCOMPtr container = doc->GetContainer(); - nsCOMPtr loadContext = do_QueryInterface(container); + nsCOMPtr loadContext = doc->GetLoadContext(); return loadContext.forget(); } diff --git a/editor/libeditor/base/nsEditorUtils.cpp b/editor/libeditor/base/nsEditorUtils.cpp index d5ee5afd2991..4d7b61f42b70 100644 --- a/editor/libeditor/base/nsEditorUtils.cpp +++ b/editor/libeditor/base/nsEditorUtils.cpp @@ -189,8 +189,7 @@ nsEditorHookUtils::GetHookEnumeratorFromDocument(nsIDOMDocument *aDoc, nsCOMPtr doc = do_QueryInterface(aDoc); NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); - nsCOMPtr container = doc->GetContainer(); - nsCOMPtr docShell = do_QueryInterface(container); + nsCOMPtr docShell = doc->GetDocShell(); nsCOMPtr hookObj = do_GetInterface(docShell); NS_ENSURE_TRUE(hookObj, NS_ERROR_FAILURE); diff --git a/editor/libeditor/text/nsPlaintextDataTransfer.cpp b/editor/libeditor/text/nsPlaintextDataTransfer.cpp index 1724576712a1..8f5409b0441b 100644 --- a/editor/libeditor/text/nsPlaintextDataTransfer.cpp +++ b/editor/libeditor/text/nsPlaintextDataTransfer.cpp @@ -432,8 +432,7 @@ bool nsPlaintextEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc) nsCOMPtr destdoc = GetDocument(); NS_ASSERTION(destdoc, "Where is our destination doc?"); - nsCOMPtr container = destdoc->GetContainer(); - nsCOMPtr dsti = do_QueryInterface(container); + nsCOMPtr dsti = destdoc->GetDocShell(); nsCOMPtr root; if (dsti) dsti->GetRootTreeItem(getter_AddRefs(root)); diff --git a/gfx/2d/BasePoint3D.h b/gfx/2d/BasePoint3D.h index 0312e425b8c8..c64ea247a5a2 100644 --- a/gfx/2d/BasePoint3D.h +++ b/gfx/2d/BasePoint3D.h @@ -6,7 +6,7 @@ #ifndef MOZILLA_BASEPOINT3D_H_ #define MOZILLA_BASEPOINT3D_H_ -#include "nsDebug.h" +#include "mozilla/Assertions.h" namespace mozilla { namespace gfx { @@ -31,12 +31,12 @@ struct BasePoint3D { // compiler generated default assignment operator T& operator[](int aIndex) { - NS_ABORT_IF_FALSE(aIndex >= 0 && aIndex <= 2, "Invalid array index"); + MOZ_ASSERT(aIndex >= 0 && aIndex <= 2); return *((&x)+aIndex); } const T& operator[](int aIndex) const { - NS_ABORT_IF_FALSE(aIndex >= 0 && aIndex <= 2, "Invalid array index"); + MOZ_ASSERT(aIndex >= 0 && aIndex <= 2); return *((&x)+aIndex); } diff --git a/gfx/2d/BasePoint4D.h b/gfx/2d/BasePoint4D.h index ab9fa452328e..a35deeed88b8 100644 --- a/gfx/2d/BasePoint4D.h +++ b/gfx/2d/BasePoint4D.h @@ -6,6 +6,8 @@ #ifndef MOZILLA_BASEPOINT4D_H_ #define MOZILLA_BASEPOINT4D_H_ +#include "mozilla/Assertions.h" + namespace mozilla { namespace gfx { @@ -86,12 +88,12 @@ struct BasePoint4D { } T& operator[](int aIndex) { - NS_ABORT_IF_FALSE(aIndex >= 0 && aIndex <= 3, "Invalid array index"); + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid array index"); return *((&x)+aIndex); } const T& operator[](int aIndex) const { - NS_ABORT_IF_FALSE(aIndex >= 0 && aIndex <= 3, "Invalid array index"); + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid array index"); return *((&x)+aIndex); } diff --git a/gfx/2d/DrawTargetD2D.cpp b/gfx/2d/DrawTargetD2D.cpp index c209c80788dc..163c9ebbe547 100644 --- a/gfx/2d/DrawTargetD2D.cpp +++ b/gfx/2d/DrawTargetD2D.cpp @@ -85,9 +85,7 @@ public: } mDT->mDevice->CopyResource(tmpTexture, mDT->mTexture); - D2D1_BITMAP_PROPERTIES props = - D2D1::BitmapProperties(D2D1::PixelFormat(DXGIFormat(format), - AlphaMode(format))); + D2D1_BITMAP_PROPERTIES props = D2D1::BitmapProperties(D2DPixelFormat(format)); RefPtr surf; @@ -306,7 +304,7 @@ DrawTargetD2D::GetBitmapForSurface(SourceSurface *aSurface, (uint32_t)aSource.x * BytesPerPixel(srcSurf->GetFormat()); D2D1_BITMAP_PROPERTIES props = - D2D1::BitmapProperties(D2D1::PixelFormat(DXGIFormat(srcSurf->GetFormat()), AlphaMode(srcSurf->GetFormat()))); + D2D1::BitmapProperties(D2DPixelFormat(srcSurf->GetFormat())); mRT->CreateBitmap(D2D1::SizeU(UINT32(aSource.width), UINT32(aSource.height)), data, stride, props, byRef(bitmap)); // subtract the integer part leaving the fractional part diff --git a/gfx/2d/HelpersD2D.h b/gfx/2d/HelpersD2D.h index 802a4e1b9a55..2e10643e1be0 100644 --- a/gfx/2d/HelpersD2D.h +++ b/gfx/2d/HelpersD2D.h @@ -162,7 +162,7 @@ static inline DXGI_FORMAT DXGIFormat(SurfaceFormat aFormat) } } -static inline D2D1_ALPHA_MODE AlphaMode(SurfaceFormat aFormat) +static inline D2D1_ALPHA_MODE D2DAlphaModeForFormat(SurfaceFormat aFormat) { switch (aFormat) { case FORMAT_B8G8R8X8: @@ -174,7 +174,7 @@ static inline D2D1_ALPHA_MODE AlphaMode(SurfaceFormat aFormat) static inline D2D1_PIXEL_FORMAT D2DPixelFormat(SurfaceFormat aFormat) { - return D2D1::PixelFormat(DXGIFormat(aFormat), AlphaMode(aFormat)); + return D2D1::PixelFormat(DXGIFormat(aFormat), D2DAlphaModeForFormat(aFormat)); } #ifdef USE_D2D1_1 diff --git a/gfx/2d/SourceSurfaceD2D.cpp b/gfx/2d/SourceSurfaceD2D.cpp index 005a6901e5f7..9704f1103925 100644 --- a/gfx/2d/SourceSurfaceD2D.cpp +++ b/gfx/2d/SourceSurfaceD2D.cpp @@ -68,8 +68,7 @@ SourceSurfaceD2D::InitFromData(unsigned char *aData, return false; } - D2D1_BITMAP_PROPERTIES props = - D2D1::BitmapProperties(D2D1::PixelFormat(DXGIFormat(aFormat), AlphaMode(aFormat))); + D2D1_BITMAP_PROPERTIES props = D2D1::BitmapProperties(D2DPixelFormat(aFormat)); hr = aRT->CreateBitmap(D2DIntSize(aSize), aData, aStride, props, byRef(mBitmap)); if (FAILED(hr)) { @@ -105,8 +104,7 @@ SourceSurfaceD2D::InitFromTexture(ID3D10Texture2D *aTexture, mSize = IntSize(desc.Width, desc.Height); mFormat = aFormat; - D2D1_BITMAP_PROPERTIES props = - D2D1::BitmapProperties(D2D1::PixelFormat(DXGIFormat(aFormat), AlphaMode(aFormat))); + D2D1_BITMAP_PROPERTIES props = D2D1::BitmapProperties(D2DPixelFormat(aFormat)); hr = aRT->CreateSharedBitmap(IID_IDXGISurface, surf, &props, byRef(mBitmap)); if (FAILED(hr)) { diff --git a/gfx/2d/SourceSurfaceD2DTarget.cpp b/gfx/2d/SourceSurfaceD2DTarget.cpp index 4f7e484cb0c1..a7ba9f710e8a 100644 --- a/gfx/2d/SourceSurfaceD2DTarget.cpp +++ b/gfx/2d/SourceSurfaceD2DTarget.cpp @@ -138,15 +138,13 @@ SourceSurfaceD2DTarget::GetBitmap(ID2D1RenderTarget *aRT) return nullptr; } - D2D1_BITMAP_PROPERTIES props = - D2D1::BitmapProperties(D2D1::PixelFormat(DXGIFormat(mFormat), AlphaMode(mFormat))); + D2D1_BITMAP_PROPERTIES props = D2D1::BitmapProperties(D2DPixelFormat(mFormat)); hr = aRT->CreateSharedBitmap(IID_IDXGISurface, surf, &props, byRef(mBitmap)); if (FAILED(hr)) { // This seems to happen for FORMAT_A8 sometimes... aRT->CreateBitmap(D2D1::SizeU(desc.Width, desc.Height), - D2D1::BitmapProperties(D2D1::PixelFormat(DXGIFormat(mFormat), - AlphaMode(mFormat))), + D2D1::BitmapProperties(D2DPixelFormat(mFormat)), byRef(mBitmap)); RefPtr rt; @@ -169,7 +167,7 @@ SourceSurfaceD2DTarget::GetBitmap(ID2D1RenderTarget *aRT) } D2D1_RENDER_TARGET_PROPERTIES props = - D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGIFormat(mFormat), AlphaMode(mFormat))); + D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2DPixelFormat(mFormat)); hr = DrawTargetD2D::factory()->CreateDxgiSurfaceRenderTarget(surface, props, byRef(rt)); if (FAILED(hr)) { diff --git a/gfx/2d/SourceSurfaceRawData.cpp b/gfx/2d/SourceSurfaceRawData.cpp index eca819cafa66..12c04b87b69a 100644 --- a/gfx/2d/SourceSurfaceRawData.cpp +++ b/gfx/2d/SourceSurfaceRawData.cpp @@ -5,7 +5,6 @@ #include "SourceSurfaceRawData.h" #include "Logging.h" -#include "Tools.h" namespace mozilla { namespace gfx { diff --git a/gfx/2d/SourceSurfaceRawData.h b/gfx/2d/SourceSurfaceRawData.h index 3a3e10a7cb57..60333432b778 100644 --- a/gfx/2d/SourceSurfaceRawData.h +++ b/gfx/2d/SourceSurfaceRawData.h @@ -7,6 +7,7 @@ #define MOZILLA_GFX_SOURCESURFACERAWDATA_H_ #include "2D.h" +#include "Tools.h" namespace mozilla { namespace gfx { diff --git a/gfx/gl/GLLibraryEGL.cpp b/gfx/gl/GLLibraryEGL.cpp index 8ac22e8c7277..0043bfc81058 100644 --- a/gfx/gl/GLLibraryEGL.cpp +++ b/gfx/gl/GLLibraryEGL.cpp @@ -18,7 +18,7 @@ namespace gl { GLLibraryEGL sEGLLibrary; // should match the order of EGLExtensions, and be null-terminated. -static const char *sExtensionNames[] = { +static const char *sEGLExtensionNames[] = { "EGL_KHR_image_base", "EGL_KHR_image_pixmap", "EGL_KHR_gl_texture_2D_image", @@ -321,7 +321,7 @@ GLLibraryEGL::InitExtensions() const bool firstRun = false; #endif - GLContext::InitializeExtensionsBitSet(mAvailableExtensions, extensions, sExtensionNames, firstRun && debugMode); + GLContext::InitializeExtensionsBitSet(mAvailableExtensions, extensions, sEGLExtensionNames, firstRun && debugMode); #ifdef DEBUG firstRun = false; diff --git a/gfx/gl/moz.build b/gfx/gl/moz.build index ba3ac861f407..df92abcc4ec9 100644 --- a/gfx/gl/moz.build +++ b/gfx/gl/moz.build @@ -59,36 +59,48 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': 'SharedSurfaceANGLE.h', 'WGLLibrary.h', ] - SOURCES += [ + UNIFIED_SOURCES += [ 'GLContextProviderEGL.cpp', 'SharedSurfaceANGLE.cpp', ] if CONFIG['MOZ_ENABLE_SKIA_GPU']: EXPORTS += ['GLContextSkia.h'] - SOURCES += [ + UNIFIED_SOURCES += [ 'GLContextSkia.cpp', ] if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': - SOURCES += ['SharedSurfaceGralloc.cpp'] + UNIFIED_SOURCES += ['SharedSurfaceGralloc.cpp'] EXPORTS += ['SharedSurfaceGralloc.h'] if gl_provider == 'CGL': + # This file includes Mac headers that are unfriendly to unified builds, + # and we have only one .mm file here anyway. SOURCES += [ "GLContextProvider%s.mm" % (gl_provider), ] EXPORTS += [ 'SharedSurfaceIO.h', ] + # SharedSurfaceIO.cpp includes MacIOSurface.h which include Mac headers + # which define Size and Point types in root namespace with often conflict with + # our own types. While I haven't actually hit this issue in the present case, + # it's been an issue in gfx/layers so let's not risk it. SOURCES += [ 'SharedSurfaceIO.cpp', ] -else: +elif gl_provider == 'GLX': + # GLContextProviderGLX.cpp needs to be kept out of UNIFIED_SOURCES + # as it includes X11 headers which cause conflicts. SOURCES += [ + 'GLContextProviderGLX.cpp', + ] +else: + UNIFIED_SOURCES += [ 'GLContextProvider%s.cpp' % gl_provider, ] -SOURCES += [ +UNIFIED_SOURCES += [ 'GfxTexturesReporter.cpp', 'GLContext.cpp', 'GLContextFeatures.cpp', diff --git a/gfx/graphite2/src/moz.build b/gfx/graphite2/src/moz.build index acbe4d2dad5d..f75fe45bc1b9 100644 --- a/gfx/graphite2/src/moz.build +++ b/gfx/graphite2/src/moz.build @@ -13,16 +13,16 @@ EXPORTS.graphite2 += [ ] if CONFIG['GNU_CC']: - SOURCES += [ + UNIFIED_SOURCES += [ 'direct_machine.cpp' ] else: - SOURCES += [ + UNIFIED_SOURCES += [ 'call_machine.cpp' ] # This should contain all of the _SOURCES from files.mk, except *_machine.cpp -SOURCES += [ +UNIFIED_SOURCES += [ 'Bidi.cpp', 'CachedFace.cpp', 'CmapCache.cpp', diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 39fcbd4eb8b0..1ed1b033100a 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -1261,6 +1261,12 @@ Layer::LogSelf(const char* aPrefix) nsAutoCString str; PrintInfo(str, aPrefix); MOZ_LAYERS_LOG(("%s", str.get())); + + if (mMaskLayer) { + nsAutoCString pfx(aPrefix); + pfx += " \\ MaskLayer "; + mMaskLayer->LogSelf(pfx.get()); + } } nsACString& @@ -1305,6 +1311,9 @@ Layer::PrintInfo(nsACString& aTo, const char* aPrefix) mStickyPositionData->mInner.x, mStickyPositionData->mInner.y, mStickyPositionData->mInner.width, mStickyPositionData->mInner.height); } + if (mMaskLayer) { + aTo.AppendPrintf(" [mMaskLayer=%p]", mMaskLayer.get()); + } return aTo; } diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp index 49d6a51a733a..cd4421f1cb97 100644 --- a/gfx/layers/composite/LayerManagerComposite.cpp +++ b/gfx/layers/composite/LayerManagerComposite.cpp @@ -168,10 +168,11 @@ LayerManagerComposite::BeginTransactionWithDrawTarget(DrawTarget* aTarget) bool LayerManagerComposite::EndEmptyTransaction(EndTransactionFlags aFlags) { - mInTransaction = false; - - if (!mRoot) + NS_ASSERTION(mInTransaction, "Didn't call BeginTransaction?"); + if (!mRoot) { + mInTransaction = false; return false; + } EndTransaction(nullptr, nullptr); return true; @@ -182,6 +183,7 @@ LayerManagerComposite::EndTransaction(DrawThebesLayerCallback aCallback, void* aCallbackData, EndTransactionFlags aFlags) { + NS_ASSERTION(mInTransaction, "Didn't call BeginTransaction?"); mInTransaction = false; #ifdef MOZ_LAYERS_HAVE_LOG diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index c1f4a32a33f3..60e708fe3f5e 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -500,6 +500,15 @@ CompositorParent::ScheduleComposition() void CompositorParent::Composite() +{ + if (CanComposite()) { + mLayerManager->BeginTransaction(); + } + CompositeInTransaction(); +} + +void +CompositorParent::CompositeInTransaction() { profiler_tracing("Paint", "Composite", TRACING_INTERVAL_START); PROFILER_LABEL("CompositorParent", "Composite"); @@ -564,7 +573,7 @@ CompositorParent::ComposeToTarget(DrawTarget* aTarget) mLayerManager->BeginTransactionWithDrawTarget(aTarget); // Since CanComposite() is true, Composite() must end the layers txn // we opened above. - Composite(); + CompositeInTransaction(); } bool @@ -629,10 +638,7 @@ CompositorParent::ShadowLayersUpdated(LayerTransactionParent* aLayerTree, } } ScheduleComposition(); - LayerManagerComposite *layerComposite = mLayerManager->AsLayerManagerComposite(); - if (layerComposite) { - layerComposite->NotifyShadowTreeTransaction(); - } + mLayerManager->NotifyShadowTreeTransaction(); } void diff --git a/gfx/layers/ipc/CompositorParent.h b/gfx/layers/ipc/CompositorParent.h index be1fbfe5d87d..544500b8b720 100644 --- a/gfx/layers/ipc/CompositorParent.h +++ b/gfx/layers/ipc/CompositorParent.h @@ -230,7 +230,8 @@ protected: bool* aSuccess); virtual bool DeallocPLayerTransactionParent(PLayerTransactionParent* aLayers); virtual void ScheduleTask(CancelableTask*, int); - virtual void Composite(); + void Composite(); + void CompositeInTransaction(); virtual void ComposeToTarget(gfx::DrawTarget* aTarget); void SetEGLSurfaceSize(int width, int height); diff --git a/gfx/ots/src/moz.build b/gfx/ots/src/moz.build index 5a6e489a9ae1..ab39e428e64e 100644 --- a/gfx/ots/src/moz.build +++ b/gfx/ots/src/moz.build @@ -10,25 +10,31 @@ EXPORTS += [ ] SOURCES += [ + # don't unify sources that use a (file-specific) DROP_THIS_TABLE macro + 'gasp.cc', + 'gdef.cc', + 'gpos.cc', + 'gsub.cc', + 'hdmx.cc', + 'kern.cc', + 'ltsh.cc', + 'vdmx.cc', + 'vorg.cc', +] + +UNIFIED_SOURCES += [ 'cff.cc', 'cff_type2_charstring.cc', 'cmap.cc', 'cvt.cc', 'fpgm.cc', - 'gasp.cc', - 'gdef.cc', 'glyf.cc', - 'gpos.cc', 'graphite.cc', - 'gsub.cc', - 'hdmx.cc', 'head.cc', 'hhea.cc', 'hmtx.cc', - 'kern.cc', 'layout.cc', 'loca.cc', - 'ltsh.cc', 'maxp.cc', 'metrics.cc', 'name.cc', @@ -37,10 +43,8 @@ SOURCES += [ 'post.cc', 'prep.cc', 'svg.cc', - 'vdmx.cc', 'vhea.cc', 'vmtx.cc', - 'vorg.cc', ] MSVC_ENABLE_PGO = True diff --git a/gfx/tests/reftest/reftest.list b/gfx/tests/reftest/reftest.list index 7fd9bb9b425a..0e7583ea2429 100644 --- a/gfx/tests/reftest/reftest.list +++ b/gfx/tests/reftest/reftest.list @@ -1,4 +1,4 @@ # 468496-1 will also detect bugs in video drivers. == 468496-1.html 468496-1-ref.html -== 611498-1.html 611498-ref.html +fuzzy-if(winWidget,175,443) == 611498-1.html 611498-ref.html skip-if(B2G) fuzzy-if(Android&&AndroidVersion>=15,8,1000) == 709477-1.html 709477-1-ref.html # bug 773482 diff --git a/image/src/Decoder.h b/image/src/Decoder.h index 15b937de47e1..0387a98954b9 100644 --- a/image/src/Decoder.h +++ b/image/src/Decoder.h @@ -94,11 +94,6 @@ public: mSizeDecode = aSizeDecode; } - void SetSynchronous(bool aSynchronous) - { - mSynchronous = aSynchronous; - } - bool IsSynchronous() const { return mSynchronous; @@ -246,6 +241,17 @@ protected: bool mDataError; private: + // Decode in synchronous mode. This is unsafe off-main-thread since it may + // attempt to allocate frames. To ensure that we never accidentally leave the + // decoder in synchronous mode, this should only be called by + // AutoSetSyncDecode. + void SetSynchronous(bool aSynchronous) + { + mSynchronous = aSynchronous; + } + + friend class AutoSetSyncDecode; + uint32_t mFrameCount; // Number of frames, including anything in-progress nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame. @@ -285,6 +291,34 @@ private: bool mSynchronous; }; +// A RAII helper class to automatically pair a call to SetSynchronous(true) +// with a call to SetSynchronous(false), since failing to do so can lead us +// to try to allocate frames off-main-thread, which is unsafe. Synchronous +// decoding may only happen within the scope of an AutoSetSyncDecode. Nested +// AutoSetSyncDecode's are OK. +class AutoSetSyncDecode +{ +public: + AutoSetSyncDecode(Decoder* aDecoder) + : mDecoder(aDecoder) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mDecoder); + + mOriginalValue = mDecoder->IsSynchronous(); + mDecoder->SetSynchronous(true); + } + + ~AutoSetSyncDecode() + { + mDecoder->SetSynchronous(mOriginalValue); + } + +private: + nsRefPtr mDecoder; + bool mOriginalValue; +}; + } // namespace image } // namespace mozilla diff --git a/image/src/ImageMetadata.h b/image/src/ImageMetadata.h index 5ec74a3e209b..3c32ef66a3a3 100644 --- a/image/src/ImageMetadata.h +++ b/image/src/ImageMetadata.h @@ -4,6 +4,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef ImageMetadata_h___ +#define ImageMetadata_h___ + #include #include "mozilla/Maybe.h" #include "nsSize.h" @@ -72,3 +75,5 @@ private: } // namespace image } // namespace mozilla + +#endif // ImageMetadata_h___ diff --git a/image/src/RasterImage.cpp b/image/src/RasterImage.cpp index 6f0db725dbdb..389e0716750a 100644 --- a/image/src/RasterImage.cpp +++ b/image/src/RasterImage.cpp @@ -1589,10 +1589,11 @@ RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount) // write the data directly to the decoder. (If we haven't gotten the size, // we'll queue up the data and write it out when we do.) if (!StoringSourceData() && mHasSize) { - mDecoder->SetSynchronous(true); - rv = WriteToDecoder(aBuffer, aCount); - mDecoder->SetSynchronous(false); - CONTAINER_ENSURE_SUCCESS(rv); + { + AutoSetSyncDecode syncDecode(mDecoder); + rv = WriteToDecoder(aBuffer, aCount); + CONTAINER_ENSURE_SUCCESS(rv); + } // We're not storing source data, so this data is probably coming straight // from the network. In this case, we want to display data as soon as we @@ -1961,7 +1962,7 @@ RasterImage::StoringSourceData() const { // Sets up a decoder for this image. It is an error to call this function // when decoding is already in process (ie - when mDecoder is non-null). nsresult -RasterImage::InitDecoder(bool aDoSizeDecode, bool aIsSynchronous /* = false */) +RasterImage::InitDecoder(bool aDoSizeDecode) { // Ensure that the decoder is not already initialized NS_ABORT_IF_FALSE(!mDecoder, "Calling InitDecoder() while already decoding!"); @@ -2026,7 +2027,6 @@ RasterImage::InitDecoder(bool aDoSizeDecode, bool aIsSynchronous /* = false */) mDecoder->SetObserver(mDecodeRequest->mStatusTracker->GetDecoderObserver()); mDecoder->SetSizeDecode(aDoSizeDecode); mDecoder->SetDecodeFlags(mFrameDecodeFlags); - mDecoder->SetSynchronous(aIsSynchronous); if (!aDoSizeDecode) { // We already have the size; tell the decoder so it can preallocate a // frame. By default, we create an ARGB frame with no offset. If decoders @@ -2313,14 +2313,8 @@ RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType) // to finish decoding. if (!mDecoded && !mInDecoder && mHasSourceData && aDecodeType == SYNCHRONOUS_NOTIFY_AND_SOME_DECODE) { PROFILER_LABEL_PRINTF("RasterImage", "DecodeABitOf", "%s", GetURIString().get()); - mDecoder->SetSynchronous(true); - + AutoSetSyncDecode syncDecode(mDecoder); DecodePool::Singleton()->DecodeABitOf(this); - - // DecodeABitOf can destroy mDecoder. - if (mDecoder) { - mDecoder->SetSynchronous(false); - } return NS_OK; } @@ -2398,15 +2392,17 @@ RasterImage::SyncDecode() // If we don't have a decoder, create one if (!mDecoder) { - rv = InitDecoder(/* aDoSizeDecode = */ false, /* aIsSynchronous = */ true); + rv = InitDecoder(/* aDoSizeDecode = */ false); CONTAINER_ENSURE_SUCCESS(rv); - } else { - mDecoder->SetSynchronous(true); } - // Write everything we have - rv = DecodeSomeData(mSourceData.Length() - mBytesDecoded); - CONTAINER_ENSURE_SUCCESS(rv); + { + AutoSetSyncDecode syncDecode(mDecoder); + + // Write everything we have + rv = DecodeSomeData(mSourceData.Length() - mBytesDecoded); + CONTAINER_ENSURE_SUCCESS(rv); + } // When we're doing a sync decode, we want to get as much information from the // image as possible. We've send the decoder all of our data, so now's a good @@ -2419,11 +2415,9 @@ RasterImage::SyncDecode() rv = FinishedSomeDecoding(); CONTAINER_ENSURE_SUCCESS(rv); - + + // If our decoder's still open, there's still work to be done. if (mDecoder) { - mDecoder->SetSynchronous(false); - - // If our decoder's still open, there's still work to be done. DecodePool::Singleton()->RequestDecode(this); } diff --git a/image/src/RasterImage.h b/image/src/RasterImage.h index 13d116fbfab6..785fec2db492 100644 --- a/image/src/RasterImage.h +++ b/image/src/RasterImage.h @@ -690,7 +690,7 @@ private: // data // Decoding nsresult WantDecodedFrames(); nsresult SyncDecode(); - nsresult InitDecoder(bool aDoSizeDecode, bool aIsSynchronous = false); + nsresult InitDecoder(bool aDoSizeDecode); nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount); nsresult DecodeSomeData(uint32_t aMaxBytes); bool IsDecodeFinished(); diff --git a/image/src/SVGDocumentWrapper.cpp b/image/src/SVGDocumentWrapper.cpp index 15984bae188d..3fcd869d7bde 100644 --- a/image/src/SVGDocumentWrapper.cpp +++ b/image/src/SVGDocumentWrapper.cpp @@ -28,6 +28,9 @@ #include "mozilla/dom/SVGAnimatedLength.h" #include "nsMimeTypes.h" +// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK +#undef GetCurrentTime + using namespace mozilla::dom; namespace mozilla { diff --git a/image/src/SVGDocumentWrapper.h b/image/src/SVGDocumentWrapper.h index 9b4cc9c02ac2..d70a0241f440 100644 --- a/image/src/SVGDocumentWrapper.h +++ b/image/src/SVGDocumentWrapper.h @@ -25,6 +25,8 @@ struct nsIntSize; #define OBSERVER_SVC_CID "@mozilla.org/observer-service;1" +// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK +#undef GetCurrentTime namespace mozilla { namespace dom { diff --git a/image/src/VectorImage.cpp b/image/src/VectorImage.cpp index 6a72d2157d36..e99c7a1f4fc3 100644 --- a/image/src/VectorImage.cpp +++ b/image/src/VectorImage.cpp @@ -26,6 +26,9 @@ #include "nsIDOMEventListener.h" #include "SurfaceCache.h" +// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK +#undef GetCurrentTime + namespace mozilla { using namespace dom; @@ -488,20 +491,35 @@ VectorImage::RequestRefresh(const mozilla::TimeStamp& aTime) // TODO: Implement for b666446. EvaluateAnimation(); - if (mHasPendingInvalidation && mStatusTracker) { - // This method is called under the Tick() of an observing document's - // refresh driver. We send out the following notifications here rather than - // under WillRefresh() (which would be called by our own refresh driver) so - // that we only send these notifications if we actually have a document - // that is observing us. - // XXX(seth): We may need to duplicate this code so that non-animated images - // get handled correctly. See bug 922899. + if (mHasPendingInvalidation) { + SendInvalidationNotifications(); + mHasPendingInvalidation = false; + } +} + +void +VectorImage::SendInvalidationNotifications() +{ + // Animated images don't send out invalidation notifications as soon as + // they're generated. Instead, InvalidateObserversOnNextRefreshDriverTick + // records that there are pending invalidations and then returns immediately. + // The notifications are actually sent from RequestRefresh(). We send these + // notifications there to ensure that there is actually a document observing + // us. Otherwise, the notifications are just wasted effort. + // + // Non-animated images call this method directly from + // InvalidateObserversOnNextRefreshDriverTick, because RequestRefresh is never + // called for them. Ordinarily this isn't needed, since we send out + // invalidation notifications in OnSVGDocumentLoaded, but in rare cases the + // SVG document may not be 100% ready to render at that time. In those cases + // we would miss the subsequent invalidations if we didn't send out the + // notifications directly in |InvalidateObservers...|. + + if (mStatusTracker) { SurfaceCache::Discard(this); mStatusTracker->FrameChanged(&nsIntRect::GetMaxSizedIntRect()); mStatusTracker->OnStopFrame(); } - - mHasPendingInvalidation = false; } //****************************************************************************** @@ -1127,7 +1145,11 @@ VectorImage::OnDataAvailable(nsIRequest* aRequest, nsISupports* aCtxt, void VectorImage::InvalidateObserversOnNextRefreshDriverTick() { - mHasPendingInvalidation = true; + if (mHaveAnimations) { + mHasPendingInvalidation = true; + } else { + SendInvalidationNotifications(); + } } } // namespace image diff --git a/image/src/VectorImage.h b/image/src/VectorImage.h index 6eda36e9e839..96f4e1a26aab 100644 --- a/image/src/VectorImage.h +++ b/image/src/VectorImage.h @@ -89,6 +89,7 @@ protected: private: void CancelAllListeners(); + void SendInvalidationNotifications(); nsRefPtr mSVGDocumentWrapper; nsRefPtr mRenderingObserver; diff --git a/image/src/moz.build b/image/src/moz.build index 11573ec861f3..7fc8d0559cf1 100644 --- a/image/src/moz.build +++ b/image/src/moz.build @@ -12,7 +12,7 @@ EXPORTS += [ 'Orientation.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'ClippedImage.cpp', 'Decoder.cpp', 'DiscardTracker.cpp', @@ -26,19 +26,23 @@ SOURCES += [ 'ImageOps.cpp', 'ImageWrapper.cpp', 'imgFrame.cpp', - 'imgLoader.cpp', - 'imgRequest.cpp', - 'imgRequestProxy.cpp', - 'imgStatusTracker.cpp', 'imgTools.cpp', 'OrientedImage.cpp', - 'RasterImage.cpp', 'ScriptedNotificationObserver.cpp', 'SurfaceCache.cpp', 'SVGDocumentWrapper.cpp', 'VectorImage.cpp', ] +# These files can't be unified because of ImageLogging.h #include order issues. +SOURCES += [ + 'imgLoader.cpp', + 'imgRequest.cpp', + 'imgRequestProxy.cpp', + 'imgStatusTracker.cpp', + 'RasterImage.cpp', +] + FAIL_ON_WARNINGS = True include('/ipc/chromium/chromium-config.mozbuild') diff --git a/image/test/mochitest/bug900200-ref.png b/image/test/mochitest/bug900200-ref.png new file mode 100644 index 000000000000..636013132500 Binary files /dev/null and b/image/test/mochitest/bug900200-ref.png differ diff --git a/image/test/mochitest/bug900200.png b/image/test/mochitest/bug900200.png new file mode 100644 index 000000000000..d7d87adce09a Binary files /dev/null and b/image/test/mochitest/bug900200.png differ diff --git a/image/test/mochitest/clear.gif b/image/test/mochitest/clear.gif new file mode 100644 index 000000000000..7ae79ba86e4e Binary files /dev/null and b/image/test/mochitest/clear.gif differ diff --git a/image/test/mochitest/clear.png b/image/test/mochitest/clear.png new file mode 100644 index 000000000000..b09aecaaa031 Binary files /dev/null and b/image/test/mochitest/clear.png differ diff --git a/image/test/mochitest/clear2-results.gif b/image/test/mochitest/clear2-results.gif new file mode 100644 index 000000000000..965b65025334 Binary files /dev/null and b/image/test/mochitest/clear2-results.gif differ diff --git a/image/test/mochitest/clear2.gif b/image/test/mochitest/clear2.gif new file mode 100644 index 000000000000..00ad873c65d7 Binary files /dev/null and b/image/test/mochitest/clear2.gif differ diff --git a/image/test/mochitest/green-background.html b/image/test/mochitest/green-background.html new file mode 100644 index 000000000000..afc3c206e4fc --- /dev/null +++ b/image/test/mochitest/green-background.html @@ -0,0 +1,28 @@ + + + +Background color wrapper for clear image tests + + + + + + + diff --git a/image/test/mochitest/green.png b/image/test/mochitest/green.png new file mode 100644 index 000000000000..7df25f33bdb2 Binary files /dev/null and b/image/test/mochitest/green.png differ diff --git a/image/test/mochitest/grey.png b/image/test/mochitest/grey.png new file mode 100644 index 000000000000..5c82cdeb10c8 Binary files /dev/null and b/image/test/mochitest/grey.png differ diff --git a/image/test/mochitest/keep.gif b/image/test/mochitest/keep.gif new file mode 100644 index 000000000000..e967d6a6dc96 Binary files /dev/null and b/image/test/mochitest/keep.gif differ diff --git a/image/test/mochitest/keep.png b/image/test/mochitest/keep.png new file mode 100644 index 000000000000..aa3ff7445025 Binary files /dev/null and b/image/test/mochitest/keep.png differ diff --git a/image/test/mochitest/mochitest.ini b/image/test/mochitest/mochitest.ini index 5846156b79d3..9d443106aba5 100644 --- a/image/test/mochitest/mochitest.ini +++ b/image/test/mochitest/mochitest.ini @@ -28,16 +28,31 @@ support-files = bug767779.sjs bug89419-iframe.html bug89419.sjs + bug900200.png + bug900200-ref.png + clear.gif + clear.png + clear2.gif + clear2-results.gif damon.jpg error-early.png + green.png + green-background.html + grey.png imgutils.js invalid.jpg + keep.gif + keep.png lime100x100.svg red.png + restore-previous.gif + restore-previous.png rillybad.jpg schrep.png shaver.png short_header.gif + source.png + over.png [test_ImageContentLoaded.html] [test_bug399925.html] @@ -63,6 +78,7 @@ support-files = [test_bug865919.html] [test_bug89419-1.html] [test_bug89419-2.html] +[test_animation_operators.html] [test_drawDiscardedImage.html] [test_error_events.html] [test_short_gif_header.html] diff --git a/image/test/mochitest/over.png b/image/test/mochitest/over.png new file mode 100644 index 000000000000..9e957182f7e7 Binary files /dev/null and b/image/test/mochitest/over.png differ diff --git a/image/test/mochitest/restore-previous.gif b/image/test/mochitest/restore-previous.gif new file mode 100644 index 000000000000..15ba9ddc48df Binary files /dev/null and b/image/test/mochitest/restore-previous.gif differ diff --git a/image/test/mochitest/restore-previous.png b/image/test/mochitest/restore-previous.png new file mode 100644 index 000000000000..09dee6382089 Binary files /dev/null and b/image/test/mochitest/restore-previous.png differ diff --git a/image/test/mochitest/source.png b/image/test/mochitest/source.png new file mode 100644 index 000000000000..df1c76dae5d2 Binary files /dev/null and b/image/test/mochitest/source.png differ diff --git a/image/test/mochitest/test_animation_operators.html b/image/test/mochitest/test_animation_operators.html new file mode 100644 index 000000000000..b4b26b65af1b --- /dev/null +++ b/image/test/mochitest/test_animation_operators.html @@ -0,0 +1,157 @@ + + + + + Test for Bug 936720 + + + + + +Mozilla Bug 936720 +
+
+
+ + diff --git a/image/test/reftest/animated/delay-test.html b/image/test/reftest/animated/delay-test.html index f2ed5279e1b3..7372a6bc2435 100644 --- a/image/test/reftest/animated/delay-test.html +++ b/image/test/reftest/animated/delay-test.html @@ -38,7 +38,7 @@ function forceDecode() { } function startTimer() { - const delay = 1000; + const delay = 10000; setTimeout("document.documentElement.className = '';", delay); } diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index f8782a41df3f..f758f52a96eb 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -272,7 +272,6 @@ struct NotableStringInfo : public StringInfo { NotableStringInfo(); NotableStringInfo(JSString *str, const StringInfo &info); - NotableStringInfo(const NotableStringInfo& info); NotableStringInfo(NotableStringInfo &&info); NotableStringInfo &operator=(NotableStringInfo &&info); @@ -286,10 +285,10 @@ struct NotableStringInfo : public StringInfo return js::MemoryReportingSundriesThreshold(); } - // The amount of memory we requested for |buffer|; i.e. - // buffer = malloc(bufferSize). - size_t bufferSize; char *buffer; + + private: + NotableStringInfo(const NotableStringInfo& info) MOZ_DELETE; }; // These measurements relate directly to the JSRuntime, and not to zones and diff --git a/js/public/Value.h b/js/public/Value.h index 0beabd8fff3e..dc8225b6dfd9 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -1520,7 +1520,6 @@ template <> struct GCMethods #endif }; -template class UnbarrieredMutableValueOperations; template class MutableValueOperations; /* @@ -1532,7 +1531,6 @@ template class MutableValueOperations; template class ValueOperations { - friend class UnbarrieredMutableValueOperations; friend class MutableValueOperations; const JS::Value * value() const { return static_cast(this)->extract(); } @@ -1573,16 +1571,14 @@ class ValueOperations }; /* - * A class designed for CRTP use in implementing the mutating parts of the Value - * interface in Value-like classes that don't need post barriers. Outer must be - * a class inheriting UnbarrieredMutableValueOperations with visible - * extractMutable() and extract() methods returning the const Value* and Value* - * abstracted by Outer. + * A class designed for CRTP use in implementing all the mutating parts of the + * Value interface in Value-like classes. Outer must be a class inheriting + * MutableValueOperations with visible extractMutable() and extract() + * methods returning the const Value* and Value* abstracted by Outer. */ template -class UnbarrieredMutableValueOperations : public ValueOperations +class MutableValueOperations : public ValueOperations { - friend class MutableValueOperations; JS::Value * value() { return static_cast(this)->extractMutable(); } public: @@ -1595,18 +1591,6 @@ class UnbarrieredMutableValueOperations : public ValueOperations void setMagic(JSWhyMagic why) { value()->setMagic(why); } bool setNumber(uint32_t ui) { return value()->setNumber(ui); } bool setNumber(double d) { return value()->setNumber(d); } -}; - -/* - * A class designed for CRTP use in implementing all the mutating parts of the - * Value interface in Value-like classes. Outer must be a class inheriting - * MutableValueOperations with visible extractMutable() and extract() - * methods returning the const Value* and Value* abstracted by Outer. - */ -template -class MutableValueOperations : public UnbarrieredMutableValueOperations -{ - public: void setString(JSString *str) { this->value()->setString(str); } void setString(const JS::Anchor &str) { this->value()->setString(str); } void setObject(JSObject &obj) { this->value()->setObject(obj); } @@ -1696,7 +1680,6 @@ class MutableHandleBase : public MutableValueOperations*>(this)->address(); } - friend class UnbarrieredMutableValueOperations >; friend class MutableValueOperations >; JS::Value * extractMutable() { return static_cast*>(this)->address(); @@ -1715,7 +1698,6 @@ class RootedBase : public MutableValueOperations*>(this)->address(); } - friend class UnbarrieredMutableValueOperations >; friend class MutableValueOperations >; JS::Value * extractMutable() { return static_cast*>(this)->address(); diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 2268b20f8854..cc7e38adadc4 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -238,7 +238,7 @@ ifeq ($(OS_ARCH),WINNT) ICU_LIB_SUFFIX=d endif ICU_LIB_RENAME = $(foreach libname,$(ICU_LIB_NAMES),\ - cp -p intl/icu/target/lib/s$(libname)$(ICU_LIB_SUFFIX).lib intl/icu/target/lib/$(libname).lib;) + cp -p intl/icu/lib/s$(libname)$(ICU_LIB_SUFFIX).lib intl/icu/lib/$(libname).lib;) endif ifdef .PYMAKE @@ -254,17 +254,11 @@ endif # - Options for genrb: -k strict parsing; -R omit collation tailoring rules. buildicu: # ICU's build system is full of races, so force non-parallel build. -ifdef CROSS_COMPILE - +$(ICU_MAKE) -j1 -C intl/icu/host STATIC_O=$(OBJ_SUFFIX) GENRBOPTS='-k -R -C' -endif - +$(ICU_MAKE) -j1 -C intl/icu/target STATIC_O=$(OBJ_SUFFIX) GENRBOPTS='-k -R' + +$(ICU_MAKE) -j1 -C intl/icu STATIC_O=$(OBJ_SUFFIX) GENRBOPTS='-k -R' $(ICU_LIB_RENAME) distclean clean:: -ifdef CROSS_COMPILE - $(call SUBMAKE,$@,intl/icu/host) -endif - $(call SUBMAKE,$@,intl/icu/target) + $(call SUBMAKE,$@,intl/icu) endif endif @@ -274,14 +268,18 @@ endif ############################################# -# The "find any vanilla new/new[] calls" script is tailored to Linux, so -# only run it there. That should be enough to catch any such calls that -# creep in. -check-vanilla-new: - $(srcdir)/config/find_vanilla_new_calls $(LIBRARY) +# check_vanilla_allocations.py is tailored to Linux, so only run it there. +# That should be enough to catch any problems. +check-vanilla-allocations: + $(PYTHON) $(srcdir)/config/check_vanilla_allocations.py $(REAL_LIBRARY) + +# The "aggressive" variant will likely fail on some compiler/platform +# combinations, but is worth running by hand every once in a while. +check-vanilla-allocations-aggressive: + $(PYTHON) $(srcdir)/config/check_vanilla_allocations.py --aggressive $(REAL_LIBRARY) ifeq ($(OS_ARCH),Linux) -check:: check-vanilla-new +check:: check-vanilla-allocations endif # Help ensure that the number of OOM errors in SpiderMonkey doesn't increase. diff --git a/js/src/aclocal.m4 b/js/src/aclocal.m4 index ef750a638da3..cf2063441ec2 100644 --- a/js/src/aclocal.m4 +++ b/js/src/aclocal.m4 @@ -11,7 +11,6 @@ builtin(include, build/autoconf/ccache.m4)dnl builtin(include, build/autoconf/wrapper.m4)dnl builtin(include, build/autoconf/pkg.m4)dnl builtin(include, build/autoconf/nspr.m4)dnl -builtin(include, build/autoconf/codeset.m4)dnl builtin(include, build/autoconf/altoptions.m4)dnl builtin(include, build/autoconf/mozprog.m4)dnl builtin(include, build/autoconf/mozheader.m4)dnl diff --git a/js/src/assembler/assembler/X86Assembler.h b/js/src/assembler/assembler/X86Assembler.h index 51354b36470a..bd3f2e542db5 100644 --- a/js/src/assembler/assembler/X86Assembler.h +++ b/js/src/assembler/assembler/X86Assembler.h @@ -283,6 +283,7 @@ private: } OneByteOpcodeID; typedef enum { + OP2_UD2 = 0x0B, OP2_MOVSD_VsdWsd = 0x10, OP2_MOVSD_WsdVsd = 0x11, OP2_UNPCKLPS_VsdWsd = 0x14, @@ -3025,6 +3026,12 @@ public: m_formatter.oneByteOp(OP_INT3); } + void ud2() + { + spew("ud2"); + m_formatter.twoByteOp(OP2_UD2); + } + void ret() { spew("ret"); diff --git a/js/src/build/autoconf/codeset.m4 b/js/src/build/autoconf/codeset.m4 deleted file mode 100644 index 4dc1d50187c6..000000000000 --- a/js/src/build/autoconf/codeset.m4 +++ /dev/null @@ -1,24 +0,0 @@ -# codeset.m4 serial AM1 (gettext-0.10.40) -dnl Copyright (C) 2000-2002 Free Software Foundation, Inc. -dnl This file is free software, distributed under the terms of the GNU -dnl General Public License. As a special exception to the GNU General -dnl Public License, this file may be distributed as part of a program -dnl that contains a configuration script generated by Autoconf, under -dnl the same distribution terms as the rest of that program. - -dnl From Bruno Haible. - -AC_DEFUN([AM_LANGINFO_CODESET], -[ - AC_CACHE_CHECK([for nl_langinfo and CODESET], am_cv_langinfo_codeset, - [AC_TRY_LINK([#include ], - [char* cs = nl_langinfo(CODESET);], - am_cv_langinfo_codeset=yes, - am_cv_langinfo_codeset=no) - ]) - if test $am_cv_langinfo_codeset = yes; then - AC_DEFINE(HAVE_LANGINFO_CODESET, 1, - [Define if you have and nl_langinfo(CODESET).]) - HAVE_LANGINFO_CODESET=1 - fi -]) diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 149392002ded..9d8df6ac7e71 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -268,6 +268,9 @@ static const struct ParamPair { {"markStackLimit", JSGC_MARK_STACK_LIMIT} }; +// Keep this in sync with above params. +#define GC_PARAMETER_ARGS_LIST "maxBytes, maxMallocBytes, gcBytes, gcNumber, sliceTimeBudget, or markStackLimit" + static bool GCParameter(JSContext *cx, unsigned argc, Value *vp) { @@ -285,9 +288,7 @@ GCParameter(JSContext *cx, unsigned argc, Value *vp) for (;; paramIndex++) { if (paramIndex == ArrayLength(paramMap)) { JS_ReportError(cx, - "the first argument must be maxBytes, " - "maxMallocBytes, gcStackpoolLifespan, gcBytes or " - "gcNumber"); + "the first argument must be one of " GC_PARAMETER_ARGS_LIST); return false; } if (JS_FlatStringEqualsAscii(flatStr, paramMap[paramIndex].name)) @@ -1368,8 +1369,7 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = { JS_FN_HELP("gcparam", GCParameter, 2, 0, "gcparam(name [, value])", -" Wrapper for JS_[GS]etGCParameter. The name is either maxBytes,\n" -" maxMallocBytes, gcBytes, gcNumber, or sliceTimeBudget."), +" Wrapper for JS_[GS]etGCParameter. The name is one of " GC_PARAMETER_ARGS_LIST), JS_FN_HELP("getBuildConfiguration", GetBuildConfiguration, 0, 0, "getBuildConfiguration()", diff --git a/js/src/builtin/TypeRepresentation.cpp b/js/src/builtin/TypeRepresentation.cpp index 26db68d59109..09af8fd2c234 100644 --- a/js/src/builtin/TypeRepresentation.cpp +++ b/js/src/builtin/TypeRepresentation.cpp @@ -62,6 +62,9 @@ TypeRepresentationHasher::match(TypeRepresentation *key1, case TypeRepresentation::Reference: return matchReferences(key1->asReference(), key2->asReference()); + case TypeRepresentation::X4: + return matchX4s(key1->asX4(), key2->asX4()); + case TypeRepresentation::Struct: return matchStructs(key1->asStruct(), key2->asStruct()); @@ -86,6 +89,13 @@ TypeRepresentationHasher::matchReferences(ReferenceTypeRepresentation *key1, return key1->type() == key2->type(); } +bool +TypeRepresentationHasher::matchX4s(X4TypeRepresentation *key1, + X4TypeRepresentation *key2) +{ + return key1->type() == key2->type(); +} + bool TypeRepresentationHasher::matchStructs(StructTypeRepresentation *key1, StructTypeRepresentation *key2) @@ -122,6 +132,9 @@ TypeRepresentationHasher::hash(TypeRepresentation *key) { case TypeRepresentation::Reference: return hashReference(key->asReference()); + case TypeRepresentation::X4: + return hashX4(key->asX4()); + case TypeRepresentation::Struct: return hashStruct(key->asStruct()); @@ -144,6 +157,12 @@ TypeRepresentationHasher::hashReference(ReferenceTypeRepresentation *key) return HashGeneric(key->kind(), key->type()); } +HashNumber +TypeRepresentationHasher::hashX4(X4TypeRepresentation *key) +{ + return HashGeneric(key->kind(), key->type()); +} + HashNumber TypeRepresentationHasher::hashStruct(StructTypeRepresentation *key) { @@ -172,32 +191,30 @@ TypeRepresentation::TypeRepresentation(Kind kind, size_t size, opaque_(opaque) {} +static size_t ScalarSizes[] = { +#define SCALAR_SIZE(_kind, _type, _name) \ + sizeof(_type), + JS_FOR_EACH_SCALAR_TYPE_REPR(SCALAR_SIZE) 0 +#undef SCALAR_SIZE +}; + ScalarTypeRepresentation::ScalarTypeRepresentation(Type type) - : TypeRepresentation(Scalar, 0, 1, false), + : TypeRepresentation(Scalar, ScalarSizes[type], ScalarSizes[type], false), type_(type) { - switch (type) { - case TYPE_INT8: - case TYPE_UINT8: - case TYPE_UINT8_CLAMPED: - size_ = alignment_ = 1; - break; +} - case TYPE_INT16: - case TYPE_UINT16: - size_ = alignment_ = 2; - break; +static size_t X4Sizes[] = { +#define X4_SIZE(_kind, _type, _name) \ + sizeof(_type) * 4, + JS_FOR_EACH_X4_TYPE_REPR(X4_SIZE) 0 +#undef X4_SIZE +}; - case TYPE_INT32: - case TYPE_UINT32: - case TYPE_FLOAT32: - size_ = alignment_ = 4; - break; - - case TYPE_FLOAT64: - size_ = alignment_ = 8; - break; - } +X4TypeRepresentation::X4TypeRepresentation(Type type) + : TypeRepresentation(X4, X4Sizes[type], X4Sizes[type], false), + type_(type) +{ } ReferenceTypeRepresentation::ReferenceTypeRepresentation(Type type) @@ -358,6 +375,11 @@ TypeRepresentation::addToTableOrFree(JSContext *cx, Int32Value(asReference()->type())); break; + case X4: + ownerObject->initReservedSlot(JS_TYPEREPR_SLOT_TYPE, + Int32Value(asX4()->type())); + break; + case Struct: break; } @@ -366,27 +388,43 @@ TypeRepresentation::addToTableOrFree(JSContext *cx, return &*ownerObject; } +namespace js { +class TypeRepresentationHelper { + public: + template + static JSObject *CreateSimple(JSContext *cx, typename T::Type type) { + JSCompartment *comp = cx->compartment(); + + T sample(type); + TypeRepresentationHash::AddPtr p = comp->typeReprs.lookupForAdd(&sample); + if (p) + return (*p)->ownerObject(); + + // Note: cannot use cx->new_ because constructor is private. + T *ptr = (T *) cx->malloc_(sizeof(T)); + if (!ptr) + return nullptr; + new(ptr) T(type); + + return ptr->addToTableOrFree(cx, p); + } +}; +} // namespace js + /*static*/ JSObject * ScalarTypeRepresentation::Create(JSContext *cx, ScalarTypeRepresentation::Type type) { - JSCompartment *comp = cx->compartment(); + return TypeRepresentationHelper::CreateSimple(cx, type); +} - ScalarTypeRepresentation sample(type); - TypeRepresentationHash::AddPtr p = comp->typeReprs.lookupForAdd(&sample); - if (p) - return (*p)->ownerObject(); - - // Note: cannot use cx->new_ because constructor is private. - ScalarTypeRepresentation *ptr = - (ScalarTypeRepresentation *) cx->malloc_( - sizeof(ScalarTypeRepresentation)); - if (!ptr) - return nullptr; - new(ptr) ScalarTypeRepresentation(type); - - return ptr->addToTableOrFree(cx, p); +/*static*/ +JSObject * +X4TypeRepresentation::Create(JSContext *cx, + X4TypeRepresentation::Type type) +{ + return TypeRepresentationHelper::CreateSimple(cx, type); } /*static*/ @@ -499,6 +537,7 @@ TypeRepresentation::traceFields(JSTracer *trace) switch (kind()) { case Scalar: case Reference: + case X4: break; case Struct: @@ -552,6 +591,9 @@ TypeRepresentation::appendString(JSContext *cx, StringBuffer &contents) case Reference: return asReference()->appendStringReference(cx, contents); + case X4: + return asX4()->appendStringX4(cx, contents); + case Array: return asArray()->appendStringArray(cx, contents); @@ -607,6 +649,18 @@ ReferenceTypeRepresentation::appendStringReference(JSContext *cx, StringBuffer & MOZ_ASSUME_UNREACHABLE("Invalid type"); } +bool +X4TypeRepresentation::appendStringX4(JSContext *cx, StringBuffer &contents) +{ + switch (type()) { + case TYPE_FLOAT32: + return contents.append("float32x4"); + case TYPE_INT32: + return contents.append("int32x4"); + } + MOZ_ASSUME_UNREACHABLE("Invalid type"); +} + bool ArrayTypeRepresentation::appendStringArray(JSContext *cx, StringBuffer &contents) { @@ -672,6 +726,7 @@ visitReferences(TypeRepresentation *repr, uint8_t *mem, V& visitor) switch (repr->kind()) { case TypeRepresentation::Scalar: + case TypeRepresentation::X4: return; case TypeRepresentation::Reference: diff --git a/js/src/builtin/TypeRepresentation.h b/js/src/builtin/TypeRepresentation.h index 4dce2f90265e..33b3d39b9101 100644 --- a/js/src/builtin/TypeRepresentation.h +++ b/js/src/builtin/TypeRepresentation.h @@ -68,6 +68,7 @@ namespace js { class TypeRepresentation; class ScalarTypeRepresentation; class ReferenceTypeRepresentation; +class X4TypeRepresentation; class ArrayTypeRepresentation; class StructTypeRepresentation; @@ -83,6 +84,7 @@ struct TypeRepresentationHasher private: static HashNumber hashScalar(ScalarTypeRepresentation *key); static HashNumber hashReference(ReferenceTypeRepresentation *key); + static HashNumber hashX4(X4TypeRepresentation *key); static HashNumber hashStruct(StructTypeRepresentation *key); static HashNumber hashArray(ArrayTypeRepresentation *key); @@ -90,6 +92,8 @@ struct TypeRepresentationHasher ScalarTypeRepresentation *key2); static bool matchReferences(ReferenceTypeRepresentation *key1, ReferenceTypeRepresentation *key2); + static bool matchX4s(X4TypeRepresentation *key1, + X4TypeRepresentation *key2); static bool matchStructs(StructTypeRepresentation *key1, StructTypeRepresentation *key2); static bool matchArrays(ArrayTypeRepresentation *key1, @@ -100,11 +104,14 @@ typedef js::HashSet TypeRepresentationHash; +class TypeRepresentationHelper; + class TypeRepresentation { public: enum Kind { Scalar = JS_TYPEREPR_SCALAR_KIND, Reference = JS_TYPEREPR_REFERENCE_KIND, + X4 = JS_TYPEREPR_X4_KIND, Struct = JS_TYPEREPR_STRUCT_KIND, Array = JS_TYPEREPR_ARRAY_KIND }; @@ -112,6 +119,9 @@ class TypeRepresentation { protected: TypeRepresentation(Kind kind, size_t size, size_t align, bool opaque); + // in order to call addToTableOrFree() + friend class TypeRepresentationHelper; + size_t size_; size_t alignment_; Kind kind_; @@ -167,6 +177,15 @@ class TypeRepresentation { return (ReferenceTypeRepresentation*) this; } + bool isX4() const { + return kind() == X4; + } + + X4TypeRepresentation *asX4() { + JS_ASSERT(isX4()); + return (X4TypeRepresentation*) this; + } + bool isArray() const { return kind() == Array; } @@ -213,7 +232,10 @@ class ScalarTypeRepresentation : public TypeRepresentation { // so TypeRepresentation can call appendStringScalar() etc friend class TypeRepresentation; - Type type_; + // in order to call constructor + friend class TypeRepresentationHelper; + + const Type type_; explicit ScalarTypeRepresentation(Type type); @@ -290,6 +312,40 @@ class ReferenceTypeRepresentation : public TypeRepresentation { macro_(ReferenceTypeRepresentation::TYPE_OBJECT, HeapPtrObject, Object) \ macro_(ReferenceTypeRepresentation::TYPE_STRING, HeapPtrString, string) +class X4TypeRepresentation : public TypeRepresentation { + public: + enum Type { + TYPE_INT32 = JS_X4TYPEREPR_INT32, + TYPE_FLOAT32 = JS_X4TYPEREPR_FLOAT32, + }; + + private: + // so TypeRepresentation can call appendStringScalar() etc + friend class TypeRepresentation; + + // in order to call constructor + friend class TypeRepresentationHelper; + + const Type type_; + + explicit X4TypeRepresentation(Type type); + + // See TypeRepresentation::appendString() + bool appendStringX4(JSContext *cx, StringBuffer &buffer); + + public: + Type type() const { + return type_; + } + + static JSObject *Create(JSContext *cx, Type type); +}; + +// Must be in same order as the enum ScalarTypeRepresentation::Type: +#define JS_FOR_EACH_X4_TYPE_REPR(macro_) \ + macro_(X4TypeRepresentation::TYPE_INT32, int32_t, int32) \ + macro_(X4TypeRepresentation::TYPE_FLOAT32, float, float32) + class ArrayTypeRepresentation : public TypeRepresentation { private: // so TypeRepresentation can call appendStringArray() etc diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index 63b6aecf7d6f..f82a2421d71e 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -41,6 +41,11 @@ const Class js::TypedObjectClass = { JS_ConvertStub }; +static const JSFunctionSpec TypedObjectMethods[] = { + JS_SELF_HOSTED_FN("objectType", "TypeOfTypedDatum", 1, 0), + JS_FS_END +}; + static void ReportCannotConvertTo(JSContext *cx, HandleValue fromValue, const char *toType) { @@ -129,11 +134,25 @@ IsStructTypeObject(JSObject &type) return type.hasClass(&StructType::class_); } +static inline bool +IsX4TypeObject(JSObject &type) +{ + return type.hasClass(&X4Type::class_); +} + +static inline bool +IsComplexTypeObject(JSObject &type) +{ + return IsArrayTypeObject(type) || IsStructTypeObject(type); +} + static inline bool IsTypeObject(JSObject &type) { - return IsScalarTypeObject(type) || IsArrayTypeObject(type) || - IsStructTypeObject(type) || IsReferenceTypeObject(type); + return IsScalarTypeObject(type) || + IsReferenceTypeObject(type) || + IsX4TypeObject(type) || + IsComplexTypeObject(type); } static inline bool @@ -1265,6 +1284,164 @@ DefineSimpleTypeObject(JSContext *cx, return true; } +/////////////////////////////////////////////////////////////////////////// +// X4 + +const Class X4Type::class_ = { + "X4", + JSCLASS_HAS_RESERVED_SLOTS(JS_TYPEOBJ_X4_SLOTS), + JS_PropertyStub, + JS_DeletePropertyStub, + JS_PropertyStub, + JS_StrictPropertyStub, + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + nullptr, + nullptr, + call, + nullptr, + nullptr, + nullptr +}; + +// These classes just exist to group together various properties and so on. +namespace js { +class Int32x4Defn { + public: + static const X4TypeRepresentation::Type type = X4TypeRepresentation::TYPE_INT32; + static const JSPropertySpec TypedObjectProperties[]; + static const JSFunctionSpec TypedObjectMethods[]; +}; +class Float32x4Defn { + public: + static const X4TypeRepresentation::Type type = X4TypeRepresentation::TYPE_FLOAT32; + static const JSPropertySpec TypedObjectProperties[]; + static const JSFunctionSpec TypedObjectMethods[]; +}; +} // namespace js + +const JSPropertySpec js::Int32x4Defn::TypedObjectProperties[] = { + JS_SELF_HOSTED_GET("x", "Int32x4Lane0", JSPROP_PERMANENT), + JS_SELF_HOSTED_GET("y", "Int32x4Lane1", JSPROP_PERMANENT), + JS_SELF_HOSTED_GET("z", "Int32x4Lane2", JSPROP_PERMANENT), + JS_SELF_HOSTED_GET("w", "Int32x4Lane3", JSPROP_PERMANENT), + JS_PS_END +}; + +const JSFunctionSpec js::Int32x4Defn::TypedObjectMethods[] = { + JS_SELF_HOSTED_FN("toSource", "X4ToSource", 0, 0), + JS_FS_END +}; + +const JSPropertySpec js::Float32x4Defn::TypedObjectProperties[] = { + JS_SELF_HOSTED_GET("x", "Float32x4Lane0", JSPROP_PERMANENT), + JS_SELF_HOSTED_GET("y", "Float32x4Lane1", JSPROP_PERMANENT), + JS_SELF_HOSTED_GET("z", "Float32x4Lane2", JSPROP_PERMANENT), + JS_SELF_HOSTED_GET("w", "Float32x4Lane3", JSPROP_PERMANENT), + JS_PS_END +}; + +const JSFunctionSpec js::Float32x4Defn::TypedObjectMethods[] = { + JS_SELF_HOSTED_FN("toSource", "X4ToSource", 0, 0), + JS_FS_END +}; + +template +static JSObject * +CreateX4Class(JSContext *cx, Handle global) +{ + RootedObject funcProto(cx, global->getOrCreateFunctionPrototype(cx)); + if (!funcProto) + return nullptr; + + // Create type representation + + RootedObject typeReprObj(cx); + typeReprObj = X4TypeRepresentation::Create(cx, T::type); + if (!typeReprObj) + return nullptr; + + // Create prototype property, which inherits from Object.prototype + + RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx)); + if (!objProto) + return nullptr; + RootedObject proto(cx); + proto = NewObjectWithGivenProto(cx, &JSObject::class_, objProto, global, SingletonObject); + if (!proto) + return nullptr; + + // Create type constructor itself + + RootedObject x4(cx); + x4 = NewObjectWithClassProto(cx, &X4Type::class_, funcProto, global); + if (!x4 || + !InitializeCommonTypeDescriptorProperties(cx, x4, typeReprObj) || + !DefinePropertiesAndBrand(cx, proto, nullptr, nullptr)) + { + return nullptr; + } + + // Link type constructor to the type representation + + x4->initReservedSlot(JS_TYPEOBJ_SLOT_TYPE_REPR, ObjectValue(*typeReprObj)); + + // Link constructor to prototype and install properties + + if (!LinkConstructorAndPrototype(cx, x4, proto) || + !DefinePropertiesAndBrand(cx, proto, T::TypedObjectProperties, + T::TypedObjectMethods)) + { + return nullptr; + } + + return x4; +} + +bool +X4Type::call(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + const uint32_t LANES = 4; + + if (args.length() < LANES) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, + args.callee().getClass()->name, "3", "s"); + return false; + } + + double values[LANES]; + for (uint32_t i = 0; i < LANES; i++) { + if (!ToNumber(cx, args[i], &values[i])) + return false; + } + + RootedObject typeObj(cx, &args.callee()); + RootedObject result(cx, TypedObject::createZeroed(cx, typeObj)); + if (!result) + return false; + + X4TypeRepresentation *typeRepr = typeRepresentation(*typeObj)->asX4(); + switch (typeRepr->type()) { +#define STORE_LANES(_constant, _type, _name) \ + case _constant: \ + { \ + _type *mem = (_type*) TypedMem(*result); \ + for (uint32_t i = 0; i < LANES; i++) { \ + mem[i] = values[i]; \ + } \ + break; \ + } + JS_FOR_EACH_X4_TYPE_REPR(STORE_LANES) +#undef STORE_LANES + } + args.rval().setObject(*result); + return true; +} + +/////////////////////////////////////////////////////////////////////////// + template static JSObject * DefineMetaTypeObject(JSContext *cx, @@ -1349,6 +1526,9 @@ js_InitTypedObjectClass(JSContext *cx, HandleObject obj) if (!module) return nullptr; + if (!JS_DefineFunctions(cx, module, TypedObjectMethods)) + return nullptr; + // Define TypedObject global. RootedValue moduleValue(cx, ObjectValue(*module)); @@ -1369,6 +1549,38 @@ js_InitTypedObjectClass(JSContext *cx, HandleObject obj) JS_FOR_EACH_REFERENCE_TYPE_REPR(BINARYDATA_REFERENCE_DEFINE) #undef BINARYDATA_REFERENCE_DEFINE + // float32x4 + + RootedObject float32x4Object(cx); + float32x4Object = CreateX4Class(cx, global); + if (!float32x4Object) + return nullptr; + + RootedValue float32x4Value(cx, ObjectValue(*float32x4Object)); + if (!JSObject::defineProperty(cx, module, cx->names().float32x4, + float32x4Value, + nullptr, nullptr, + JSPROP_READONLY | JSPROP_PERMANENT)) + { + return nullptr; + } + + // int32x4 + + RootedObject int32x4Object(cx); + int32x4Object = CreateX4Class(cx, global); + if (!int32x4Object) + return nullptr; + + RootedValue int32x4Value(cx, ObjectValue(*int32x4Object)); + if (!JSObject::defineProperty(cx, module, cx->names().int32x4, + int32x4Value, + nullptr, nullptr, + JSPROP_READONLY | JSPROP_PERMANENT)) + { + return nullptr; + } + // ArrayType. RootedObject arrayType(cx, DefineMetaTypeObject(cx, global)); @@ -1609,7 +1821,7 @@ TypedDatum::obj_finalize(js::FreeOp *op, JSObject *obj) bool TypedDatum::obj_lookupGeneric(JSContext *cx, HandleObject obj, HandleId id, - MutableHandleObject objp, MutableHandleShape propp) + MutableHandleObject objp, MutableHandleShape propp) { JS_ASSERT(IsTypedDatum(*obj)); @@ -1619,6 +1831,7 @@ TypedDatum::obj_lookupGeneric(JSContext *cx, HandleObject obj, HandleId id, switch (typeRepr->kind()) { case TypeRepresentation::Scalar: case TypeRepresentation::Reference: + case TypeRepresentation::X4: break; case TypeRepresentation::Array: @@ -1797,6 +2010,9 @@ TypedDatum::obj_getGeneric(JSContext *cx, HandleObject obj, HandleObject receive case TypeRepresentation::Reference: break; + case TypeRepresentation::X4: + break; + case TypeRepresentation::Array: if (JSID_IS_ATOM(id, cx->names().length)) { vp.setInt32(typeRepr->asArray()->length()); @@ -1854,6 +2070,7 @@ TypedDatum::obj_getElementIfPresent(JSContext *cx, HandleObject obj, switch (typeRepr->kind()) { case TypeRepresentation::Scalar: case TypeRepresentation::Reference: + case TypeRepresentation::X4: case TypeRepresentation::Struct: break; @@ -1907,6 +2124,9 @@ TypedDatum::obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id, case TypeRepresentation::Reference: break; + case ScalarTypeRepresentation::X4: + break; + case ScalarTypeRepresentation::Array: if (JSID_IS_ATOM(id, cx->names().length)) { JS_ReportErrorNumber(cx, js_GetErrorMessage, @@ -1950,6 +2170,7 @@ TypedDatum::obj_setElement(JSContext *cx, HandleObject obj, uint32_t index, switch (typeRepr->kind()) { case TypeRepresentation::Scalar: case TypeRepresentation::Reference: + case TypeRepresentation::X4: case TypeRepresentation::Struct: break; @@ -1982,7 +2203,7 @@ TypedDatum::obj_setSpecial(JSContext *cx, HandleObject obj, bool TypedDatum::obj_getGenericAttributes(JSContext *cx, HandleObject obj, - HandleId id, unsigned *attrsp) + HandleId id, unsigned *attrsp) { uint32_t index; RootedObject type(cx, GetType(*obj)); @@ -1993,6 +2214,9 @@ TypedDatum::obj_getGenericAttributes(JSContext *cx, HandleObject obj, case TypeRepresentation::Reference: break; + case TypeRepresentation::X4: + break; + case TypeRepresentation::Array: if (js_IdIsIndex(id, &index)) { *attrsp = JSPROP_ENUMERATE | JSPROP_PERMANENT; @@ -2031,6 +2255,7 @@ IsOwnId(JSContext *cx, HandleObject obj, HandleId id) switch (typeRepr->kind()) { case TypeRepresentation::Scalar: case TypeRepresentation::Reference: + case TypeRepresentation::X4: return false; case TypeRepresentation::Array: @@ -2123,6 +2348,7 @@ TypedDatum::obj_enumerate(JSContext *cx, HandleObject obj, JSIterateOp enum_op, switch (typeRepr->kind()) { case TypeRepresentation::Scalar: case TypeRepresentation::Reference: + case TypeRepresentation::X4: switch (enum_op) { case JSENUMERATE_INIT_ALL: case JSENUMERATE_INIT: @@ -2518,6 +2744,15 @@ const JSJitInfo js::MemcpyJitInfo = JS_JITINFO_NATIVE_PARALLEL( JSParallelNativeThreadSafeWrapper); +bool +js::StandardTypeObjectDescriptors(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + Rooted global(cx, cx->global()); + args.rval().setObject(global->getTypedObject()); + return true; +} + #define JS_STORE_SCALAR_CLASS_IMPL(_constant, T, _name) \ bool \ js::StoreScalar##T::Func(ThreadSafeContext *, unsigned argc, Value *vp) \ diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index a8391055ac7c..b678914c239e 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -130,6 +130,18 @@ class ReferenceType static bool call(JSContext *cx, unsigned argc, Value *vp); }; +/* + * Type descriptors `float32x4` and `int32x4` + */ +class X4Type : public JSObject +{ + private: + public: + static const Class class_; + + static bool call(JSContext *cx, unsigned argc, Value *vp); +}; + /* * Type descriptor created by `new ArrayType(...)` */ @@ -426,6 +438,17 @@ extern const JSJitInfo ClampToUint8JitInfo; bool Memcpy(ThreadSafeContext *cx, unsigned argc, Value *vp); extern const JSJitInfo MemcpyJitInfo; +/* + * Usage: StandardTypeObjectDescriptors() + * + * Returns the global "typed object" object, which provides access + * to the various builtin type descriptors. These are currently + * exported as immutable properties so it is safe for self-hosted code + * to access them; eventually this should be linked into the module + * system. + */ +bool StandardTypeObjectDescriptors(JSContext *cx, unsigned argc, Value *vp); + /* * Usage: Store_int8(targetDatum, targetOffset, value) * ... diff --git a/js/src/builtin/TypedObject.js b/js/src/builtin/TypedObject.js index 3ebd50b65008..99162d28e6e8 100644 --- a/js/src/builtin/TypedObject.js +++ b/js/src/builtin/TypedObject.js @@ -107,6 +107,7 @@ TypedObjectPointer.prototype.moveTo = function(propName) { switch (this.kind()) { case JS_TYPEREPR_SCALAR_KIND: case JS_TYPEREPR_REFERENCE_KIND: + case JS_TYPEREPR_X4_KIND: break; case JS_TYPEREPR_ARRAY_KIND: @@ -188,13 +189,22 @@ TypedObjectPointer.prototype.moveToField = function(propName) { TypedObjectPointer.prototype.get = function() { assert(ObjectIsAttached(this.datum), "get() called with unattached datum"); - if (REPR_KIND(this.typeRepr) == JS_TYPEREPR_SCALAR_KIND) + switch (REPR_KIND(this.typeRepr)) { + case JS_TYPEREPR_SCALAR_KIND: return this.getScalar(); - if (REPR_KIND(this.typeRepr) == JS_TYPEREPR_REFERENCE_KIND) + case JS_TYPEREPR_REFERENCE_KIND: return this.getReference(); - return NewDerivedTypedDatum(this.typeObj, this.datum, this.offset); + case JS_TYPEREPR_X4_KIND: + return this.getX4(); + + case JS_TYPEREPR_ARRAY_KIND: + case JS_TYPEREPR_STRUCT_KIND: + return NewDerivedTypedDatum(this.typeObj, this.datum, this.offset); + } + + assert(false, "Unhandled kind: " + REPR_KIND(this.typeRepr)); } TypedObjectPointer.prototype.getScalar = function() { @@ -245,6 +255,27 @@ TypedObjectPointer.prototype.getReference = function() { assert(false, "Unhandled scalar type: " + type); } +TypedObjectPointer.prototype.getX4 = function() { + var type = REPR_TYPE(this.typeRepr); + var T = StandardTypeObjectDescriptors(); + switch (type) { + case JS_X4TYPEREPR_FLOAT32: + var x = Load_float32(this.datum, this.offset + 0); + var y = Load_float32(this.datum, this.offset + 4); + var z = Load_float32(this.datum, this.offset + 8); + var w = Load_float32(this.datum, this.offset + 12); + return T.float32x4(x, y, z, w); + + case JS_X4TYPEREPR_INT32: + var x = Load_int32(this.datum, this.offset + 0); + var y = Load_int32(this.datum, this.offset + 4); + var z = Load_int32(this.datum, this.offset + 8); + var w = Load_int32(this.datum, this.offset + 12); + return T.int32x4(x, y, z, w); + } + assert(false, "Unhandled x4 type: " + type); +} + /////////////////////////////////////////////////////////////////////////// // Setting values // @@ -281,6 +312,10 @@ TypedObjectPointer.prototype.set = function(fromValue) { this.setReference(fromValue); return; + case JS_TYPEREPR_X4_KIND: + this.setX4(fromValue); + return; + case JS_TYPEREPR_ARRAY_KIND: if (!IsObject(fromValue)) break; @@ -382,6 +417,17 @@ TypedObjectPointer.prototype.setReference = function(fromValue) { assert(false, "Unhandled scalar type: " + type); } +// Sets `fromValue` to `this` assuming that `this` is a scalar type. +TypedObjectPointer.prototype.setX4 = function(fromValue) { + // It is only permitted to set a float32x4/int32x4 value from another + // float32x4/int32x4; in that case, the "fast path" that uses memcopy will + // have already matched. So if we get to this point, we're supposed + // to "adapt" fromValue, but there are no legal adaptions. + ThrowError(JSMSG_CANT_CONVERT_TO, + typeof(fromValue), + this.typeRepr.toSource()) +} + /////////////////////////////////////////////////////////////////////////// // C++ Wrappers // @@ -605,9 +651,93 @@ function HandleTest(obj) { return IsObject(obj) && ObjectIsTypedHandle(obj); } +/////////////////////////////////////////////////////////////////////////// +// X4 + +function X4ProtoString(type) { + switch (type) { + case JS_X4TYPEREPR_INT32: + return "int32x4"; + case JS_X4TYPEREPR_FLOAT32: + return "float32x4"; + } + assert(false, "Unhandled type constant"); +} + +X4LaneStrings = ["x", "y", "z", "w"]; + +// Generalized handler for the various properties for accessing a +// single lane of an X4 vector value. Note that this is the slow path; +// the fast path will be inlined into ion code. +function X4GetLane(datum, type, lane) { + if (!IsObject(datum) || !ObjectIsTypedDatum(datum)) + ThrowError(JSMSG_INCOMPATIBLE_PROTO, X4ProtoString(type), + X4LaneStrings[lane], typeof this); + + var repr = DATUM_TYPE_REPR(datum); + if (REPR_KIND(repr) != JS_TYPEREPR_X4_KIND || REPR_TYPE(repr) != type) + ThrowError(JSMSG_INCOMPATIBLE_PROTO, X4ProtoString(type), + X4LaneStrings[lane], typeof this); + + switch (type) { + case JS_X4TYPEREPR_INT32: + return Load_int32(datum, lane * 4); + case JS_X4TYPEREPR_FLOAT32: + return Load_float32(datum, lane * 4); + } + assert(false, "Unhandled type constant"); +} + +function Float32x4Lane0() { return X4GetLane(this, JS_X4TYPEREPR_FLOAT32, 0); } +function Float32x4Lane1() { return X4GetLane(this, JS_X4TYPEREPR_FLOAT32, 1); } +function Float32x4Lane2() { return X4GetLane(this, JS_X4TYPEREPR_FLOAT32, 2); } +function Float32x4Lane3() { return X4GetLane(this, JS_X4TYPEREPR_FLOAT32, 3); } + +function Int32x4Lane0() { return X4GetLane(this, JS_X4TYPEREPR_INT32, 0); } +function Int32x4Lane1() { return X4GetLane(this, JS_X4TYPEREPR_INT32, 1); } +function Int32x4Lane2() { return X4GetLane(this, JS_X4TYPEREPR_INT32, 2); } +function Int32x4Lane3() { return X4GetLane(this, JS_X4TYPEREPR_INT32, 3); } + +function X4ToSource() { + if (!IsObject(this) || !ObjectIsTypedDatum(this)) + ThrowError(JSMSG_INCOMPATIBLE_PROTO, "X4", "toSource", typeof this); + + var repr = DATUM_TYPE_REPR(this); + if (REPR_KIND(repr) != JS_TYPEREPR_X4_KIND) + ThrowError(JSMSG_INCOMPATIBLE_PROTO, "X4", "toSource", typeof this); + + var type = REPR_TYPE(repr); + return X4ProtoString(type)+"("+this.x+", "+this.y+", "+this.z+", "+this.w+")"; +} + /////////////////////////////////////////////////////////////////////////// // Miscellaneous +// This is the `objectType()` function defined in the spec. +// It returns the type of its argument. +// +// Warning: user exposed! +function TypeOfTypedDatum(obj) { + if (IsObject(obj) && ObjectIsTypedDatum(obj)) + return DATUM_TYPE_OBJ(obj); + + // Note: Do not create bindings for `Any`, `String`, etc in + // Utilities.js, but rather access them through + // `StandardTypeObjectDescriptors()`. The reason is that bindings + // you create in Utilities.js are part of the self-hosted global, + // vs the user-accessible global, and hence should not escape to + // user script. + var T = StandardTypeObjectDescriptors(); + switch (typeof obj) { + case "object": return T.Object; + case "function": return T.Object; + case "string": return T.String; + case "number": return T.float64; + case "undefined": return T.Any; + default: return T.Any; + } +} + function ObjectIsTypedDatum(obj) { assert(IsObject(obj), "ObjectIsTypedDatum invoked with non-object") return ObjectIsTypedObject(obj) || ObjectIsTypedHandle(obj); diff --git a/js/src/builtin/TypedObjectConstants.h b/js/src/builtin/TypedObjectConstants.h index 0da7bee5533a..2c47dbbc53e7 100644 --- a/js/src/builtin/TypedObjectConstants.h +++ b/js/src/builtin/TypedObjectConstants.h @@ -26,6 +26,9 @@ // Slots on references #define JS_TYPEOBJ_REFERENCE_SLOTS 1 // Maximum number +// Slots on x4s +#define JS_TYPEOBJ_X4_SLOTS 1 // Maximum number + // Slots on arrays #define JS_TYPEOBJ_SLOT_ARRAY_ELEM_TYPE 1 #define JS_TYPEOBJ_ARRAY_SLOTS 2 // Maximum number @@ -34,7 +37,6 @@ #define JS_TYPEOBJ_SLOT_STRUCT_FIELD_TYPES 1 #define JS_TYPEOBJ_STRUCT_SLOTS 2 // Maximum number - /////////////////////////////////////////////////////////////////////////// // Slots for type representation objects // @@ -52,7 +54,7 @@ // Slots on arrays: #define JS_TYPEREPR_SLOT_LENGTH 3 // Length of the array -// Slots on scalars and references: +// Slots on scalars, references, and X4s: #define JS_TYPEREPR_SLOT_TYPE 3 // One of the constants below // Maximum number of slots for any type representation @@ -61,10 +63,11 @@ // These constants are for use exclusively in JS code. In C++ code, // prefer TypeRepresentation::Scalar etc, which allows you to // write a switch which will receive a warning if you omit a case. -#define JS_TYPEREPR_SCALAR_KIND 0 +#define JS_TYPEREPR_SCALAR_KIND 0 #define JS_TYPEREPR_REFERENCE_KIND 1 -#define JS_TYPEREPR_STRUCT_KIND 2 -#define JS_TYPEREPR_ARRAY_KIND 3 +#define JS_TYPEREPR_X4_KIND 2 +#define JS_TYPEREPR_STRUCT_KIND 3 +#define JS_TYPEREPR_ARRAY_KIND 4 // These constants are for use exclusively in JS code. In C++ code, // prefer ScalarTypeRepresentation::TYPE_INT8 etc, which allows @@ -88,6 +91,13 @@ #define JS_REFERENCETYPEREPR_OBJECT 1 #define JS_REFERENCETYPEREPR_STRING 2 +// These constants are for use exclusively in JS code. In C++ code, +// prefer X4TypeRepresentation::TYPE_INT32 etc, since that allows +// you to write a switch which will receive a warning if you omit a +// case. +#define JS_X4TYPEREPR_INT32 0 +#define JS_X4TYPEREPR_FLOAT32 1 + /////////////////////////////////////////////////////////////////////////// // Slots for typed objects (actually, any TypedContents objects) diff --git a/js/src/config/check_vanilla_allocations.py b/js/src/config/check_vanilla_allocations.py new file mode 100644 index 000000000000..74ea5e61d0c0 --- /dev/null +++ b/js/src/config/check_vanilla_allocations.py @@ -0,0 +1,159 @@ +# vim: set ts=8 sts=4 et sw=4 tw=79: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#---------------------------------------------------------------------------- +# All heap allocations in SpiderMonkey must go through js_malloc, js_calloc, +# js_realloc, and js_free. This is so that any embedder who uses a custom +# allocator (by defining JS_USE_CUSTOM_ALLOCATOR) will see all heap allocation +# go through that custom allocator. +# +# Therefore, the presence of any calls to "vanilla" allocation/free functions +# (e.g. malloc(), free()) is a bug. +# +# This script checks for the presence of such disallowed vanilla +# allocation/free function in SpiderMonkey when it's built as a library. It +# relies on |nm| from the GNU binutils, and so only works on Linux, but one +# platform is good enough to catch almost all violations. +# +# This checking is only 100% reliable in a JS_USE_CUSTOM_ALLOCATOR build in +# which the default definitions of js_malloc et al (in Utility.h) -- which call +# malloc et al -- are replaced with empty definitions. This is because the +# presence and possible inlining of the default js_malloc et al can cause +# malloc/calloc/realloc/free calls show up in unpredictable places. +# +# Unfortunately, that configuration cannot be tested on Mozilla's standard +# testing infrastructure. Instead, by default this script only tests that none +# of the other vanilla allocation/free functions (operator new, memalign, etc) +# are present. If given the --aggressive flag, it will also check for +# malloc/calloc/realloc/free. +# +# Note: We don't check for |operator delete| and |operator delete[]|. These +# can be present somehow due to virtual destructors, but this is not too +# because vanilla delete/delete[] calls don't make sense without corresponding +# vanilla new/new[] calls, and any explicit calls will be caught by Valgrind's +# mismatched alloc/free checking. +#---------------------------------------------------------------------------- + +from __future__ import print_function + +import argparse +import re +import subprocess +import sys + +# The obvious way to implement this script is to search for occurrences of +# malloc et al, succeed if none are found, and fail is some are found. +# However, "none are found" does not necessarily mean "none are present" -- +# this script could be buggy. (Or the output format of |nm| might change in +# the future.) +# +# So jsutil.cpp deliberately contains a (never-called) function that contains a +# single use of all the vanilla allocation/free functions. And this script +# fails if it (a) finds uses of those functions in files other than jsutil.cpp, +# *or* (b) fails to find them in jsutil.cpp. + +# Tracks overall success of the test. +has_failed = False + + +def fail(msg): + print('TEST-UNEXPECTED-FAIL | check_vanilla_allocations.py |', msg) + global has_failed + has_failed = True + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--aggressive', action='store_true', + help='also check for malloc, calloc, realloc and free') + parser.add_argument('file', type=str, + help='name of the file to check') + args = parser.parse_args() + + # Run |nm|. Options: + # -u: show only undefined symbols + # -C: demangle symbol names + # -l: show a filename and line number for each undefined symbol + cmd = ['nm', '-u', '-C', '-l', args.file] + lines = subprocess.check_output(cmd, universal_newlines=True, + stderr=subprocess.PIPE).split('\n') + + # alloc_fns contains all the vanilla allocation/free functions that we look + # for. Regexp chars are escaped appropriately. + + alloc_fns = [ + # Matches |operator new(unsigned T)|, where |T| is |int| or |long|. + r'operator new\(unsigned', + + # Matches |operator new[](unsigned T)|, where |T| is |int| or |long|. + r'operator new\[\]\(unsigned', + + r'memalign', + # These two aren't available on all Linux configurations. + #r'posix_memalign', + #r'aligned_alloc', + r'valloc', + r'strdup' + ] + + if args.aggressive: + alloc_fns += [ + r'malloc', + r'calloc', + r'realloc', + r'free' + ] + + # This is like alloc_fns, but regexp chars are not escaped. + alloc_fns_unescaped = [fn.translate(None, r'\\') for fn in alloc_fns] + + # This regexp matches the relevant lines in the output of |nm|, which look + # like the following. + # + # U malloc /path/to/objdir/dist/include/js/Utility.h:142 + # + alloc_fns_re = r'U (' + r'|'.join(alloc_fns) + r').*\/([\w\.]+):(\d+)$' + + # This tracks which allocation/free functions have been seen in jsutil.cpp. + jsutil_cpp = set([]) + + for line in lines: + m = re.search(alloc_fns_re, line) + if m is None: + continue + + fn = m.group(1) + filename = m.group(2) + linenum = m.group(3) + if filename == 'jsutil.cpp': + jsutil_cpp.add(fn) + else: + # An allocation is present in a non-special file. Fail! + fail("'" + fn + "' present at " + filename + ':' + linenum) + + + # Check that all functions we expect are used in jsutil.cpp. (This will + # fail if the function-detection code breaks at any point.) + for fn in alloc_fns_unescaped: + if fn not in jsutil_cpp: + fail("'" + fn + "' isn't used as expected in jsutil.cpp") + else: + jsutil_cpp.remove(fn) + + # This should never happen, but check just in case. + if jsutil_cpp: + fail('unexpected allocation fns used in jsutil.cpp: ' + + ', '.join(jsutil_cpp)) + + if has_failed: + sys.exit(1) + + print('TEST-PASS | check_vanilla_allocations.py | ok') + sys.exit(0) + + +if __name__ == '__main__': + main() + diff --git a/js/src/config/find_vanilla_new_calls b/js/src/config/find_vanilla_new_calls deleted file mode 100755 index 320abc1403eb..000000000000 --- a/js/src/config/find_vanilla_new_calls +++ /dev/null @@ -1,80 +0,0 @@ -# /bin/bash - -#---------------------------------------------------------------------------- -# We must avoid using the vanilla new/new[] operators (and consequently, the -# vanilla delete/delete[] operators) in SpiderMonkey, see bug 624878 for why. -# -# This script: -# - Detects if any of the vanilla new/new[] operators are used in a file. -# Its exit code is 1 if it found some, and 0 if it didn't. -# - Doesn't detect delete/delete[] because it appears they can be present -# somehow due to virtual destructors, but this is ok because vanilla -# delete/delete[] calls don't make sense without corresponding new/new[] -# calls, and any explicit calls will be caught by Valgrind's mismatched -# alloc/free checking. -# - Doesn't detect the 'nothrow' variants, which are ok but probably still -# best avoided. -# - Is designed to only run on Linux (though it may also work on Mac); one -# platform will be enough to catch any violations. -# -# If this script fails: -# - You need to find the uses of vanilla new/delete and replace them with -# {js::OffTheBooks,JSContext,JSRuntime}::{new_,/array_new}. -# - Run this script on each of the .o files, that should narrow it down. -# - After that, one way to find them is to run 'objdump -r -C' on the -# relevant .o files. For example, you might search for 'operator new' and -# find a record like this: -# -# RELOCATION RECORDS FOR [.text._ZN3JSC14ExecutablePool6createEj]: -# OFFSET TYPE VALUE -# 00000009 R_386_PC32 __i686.get_pc_thunk.bx -# 0000000f R_386_GOTPC _GLOBAL_OFFSET_TABLE_ -# 0000001b R_386_PLT32 operator new(unsigned int) -# 0000002e R_386_PC32 JSC::ExecutablePool::ExecutablePool(unsigned int) -# 0000004a R_386_PC32 JSC::ExecutablePool::~ExecutablePool() -# 00000052 R_386_PLT32 operator delete(void*) -# -# This says that vanilla 'new' and 'delete' are both used in -# JSC::ExecutablePool::create(unsigned int). This doesn't always work, -# though. (Nb: use 'c++filt' to demangle names like -# _ZN3JSC14ExecutablePool6createEj.) -# -# If that doesn't work, use grep. -#---------------------------------------------------------------------------- - -if [ -z $1 ] ; then - echo "usage: find_vanilla_new_calls " - exit 1 -fi - -file=$1 - -if [ ! -f $file ] ; then - echo "TEST-UNEXPECTED-FAIL | find_vanilla_new_calls | file '$file' not found" - exit 1 -fi - -tmpfile1=`mktemp` -tmpfile2=`mktemp` -nm -C $file > $tmpfile1 - -# Need to double-escape '[' and ']' to stop grep from interpreting them -# specially. -grep '^operator new(unsigned int)' $tmpfile1 >> $tmpfile2 -grep '^operator new(unsigned long)' $tmpfile1 >> $tmpfile2 -grep '^operator new\\[\\](unsigned int)' $tmpfile1 >> $tmpfile2 -grep '^operator new\\[\\](unsigned long)' $tmpfile1 >> $tmpfile2 -rm -f $tmpfile1 - -if [ -s $tmpfile2 ] ; then - echo "TEST-UNEXPECTED-FAIL | find_vanilla_new_calls | found calls are listed below" - cat $tmpfile2 - echo - rm -f $tmpfile2 - exit 1 -fi - -echo "TEST-PASS | find_vanilla_new_calls | ok" -echo - -exit 0 diff --git a/js/src/configure.in b/js/src/configure.in index a5596763dec8..f22895f078b6 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -2573,8 +2573,6 @@ dnl AC_CHECK_LIB(bind, res_ninit, AC_DEFINE(HAVE_RES_NINIT), dnl AC_CHECK_LIB(resolv, res_ninit, AC_DEFINE(HAVE_RES_NINIT))) fi -AM_LANGINFO_CODESET - AC_LANG_C dnl ********************** @@ -4198,7 +4196,7 @@ if test -n "$ENABLE_INTL_API"; then *) AC_MSG_ERROR([ECMAScript Internationalization API is not yet supported on this platform]) esac - MOZ_ICU_LIBS='$(call EXPAND_LIBNAME_PATH,$(ICU_LIB_NAMES),$(DEPTH)/intl/icu/target/lib)' + MOZ_ICU_LIBS='$(call EXPAND_LIBNAME_PATH,$(ICU_LIB_NAMES),$(DEPTH)/intl/icu/lib)' fi fi @@ -4228,94 +4226,33 @@ if test -n "$ENABLE_INTL_API" -a -z "$MOZ_NATIVE_ICU"; then ICU_CPPFLAGS="$ICU_CPPFLAGS -DUCONFIG_NO_REGULAR_EXPRESSIONS" ICU_CPPFLAGS="$ICU_CPPFLAGS -DUCONFIG_NO_BREAK_ITERATION" - ICU_CROSS_BUILD_OPT="" ICU_SRCDIR="" - if test "$HOST_OS_ARCH" = "WINNT"; then - ICU_SRCDIR="--srcdir=$(cd $srcdir/../../intl/icu/source; pwd -W)" - fi - - if test "$CROSS_COMPILE"; then - # Building host tools. It is necessary to build target binary. - case "$HOST_OS_ARCH" in - Darwin) - ICU_TARGET=MacOSX - ;; - Linux) - ICU_TARGET=Linux - ;; - WINNT) - ICU_TARGET=MSYS/MSVC - ;; - *bsd*|dragonfly*) - ICU_TARGET=BSD - ;; - esac - - # Remove _DEPEND_CFLAGS from HOST_FLAGS to avoid configure error - HOST_ICU_CFLAGS="$HOST_CFLAGS" - HOST_ICU_CXXFLAGS="$HOST_CXXFLAGS" - - HOST_ICU_CFLAGS=`echo $HOST_ICU_CFLAGS | sed "s|$_DEPEND_CFLAGS||g"` - HOST_ICU_CXXFLAGS=`echo $HOST_ICU_CFXXLAGS | sed "s|$_DEPEND_CFLAGS||g"` - - # ICU requires RTTI - if test "$GNU_CC"; then - HOST_ICU_CXXFLAGS=`echo $HOST_ICU_CXXFLAGS | sed 's|-fno-rtti|-frtti|g'` - elif test "$_MSC_VER"; then - HOST_ICU_CXXFLAGS=`echo $HOST_ICU_CXXFLAGS | sed 's|-GR-|-GR|g'` - fi - - HOST_ICU_BUILD_OPTS="" - if test -n "$MOZ_DEBUG"; then - HOST_ICU_BUILD_OPTS="$HOST_ICU_BUILD_OPTS --enable-debug" - fi - - abs_srcdir=`(cd $srcdir; pwd)` - mkdir -p $_objdir/intl/icu/host - (cd $_objdir/intl/icu/host - MOZ_SUBCONFIGURE_WRAP([.],[ - CC="$HOST_CC" CXX="$HOST_CXX" LD="$HOST_LD" \ - CFLAGS="$HOST_ICU_CFLAGS $HOST_OPTIMIZE_FLAGS" \ - CPPFLAGS="$ICU_CPPFLAGS" \ - CXXFLAGS="$HOST_ICU_CXXFLAGS $HOST_OPTIMZIE_FLAGS" \ - $SHELL $abs_srcdir/../../intl/icu/source/runConfigureICU \ - $HOST_ICU_BUILD_OPTS \ - $ICU_TARGET \ - dnl Shell quoting is fun. - ${ICU_SRCDIR+"$ICU_SRCDIR"} \ - --enable-static --disable-shared \ - --enable-extras=no --enable-icuio=no --enable-layout=no \ - --enable-tests=no --enable-samples=no || exit 1 - ]) - ) || exit 1 - - # generate config/icucross.mk - $GMAKE -C $_objdir/intl/icu/host/ config/icucross.mk - - # --with-cross-build requires absolute path - ICU_HOST_PATH=`cd $_objdir/intl/icu/host && pwd` - ICU_CROSS_BUILD_OPT="--with-cross-build=$ICU_HOST_PATH" - fi + # Set OS dependent options for ICU + case "$OS_TARGET" in + Darwin) + ICU_TARGET=MacOSX + ;; + Linux) + ICU_TARGET=Linux + ;; + WINNT) + ICU_TARGET=MSYS/MSVC + ICU_SRCDIR="--srcdir=$(cd $srcdir/../../intl/icu/source; pwd -W)" + ;; + DragonFly|FreeBSD|NetBSD|OpenBSD) + ICU_TARGET=BSD + ;; + esac # To reduce library size, use static linking ICU_LINK_OPTS="--enable-static --disable-shared" # Force the ICU static libraries to be position independent code - ICU_CFLAGS="$DSO_PIC_CFLAGS $CFLAGS" - ICU_CXXFLAGS="$DSO_PIC_CFLAGS $CXXFLAGS" + ICU_CFLAGS="$DSO_PIC_CFLAGS" + ICU_CXXFLAGS="$DSO_PIC_CFLAGS" ICU_BUILD_OPTS="" - if test -n "$MOZ_DEBUG" -o "MOZ_DEBUG_SYMBOLS"; then - ICU_CFLAGS="$ICU_CFLAGS $MOZ_DEBUG_FLAGS" - ICU_CXXFLAGS="$ICU_CXXFLAGS $MOZ_DEBUG_FLAGS" - ICU_LDFLAGS="$MOZ_DEBUG_LDFLAGS" - if test -z "$MOZ_DEBUG"; then - # To generate debug symbols, it requires MOZ_DEBUG_FLAGS. - # But, not debug build. - ICU_CFLAGS="$ICU_CFLAGS -UDEBUG -DNDEBUG" - ICU_CXXFLAGS="$ICU_CXXFLAGS -UDEBUG -DNDEBUG" - else - ICU_BUILD_OPTS="$ICU_BUILD_OPTS --enable-debug" - fi + if test -n "$MOZ_DEBUG"; then + ICU_BUILD_OPTS="$ICU_BUILD_OPTS --enable-debug" fi if test -z "$MOZ_OPTIMIZE"; then ICU_BUILD_OPTS="$ICU_BUILD_OPTS --disable-release" @@ -4324,43 +4261,21 @@ if test -n "$ENABLE_INTL_API" -a -z "$MOZ_NATIVE_ICU"; then ICU_CXXFLAGS="$ICU_CXXFLAGS $MOZ_OPTIMIZE_FLAGS" fi - if test "$am_cv_langinfo_codeset" = "no"; then - # ex. Android - ICU_CPPFLAGS="$ICU_CPPFLAGS -DU_HAVE_NL_LANGINFO_CODESET=0" - fi - - # ICU requires RTTI - if test "$GNU_CC"; then - ICU_CXXFLAGS=`echo $ICU_CXXFLAGS | sed 's|-fno-rtti|-frtti|g'` - else - if test "$_MSC_VER"; then - ICU_CXXFLAGS=`echo $ICU_CXXFLAGS | sed 's|-GR-|-GR|g'` - fi - fi - - # We cannot use AC_OUTPUT_SUBDIRS since ICU tree is out of spidermonkey. - # When using AC_OUTPUT_SUBDIRS, objdir of ICU is out of objdir - # due to relative path. - # If building ICU moves into root of mozilla tree, we can use - # AC_OUTPUT_SUBDIR instead. - mkdir -p $_objdir/intl/icu/target - (cd $_objdir/intl/icu/target + abs_srcdir=`(cd $srcdir; pwd)` + mkdir -p $_objdir/intl/icu + (cd $_objdir/intl/icu MOZ_SUBCONFIGURE_WRAP([.],[ - AR="$AR" CC="$CC" CXX="$CXX" LD="$LD" \ - ARFLAGS="$ARFLAGS" \ - CPPFLAGS="$ICU_CPPFLAGS $CPPFLAGS" \ - CFLAGS="$ICU_CFLAGS" \ - CXXFLAGS="$ICU_CXXFLAGS" \ - LDFLAGS="$ICU_LDFLAGS $LDFLAGS" \ - $SHELL $_topsrcdir/../../intl/icu/source/configure \ - $ICU_BUILD_OPTS \ - $ICU_CROSS_BUILD_OPT \ - $ICU_LINK_OPTS \ - ${ICU_SRCDIR+"$ICU_SRCDIR"} \ - --build=$build --host=$target \ - --disable-extras --disable-icuio --disable-layout \ - --disable-tests --disable-samples || exit 1 - ]) + CC="$CC" CXX="$CXX" \ + CFLAGS="$ICU_CFLAGS" CPPFLAGS="$ICU_CPPFLAGS" CXXFLAGS="$ICU_CXXFLAGS" \ + $SHELL $abs_srcdir/../../intl/icu/source/runConfigureICU \ + $ICU_BUILD_OPTS \ + $ICU_TARGET \ + $ICU_LINK_OPTS \ +dnl Shell quoting is fun. + ${ICU_SRCDIR+"$ICU_SRCDIR"} \ + --enable-extras=no --enable-icuio=no --enable-layout=no \ + --enable-tests=no --enable-samples=no || exit 1 + ]) ) || exit 1 fi diff --git a/js/src/devtools/rootAnalysis/annotations.js b/js/src/devtools/rootAnalysis/annotations.js index 45a7e0473603..05de1e4f3239 100644 --- a/js/src/devtools/rootAnalysis/annotations.js +++ b/js/src/devtools/rootAnalysis/annotations.js @@ -160,6 +160,9 @@ var ignoreFunctions = { // FIXME! "NS_DebugBreak": true, + // Bug 940765 - fetching preferences should not GC + "PrefHashEntry* pref_HashTableLookup(void*)": true, + // These are a little overzealous -- these destructors *can* GC if they end // up wrapping a pending exception. See bug 898815 for the heavyweight fix. "void js::AutoCompartment::~AutoCompartment(int32)" : true, @@ -225,5 +228,6 @@ function isRootedPointerTypeName(name) function isSuppressConstructor(name) { return /::AutoSuppressGC/.test(name) - || /::AutoEnterAnalysis/.test(name); + || /::AutoEnterAnalysis/.test(name) + || /::AutoAssertNoGC/.test(name); } diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 7fa0093bea84..7744b61d53f5 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -281,7 +281,6 @@ static const PhaseInfo phases[] = { { PHASE_SWEEP, "Sweep", PHASE_NO_PARENT }, { PHASE_SWEEP_MARK, "Mark During Sweeping", PHASE_SWEEP }, { PHASE_SWEEP_MARK_TYPES, "Mark Types During Sweeping", PHASE_SWEEP_MARK }, - { PHASE_SWEEP_MARK_DELAYED, "Mark Delayed During Sweeping", PHASE_SWEEP_MARK }, { PHASE_SWEEP_MARK_INCOMING_BLACK, "Mark Incoming Black Pointers", PHASE_SWEEP_MARK }, { PHASE_SWEEP_MARK_WEAK, "Mark Weak", PHASE_SWEEP_MARK }, { PHASE_SWEEP_MARK_INCOMING_GRAY, "Mark Incoming Gray Pointers", PHASE_SWEEP_MARK }, diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 301292fcf836..3240fd238f53 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -33,7 +33,6 @@ enum Phase { PHASE_SWEEP, PHASE_SWEEP_MARK, PHASE_SWEEP_MARK_TYPES, - PHASE_SWEEP_MARK_DELAYED, PHASE_SWEEP_MARK_INCOMING_BLACK, PHASE_SWEEP_MARK_WEAK, PHASE_SWEEP_MARK_INCOMING_GRAY, @@ -207,6 +206,30 @@ struct AutoPhase MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; +struct MaybeAutoPhase +{ + MaybeAutoPhase(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) + : stats(nullptr) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } + void construct(Statistics &statsArg, Phase phaseArg) + { + JS_ASSERT(!stats); + stats = &statsArg; + phase = phaseArg; + stats->beginPhase(phase); + } + ~MaybeAutoPhase() { + if (stats) + stats->endPhase(phase); + } + + Statistics *stats; + Phase phase; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + struct AutoSCC { AutoSCC(Statistics &stats, unsigned scc diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 557ec9710768..e14eea162764 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -133,7 +133,7 @@ struct Zone : public JS::shadow::Zone, } public: - enum CompartmentGCState { + enum GCState { NoGC, Mark, MarkGray, @@ -143,7 +143,7 @@ struct Zone : public JS::shadow::Zone, private: bool gcScheduled; - CompartmentGCState gcState; + GCState gcState; bool gcPreserveCode; public: @@ -166,7 +166,7 @@ struct Zone : public JS::shadow::Zone, return runtimeFromMainThread()->isHeapMajorCollecting() && gcState != NoGC; } - void setGCState(CompartmentGCState state) { + void setGCState(GCState state) { JS_ASSERT(runtimeFromMainThread()->isHeapBusy()); JS_ASSERT_IF(state != NoGC, canCollect()); gcState = state; diff --git a/js/src/jit-test/tests/baseline/bug938130.js b/js/src/jit-test/tests/baseline/bug938130.js new file mode 100644 index 000000000000..5f0ecbe7e375 --- /dev/null +++ b/js/src/jit-test/tests/baseline/bug938130.js @@ -0,0 +1,7 @@ +// |jit-test| error: TypeError + +function f() { } +function g() { } +var x = [f,f,f,undefined,g]; +for (var i = 0; i < 5; ++i) + y = x[i]("x"); diff --git a/js/src/jit-test/tests/basic/bug908915.js b/js/src/jit-test/tests/basic/bug908915.js index dbdbff98e266..86d1a514aff5 100644 --- a/js/src/jit-test/tests/basic/bug908915.js +++ b/js/src/jit-test/tests/basic/bug908915.js @@ -1,7 +1,8 @@ // |jit-test| error: 42 function f(y) {} for each(let e in newGlobal()) { - if (e.name === "quit" || e.name == "readline" || e.name == "terminate") + if (e.name === "quit" || e.name == "readline" || e.name == "terminate" || + e.name == "nestedShell") continue; try { e(); @@ -13,7 +14,8 @@ for each(let e in newGlobal()) { y() } for each(b in []) { - if (b.name === "quit" || b.name == "readline" || b.name == "terminate") + if (b.name === "quit" || b.name == "readline" || b.name == "terminate" || + b.name == "nestedShell") continue; try { f(b) diff --git a/js/src/jit/AsmJS.cpp b/js/src/jit/AsmJS.cpp index de05a9c56020..a4a6e03ef048 100644 --- a/js/src/jit/AsmJS.cpp +++ b/js/src/jit/AsmJS.cpp @@ -4339,16 +4339,18 @@ CheckExpr(FunctionCompiler &f, ParseNode *expr, MDefinition **def, Type *type) case PNK_NAME: return CheckVarRef(f, expr, def, type); case PNK_ELEM: return CheckArrayLoad(f, expr, def, type); case PNK_ASSIGN: return CheckAssign(f, expr, def, type); - case PNK_CALL: return f.fail(expr, "non-expression-statement call must be coerced"); case PNK_POS: return CheckPos(f, expr, def, type); case PNK_NOT: return CheckNot(f, expr, def, type); case PNK_NEG: return CheckNeg(f, expr, def, type); case PNK_BITNOT: return CheckBitNot(f, expr, def, type); case PNK_COMMA: return CheckComma(f, expr, def, type); case PNK_CONDITIONAL: return CheckConditional(f, expr, def, type); - case PNK_STAR: return CheckMultiply(f, expr, def, type); + case PNK_CALL: return f.fail(expr, "all function calls must either be ignored (via " + "f(); or comma-expression), coerced to signed " + "(via f()|0) or coerced to double (via +f())"); + case PNK_ADD: case PNK_SUB: return CheckAddOrSub(f, expr, def, type); diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 21f49248e635..4324968887f4 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -251,6 +251,9 @@ BaselineCompiler::compile() bytecodeMap[script->nTypeSets] = 0; } + if (script->compartment()->debugMode()) + baselineScript->setDebugMode(); + return Method_Compiled; } diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 50223fd56fc3..fdde3ce01a02 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -2537,8 +2537,7 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac // Check to see if a new stub should be generated. if (stub->numOptimizedStubs() >= ICBinaryArith_Fallback::MAX_OPTIMIZED_STUBS) { - // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. - // But for now we just bail. + stub->noteUnoptimizableOperands(); return true; } @@ -2585,8 +2584,10 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac } // Handle only int32 or double. - if (!lhs.isNumber() || !rhs.isNumber()) + if (!lhs.isNumber() || !rhs.isNumber()) { + stub->noteUnoptimizableOperands(); return true; + } JS_ASSERT(ret.isNumber()); @@ -2653,6 +2654,7 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac } } + stub->noteUnoptimizableOperands(); return true; } #if defined(_MSC_VER) @@ -4449,17 +4451,20 @@ ICGetElem_Dense::Compiler::generateStubCode(MacroAssembler &masm) Label skipNoSuchMethod; regs = availableGeneralRegs(0); regs.takeUnchecked(obj); - regs.take(R1); + regs.takeUnchecked(key); + ValueOperand val = regs.takeValueOperand(); - masm.pushValue(R1); - masm.loadValue(element, R1); - masm.branchTestUndefined(Assembler::NotEqual, R1, &skipNoSuchMethod); + masm.loadValue(element, val); + masm.branchTestUndefined(Assembler::NotEqual, val, &skipNoSuchMethod); + regs.add(val); // Call __noSuchMethod__ checker. Object pointer is in objReg. - scratchReg = regs.takeAnyExcluding(BaselineTailCallReg); - enterStubFrame(masm, scratchReg); + enterStubFrame(masm, regs.getAnyExcluding(BaselineTailCallReg)); - // propName (R1) already pushed above. + regs.take(val); + + masm.tagValue(JSVAL_TYPE_INT32, key, val); + masm.pushValue(val); masm.push(obj); if (!callVM(LookupNoSuchMethodHandlerInfo, masm)) return false; @@ -4469,8 +4474,7 @@ ICGetElem_Dense::Compiler::generateStubCode(MacroAssembler &masm) masm.jump(&afterNoSuchMethod); masm.bind(&skipNoSuchMethod); - masm.moveValue(R1, R0); - masm.addPtr(Imm32(sizeof(Value)), BaselineStackReg); // pop previously pushed propName (R1) + masm.moveValue(val, R0); masm.bind(&afterNoSuchMethod); } else { masm.loadValue(element, R0); diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index 1e96b8178365..665ce2576f87 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -2419,6 +2419,9 @@ class ICBinaryArith_Fallback : public ICFallbackStub extra_ = 0; } + static const uint16_t SAW_DOUBLE_RESULT_BIT = 0x1; + static const uint16_t UNOPTIMIZABLE_OPERANDS_BIT = 0x2; + public: static const uint32_t MAX_OPTIMIZED_STUBS = 8; @@ -2428,11 +2431,17 @@ class ICBinaryArith_Fallback : public ICFallbackStub return space->allocate(code); } - bool sawDoubleResult() { - return extra_; + bool sawDoubleResult() const { + return extra_ & SAW_DOUBLE_RESULT_BIT; } void setSawDoubleResult() { - extra_ = 1; + extra_ |= SAW_DOUBLE_RESULT_BIT; + } + bool hadUnoptimizableOperands() const { + return extra_ & UNOPTIMIZABLE_OPERANDS_BIT; + } + void noteUnoptimizableOperands() { + extra_ |= UNOPTIMIZABLE_OPERANDS_BIT; } // Compiler for this stub kind. diff --git a/js/src/jit/BaselineInspector.cpp b/js/src/jit/BaselineInspector.cpp index ea996156d33d..4768ced51887 100644 --- a/js/src/jit/BaselineInspector.cpp +++ b/js/src/jit/BaselineInspector.cpp @@ -295,6 +295,14 @@ BaselineInspector::expectedBinaryArithSpecialization(jsbytecode *pc) MIRType result; ICStub *stubs[2]; + const ICEntry &entry = icEntryFromPC(pc); + ICStub *stub = entry.fallbackStub(); + if (stub->isBinaryArith_Fallback() && + stub->toBinaryArith_Fallback()->hadUnoptimizableOperands()) + { + return MIRType_None; + } + stubs[0] = monomorphicStub(pc); if (stubs[0]) { if (TryToSpecializeBinaryArithOp(stubs, 1, &result)) diff --git a/js/src/jit/BaselineJIT.cpp b/js/src/jit/BaselineJIT.cpp index 2a1b6956924a..6ea55d9010f6 100644 --- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -746,6 +746,10 @@ BaselineScript::toggleDebugTraps(JSScript *script, jsbytecode *pc) { JS_ASSERT(script->baselineScript() == this); + // Only scripts compiled for debug mode have toggled calls. + if (!debugMode()) + return; + SrcNoteLineScanner scanner(script->notes(), script->lineno); JSRuntime *rt = script->runtimeFromMainThread(); diff --git a/js/src/jit/BaselineJIT.h b/js/src/jit/BaselineJIT.h index d9c8960a4fce..473edf187112 100644 --- a/js/src/jit/BaselineJIT.h +++ b/js/src/jit/BaselineJIT.h @@ -132,7 +132,11 @@ struct BaselineScript // Flag set when the script contains any writes to its on-stack // (rather than call object stored) arguments. - MODIFIES_ARGUMENTS = 1 << 2 + MODIFIES_ARGUMENTS = 1 << 2, + + // Flag set when compiled for use for debug mode. Handles various + // Debugger hooks and compiles toggled calls for traps. + DEBUG_MODE = 1 << 3 }; private: @@ -201,6 +205,13 @@ struct BaselineScript return flags_ & MODIFIES_ARGUMENTS; } + void setDebugMode() { + flags_ |= DEBUG_MODE; + } + bool debugMode() const { + return flags_ & DEBUG_MODE; + } + uint32_t prologueOffset() const { return prologueOffset_; } diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 42c1e5a3f917..999c38c2eae9 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -1576,11 +1576,16 @@ TryEliminateTypeBarrierFromTest(MTypeBarrier *barrier, bool filtersNull, bool fi // Disregard the possible unbox added before the Typebarrier for checking. MDefinition *input = barrier->input(); - if (input->isUnbox() && input->toUnbox()->mode() == MUnbox::TypeBarrier) - input = input->toUnbox()->input(); + MUnbox *inputUnbox = nullptr; + if (input->isUnbox() && input->toUnbox()->mode() != MUnbox::Fallible) { + inputUnbox = input->toUnbox(); + input = inputUnbox->input(); + } if (test->getOperand(0) == input && direction == TRUE_BRANCH) { *eliminated = true; + if (inputUnbox) + inputUnbox->makeInfallible(); barrier->replaceAllUsesWith(barrier->input()); return; } @@ -1614,6 +1619,8 @@ TryEliminateTypeBarrierFromTest(MTypeBarrier *barrier, bool filtersNull, bool fi } *eliminated = true; + if (inputUnbox) + inputUnbox->makeInfallible(); barrier->replaceAllUsesWith(barrier->input()); } @@ -1626,11 +1633,8 @@ TryEliminateTypeBarrier(MTypeBarrier *barrier, bool *eliminated) const types::TemporaryTypeSet *inputTypes = barrier->input()->resultTypeSet(); // Disregard the possible unbox added before the Typebarrier. - if (barrier->input()->isUnbox() && - barrier->input()->toUnbox()->mode() == MUnbox::TypeBarrier) - { + if (barrier->input()->isUnbox() && barrier->input()->toUnbox()->mode() != MUnbox::Fallible) inputTypes = barrier->input()->toUnbox()->input()->resultTypeSet(); - } if (!barrierTypes || !inputTypes) return true; diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 3f59b68a839a..aa69f2dba4ec 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -5251,21 +5251,10 @@ IonBuilder::makeCall(JSFunction *target, CallInfo &callInfo, bool cloneAtCallsit types::TemporaryTypeSet *types = bytecodeTypes(pc); - bool barrier = true; - MDefinition *replace = call; - if (call->isDOMFunction()) { - JSFunction* target = call->getSingleTarget(); - JS_ASSERT(target && target->isNative() && target->jitInfo()); - const JSJitInfo *jitinfo = target->jitInfo(); - barrier = DOMCallNeedsBarrier(jitinfo, types); - replace = ensureDefiniteType(call, jitinfo->returnType); - if (replace != call) { - current->pop(); - current->push(replace); - } - } + if (call->isDOMFunction()) + return pushDOMTypeBarrier(call, types, call->getSingleTarget()); - return pushTypeBarrier(replace, types, barrier); + return pushTypeBarrier(call, types, true); } bool @@ -6147,6 +6136,38 @@ IonBuilder::pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, return true; } +bool +IonBuilder::pushDOMTypeBarrier(MInstruction *ins, types::TemporaryTypeSet *observed, JSFunction* func) +{ + JS_ASSERT(func && func->isNative() && func->jitInfo()); + + const JSJitInfo *jitinfo = func->jitInfo(); + bool barrier = DOMCallNeedsBarrier(jitinfo, observed); + // Need to be a bit careful: if jitinfo->returnType is JSVAL_TYPE_DOUBLE but + // types->getKnownTypeTag() is JSVAL_TYPE_INT32, then don't unconditionally + // unbox as a double. Instead, go ahead and barrier on having an int type, + // since we know we need a barrier anyway due to the type mismatch. This is + // the only situation in which TI actually has more information about the + // JSValueType than codegen can, short of jitinfo->returnType just being + // JSVAL_TYPE_UNKNOWN. + MDefinition* replace = ins; + if (jitinfo->returnType != JSVAL_TYPE_DOUBLE || + observed->getKnownTypeTag() != JSVAL_TYPE_INT32) { + JS_ASSERT(jitinfo->returnType == JSVAL_TYPE_UNKNOWN || + observed->getKnownTypeTag() == JSVAL_TYPE_UNKNOWN || + jitinfo->returnType == observed->getKnownTypeTag()); + replace = ensureDefiniteType(ins, jitinfo->returnType); + if (replace != ins) { + current->pop(); + current->push(replace); + } + } else { + JS_ASSERT(barrier); + } + + return pushTypeBarrier(replace, observed, barrier); +} + MDefinition * IonBuilder::ensureDefiniteType(MDefinition *def, JSValueType definiteType) { @@ -6512,15 +6533,19 @@ IonBuilder::getElemTryTypedObject(bool *emitted, MDefinition *obj, MDefinition * return true; switch (elemTypeReprs.kind()) { - case TypeRepresentation::Struct: - case TypeRepresentation::Array: + case TypeRepresentation::X4: + // FIXME (bug 894104): load into a MIRType_float32x4 etc + return true; + + case TypeRepresentation::Struct: + case TypeRepresentation::Array: return getElemTryComplexElemOfTypedObject(emitted, obj, index, objTypeReprs, elemTypeReprs, elemSize); - case TypeRepresentation::Scalar: + case TypeRepresentation::Scalar: return getElemTryScalarElemOfTypedObject(emitted, obj, index, @@ -8187,6 +8212,10 @@ IonBuilder::getPropTryTypedObject(bool *emitted, PropertyName *name, case TypeRepresentation::Reference: return true; + case TypeRepresentation::X4: + // FIXME (bug 894104): load into a MIRType_float32x4 etc + return true; + case TypeRepresentation::Struct: case TypeRepresentation::Array: return getPropTryComplexPropOfTypedObject(emitted, @@ -8341,13 +8370,8 @@ IonBuilder::getPropTryCommonGetter(bool *emitted, PropertyName *name, if (get->isEffectful() && !resumeAfter(get)) return false; - bool barrier = DOMCallNeedsBarrier(jitinfo, types); - MDefinition *replace = ensureDefiniteType(get, jitinfo->returnType); - if (replace != get) { - current->pop(); - current->push(replace); - } - if (!pushTypeBarrier(replace, types, barrier)) + + if (!pushDOMTypeBarrier(get, types, commonGetter)) return false; *emitted = true; @@ -8725,6 +8749,10 @@ IonBuilder::setPropTryTypedObject(bool *emitted, MDefinition *obj, return true; switch (fieldTypeReprs.kind()) { + case TypeRepresentation::X4: + // FIXME (bug 894104): store into a MIRType_float32x4 etc + return true; + case TypeRepresentation::Reference: case TypeRepresentation::Struct: case TypeRepresentation::Array: diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index df6a2c1e50fb..7c44639f0504 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -334,6 +334,11 @@ class IonBuilder : public MIRGenerator // generated code correspond to the observed types for the bytecode. bool pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, bool needBarrier); + // As pushTypeBarrier, but will compute the needBarrier boolean itself based + // on observed and the JSFunction that we're planning to call. The + // JSFunction must be a DOM method or getter. + bool pushDOMTypeBarrier(MInstruction *ins, types::TemporaryTypeSet *observed, JSFunction* func); + // If definiteType is not known or def already has the right type, just // returns def. Otherwise, returns an MInstruction that has that definite // type, infallibly unboxing ins as needed. The new instruction will be diff --git a/js/src/jit/IonMacroAssembler.cpp b/js/src/jit/IonMacroAssembler.cpp index 8f2985905891..ef0eb8ae9673 100644 --- a/js/src/jit/IonMacroAssembler.cpp +++ b/js/src/jit/IonMacroAssembler.cpp @@ -1627,7 +1627,7 @@ MacroAssembler::convertValueToInt(JSContext *cx, const Value &v, Register output break; } case IntConversion_Truncate: - move32(Imm32(ToInt32(d)), output); + move32(Imm32(js::ToInt32(d)), output); break; case IntConversion_ClampToUint8: move32(Imm32(ClampDoubleToUint8(d)), output); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 849a65716207..25b8ca986bbc 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -2373,6 +2373,11 @@ class MUnbox : public MUnaryInstruction, public BoxInputsPolicy return AliasSet::None(); } void printOpcode(FILE *fp) const; + void makeInfallible() { + // Should only be called if we're already Infallible or TypeBarrier + JS_ASSERT(mode() != Fallible); + mode_ = Infallible; + } }; class MGuardObject : public MUnaryInstruction, public SingleObjectPolicy diff --git a/js/src/jit/RegisterSets.h b/js/src/jit/RegisterSets.h index 12078fc427b7..1cc5c064e58f 100644 --- a/js/src/jit/RegisterSets.h +++ b/js/src/jit/RegisterSets.h @@ -397,6 +397,15 @@ class TypedRegisterSet takeUnchecked(value.valueReg()); #else #error "Bad architecture" +#endif + } + ValueOperand takeValueOperand() { +#if defined(JS_NUNBOX32) + return ValueOperand(takeAny(), takeAny()); +#elif defined(JS_PUNBOX64) + return ValueOperand(takeAny()); +#else +#error "Bad architecture" #endif } T getAny() const { @@ -407,6 +416,17 @@ class TypedRegisterSet // EAX on x86). return getFirst(); } + T getAnyExcluding(T preclude) { + JS_ASSERT(!empty()); + if (!has(preclude)) + return getAny(); + + take(preclude); + JS_ASSERT(!empty()); + T result = getAny(); + add(preclude); + return result; + } T getFirst() const { JS_ASSERT(!empty()); return T::FromCode(mozilla::CountTrailingZeroes32(bits_)); @@ -423,13 +443,9 @@ class TypedRegisterSet return reg; } T takeAnyExcluding(T preclude) { - if (!has(preclude)) - return takeAny(); - - take(preclude); - T result = takeAny(); - add(preclude); - return result; + T reg = getAnyExcluding(preclude); + take(reg); + return reg; } ValueOperand takeAnyValue() { #if defined(JS_NUNBOX32) diff --git a/js/src/jit/arm/CodeGenerator-arm.cpp b/js/src/jit/arm/CodeGenerator-arm.cpp index 5543a94d2ac3..7a9f64cda1fa 100644 --- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -722,7 +722,7 @@ CodeGeneratorARM::visitModI(LModI *ins) Register lhs = ToRegister(ins->lhs()); Register rhs = ToRegister(ins->rhs()); Register output = ToRegister(ins->output()); - Register callTemp = ToRegister(ins->getTemp(0)); + Register callTemp = ToRegister(ins->callTemp()); MMod *mir = ins->mir(); // save the lhs in case we end up with a 0 that should be a -0.0 because lhs < 0. @@ -758,9 +758,10 @@ CodeGeneratorARM::visitSoftModI(LSoftModI *ins) Register lhs = ToRegister(ins->lhs()); Register rhs = ToRegister(ins->rhs()); Register output = ToRegister(ins->output()); - Register callTemp = ToRegister(ins->getTemp(2)); + Register callTemp = ToRegister(ins->callTemp()); MMod *mir = ins->mir(); Label done; + // save the lhs in case we end up with a 0 that should be a -0.0 because lhs < 0. JS_ASSERT(callTemp.code() > r3.code() && callTemp.code() < r12.code()); masm.ma_mov(lhs, callTemp); diff --git a/js/src/jit/arm/LIR-arm.h b/js/src/jit/arm/LIR-arm.h index faa1385ef3be..5bf6b90dae4a 100644 --- a/js/src/jit/arm/LIR-arm.h +++ b/js/src/jit/arm/LIR-arm.h @@ -139,25 +139,27 @@ class LDivI : public LBinaryMath<1> // LSoftDivI is a software divide for ARM cores that don't support a hardware // divide instruction. // -// It is implemented as a proper C function so it trashes r0, r1, r2 and r3. The -// call also trashes lr, and has the ability to trash ip. The function also +// It is implemented as a proper C function so it trashes r0, r1, r2 and r3. +// The call also trashes lr, and has the ability to trash ip. The function also // takes two arguments (dividend in r0, divisor in r1). The LInstruction gets // encoded such that the divisor and dividend are passed in their apropriate -// registers, and are marked as copy so we can modify them (and the function -// will). The other thre registers that can be trashed are marked as such. For -// the time being, the link register is not marked as trashed because we never -// allocate to the link register. -class LSoftDivI : public LBinaryMath<2> +// registers and end their life at the start of the instruction by the use of +// useFixedAtStart. The result is returned in r0 and the other three registers +// that can be trashed are marked as temps. For the time being, the link +// register is not marked as trashed because we never allocate to the link +// register. The FP registers are not trashed. +class LSoftDivI : public LBinaryMath<3> { public: LIR_HEADER(SoftDivI); LSoftDivI(const LAllocation &lhs, const LAllocation &rhs, - const LDefinition &temp1, const LDefinition &temp2) { + const LDefinition &temp1, const LDefinition &temp2, const LDefinition &temp3) { setOperand(0, lhs); setOperand(1, rhs); setTemp(0, temp1); setTemp(1, temp2); + setTemp(2, temp3); } MDiv *mir() const { @@ -204,25 +206,34 @@ class LModI : public LBinaryMath<1> setTemp(0, callTemp); } + const LDefinition *callTemp() { + return getTemp(0); + } + MMod *mir() const { return mir_->toMod(); } }; -class LSoftModI : public LBinaryMath<3> +class LSoftModI : public LBinaryMath<4> { public: LIR_HEADER(SoftModI); LSoftModI(const LAllocation &lhs, const LAllocation &rhs, - const LDefinition &temp1, const LDefinition &temp2, + const LDefinition &temp1, const LDefinition &temp2, const LDefinition &temp3, const LDefinition &callTemp) { setOperand(0, lhs); setOperand(1, rhs); setTemp(0, temp1); setTemp(1, temp2); - setTemp(2, callTemp); + setTemp(2, temp3); + setTemp(3, callTemp); + } + + const LDefinition *callTemp() { + return getTemp(3); } MMod *mir() const { @@ -414,20 +425,18 @@ class LUMod : public LBinaryMath<0> // This class performs a simple x86 'div', yielding either a quotient or remainder depending on // whether this instruction is defined to output eax (quotient) or edx (remainder). -class LSoftUDivOrMod : public LBinaryMath<2> +class LSoftUDivOrMod : public LBinaryMath<3> { public: LIR_HEADER(SoftUDivOrMod); - LSoftUDivOrMod(const LAllocation &lhs, const LAllocation &rhs, const LDefinition &temp1, const LDefinition &temp2) { + LSoftUDivOrMod(const LAllocation &lhs, const LAllocation &rhs, const LDefinition &temp1, + const LDefinition &temp2, const LDefinition &temp3) { setOperand(0, lhs); setOperand(1, rhs); setTemp(0, temp1); setTemp(1, temp2); - } - // this is incorrect, it is returned in r1, getTemp(0) is r2. - const LDefinition *remainder() { - return getTemp(0); + setTemp(2, temp3); } }; diff --git a/js/src/jit/arm/Lowering-arm.cpp b/js/src/jit/arm/Lowering-arm.cpp index 9bf9092a0f3f..e236138be5bd 100644 --- a/js/src/jit/arm/Lowering-arm.cpp +++ b/js/src/jit/arm/Lowering-arm.cpp @@ -290,8 +290,8 @@ LIRGeneratorARM::lowerDivI(MDiv *div) return define(lir, div); } - LSoftDivI *lir = new LSoftDivI(useFixed(div->lhs(), r0), use(div->rhs(), r1), - tempFixed(r2), tempFixed(r3)); + LSoftDivI *lir = new LSoftDivI(useFixedAtStart(div->lhs(), r0), useFixedAtStart(div->rhs(), r1), + tempFixed(r1), tempFixed(r2), tempFixed(r3)); if (div->fallible() && !assignSnapshot(lir, Bailout_BaselineInfo)) return false; return defineFixed(lir, div, LAllocation(AnyRegister(r0))); @@ -335,8 +335,8 @@ LIRGeneratorARM::lowerModI(MMod *mod) return define(lir, mod); } - LSoftModI *lir = new LSoftModI(useFixed(mod->lhs(), r0), use(mod->rhs(), r1), - tempFixed(r2), tempFixed(r3), temp(LDefinition::GENERAL)); + LSoftModI *lir = new LSoftModI(useFixedAtStart(mod->lhs(), r0), useFixedAtStart(mod->rhs(), r1), + tempFixed(r0), tempFixed(r2), tempFixed(r3), temp(LDefinition::GENERAL)); if (mod->fallible() && !assignSnapshot(lir, Bailout_BaselineInfo)) return false; return defineFixed(lir, mod, LAllocation(AnyRegister(r1))); @@ -427,8 +427,8 @@ LIRGeneratorARM::lowerUDiv(MInstruction *div) lir->setOperand(1, useRegister(rhs)); return define(lir, div); } else { - LSoftUDivOrMod *lir = new LSoftUDivOrMod(useFixed(lhs, r0), useFixed(rhs, r1), - tempFixed(r2), tempFixed(r3)); + LSoftUDivOrMod *lir = new LSoftUDivOrMod(useFixedAtStart(lhs, r0), useFixedAtStart(rhs, r1), + tempFixed(r1), tempFixed(r2), tempFixed(r3)); return defineFixed(lir, div, LAllocation(AnyRegister(r0))); } } @@ -451,8 +451,8 @@ LIRGeneratorARM::lowerUMod(MInstruction *mod) lir->setOperand(1, useRegister(rhs)); return define(lir, mod); } else { - LSoftUDivOrMod *lir = new LSoftUDivOrMod(useFixed(lhs, r0), useFixed(rhs, r1), - tempFixed(r2), tempFixed(r3)); + LSoftUDivOrMod *lir = new LSoftUDivOrMod(useFixedAtStart(lhs, r0), useFixedAtStart(rhs, r1), + tempFixed(r0), tempFixed(r2), tempFixed(r3)); return defineFixed(lir, mod, LAllocation(AnyRegister(r1))); } } diff --git a/js/src/jit/shared/Lowering-x86-shared.cpp b/js/src/jit/shared/Lowering-x86-shared.cpp index 4f081e647f0c..2e8aca0e2234 100644 --- a/js/src/jit/shared/Lowering-x86-shared.cpp +++ b/js/src/jit/shared/Lowering-x86-shared.cpp @@ -209,7 +209,13 @@ LIRGeneratorX86Shared::visitAsmJSNeg(MAsmJSNeg *ins) bool LIRGeneratorX86Shared::lowerUDiv(MInstruction *div) { - LUDivOrMod *lir = new LUDivOrMod(useFixed(div->getOperand(0), eax), + // Optimize x/x. The comments in lowerDivI apply here as well. + if (div->getOperand(0) == div->getOperand(1)) { + LDivSelfI *lir = new LDivSelfI(useRegisterAtStart(div->getOperand(0))); + return define(lir, div); + } + + LUDivOrMod *lir = new LUDivOrMod(useFixedAtStart(div->getOperand(0), eax), useRegister(div->getOperand(1)), tempFixed(edx)); return defineFixed(lir, div, LAllocation(AnyRegister(eax))); diff --git a/js/src/jit/x64/Assembler-x64.cpp b/js/src/jit/x64/Assembler-x64.cpp index a016d35d7b23..eb80b1cd0fd2 100644 --- a/js/src/jit/x64/Assembler-x64.cpp +++ b/js/src/jit/x64/Assembler-x64.cpp @@ -160,9 +160,14 @@ Assembler::finish() #ifdef DEBUG size_t oldSize = masm.size(); #endif - masm.jmp_rip(0); + masm.jmp_rip(2); + JS_ASSERT(masm.size() - oldSize == 6); + // Following an indirect branch with ud2 hints to the hardware that + // there's no fall-through. This also aligns the 64-bit immediate. + masm.ud2(); + JS_ASSERT(masm.size() - oldSize == 8); masm.immediate64(0); - masm.align(SizeOfJumpTableEntry); + JS_ASSERT(masm.size() - oldSize == SizeOfExtendedJump); JS_ASSERT(masm.size() - oldSize == SizeOfJumpTableEntry); } } diff --git a/js/src/jit/x64/Assembler-x64.h b/js/src/jit/x64/Assembler-x64.h index abd8694c5c50..aadcc2f7efca 100644 --- a/js/src/jit/x64/Assembler-x64.h +++ b/js/src/jit/x64/Assembler-x64.h @@ -195,15 +195,16 @@ class Assembler : public AssemblerX86Shared // The start of the relocation table contains the offset from the code // buffer to the start of the extended jump table. // - // Each entry in this table is a jmp [rip], where the next eight bytes - // contain an immediate address. This comes out to 14 bytes, which we pad - // to 16. + // Each entry in this table is a jmp [rip], followed by a ud2 to hint to the + // hardware branch predictor that there is no fallthrough, followed by the + // eight bytes containing an immediate address. This comes out to 16 bytes. // +1 byte for opcode // +1 byte for mod r/m - // +4 bytes for rip-relative offset (0) + // +4 bytes for rip-relative offset (2) + // +2 bytes for ud2 instruction // +8 bytes for 64-bit address // - static const uint32_t SizeOfExtendedJump = 1 + 1 + 4 + 8; + static const uint32_t SizeOfExtendedJump = 1 + 1 + 4 + 2 + 8; static const uint32_t SizeOfJumpTableEntry = 16; uint32_t extendedJumpTable_; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index ae3364209d99..c6f99bd722a8 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -3085,10 +3085,10 @@ DefineProperty(JSContext *cx, HandleObject obj, const char *name, const Value &v RootedValue value(cx, value_); AutoRooterGetterSetter gsRoot(cx, attrs, const_cast(&getter.op), const_cast(&setter.op)); - RootedId id(cx); + RootedId id(cx); if (attrs & JSPROP_INDEX) { - id = INT_TO_JSID(intptr_t(name)); + id.set(INT_TO_JSID(intptr_t(name))); attrs &= ~JSPROP_INDEX; } else { JSAtom *atom = Atomize(cx, name, strlen(name)); @@ -3100,6 +3100,57 @@ DefineProperty(JSContext *cx, HandleObject obj, const char *name, const Value &v return DefinePropertyById(cx, obj, id, value, getter, setter, attrs, flags, tinyid); } + +static bool +DefineSelfHostedProperty(JSContext *cx, + HandleObject obj, + const char *name, + const char *getterName, + const char *setterName, + unsigned attrs, + unsigned flags, + int tinyid) +{ + RootedAtom nameAtom(cx, Atomize(cx, name, strlen(name))); + if (!nameAtom) + return false; + + RootedAtom getterNameAtom(cx, Atomize(cx, getterName, strlen(getterName))); + if (!getterNameAtom) + return false; + + RootedValue getterValue(cx); + if (!cx->global()->getSelfHostedFunction(cx, getterNameAtom, nameAtom, + 0, &getterValue)) + { + return false; + } + JS_ASSERT(getterValue.isObject() && getterValue.toObject().is()); + RootedFunction getterFunc(cx, &getterValue.toObject().as()); + JSPropertyOp getterOp = JS_DATA_TO_FUNC_PTR(PropertyOp, getterFunc.get()); + + RootedFunction setterFunc(cx); + if (setterName) { + RootedAtom setterNameAtom(cx, Atomize(cx, setterName, strlen(setterName))); + if (!setterNameAtom) + return false; + + RootedValue setterValue(cx); + if (!cx->global()->getSelfHostedFunction(cx, setterNameAtom, nameAtom, + 0, &setterValue)) + { + return false; + } + JS_ASSERT(setterValue.isObject() && setterValue.toObject().is()); + setterFunc = &getterValue.toObject().as(); + } + JSStrictPropertyOp setterOp = JS_DATA_TO_FUNC_PTR(StrictPropertyOp, setterFunc.get()); + + return DefineProperty(cx, obj, name, UndefinedValue(), + GetterWrapper(getterOp), SetterWrapper(setterOp), + attrs, flags, tinyid); +} + JS_PUBLIC_API(bool) JS_DefineProperty(JSContext *cx, JSObject *objArg, const char *name, jsval valueArg, PropertyOp getter, JSStrictPropertyOp setter, unsigned attrs) @@ -3224,8 +3275,25 @@ JS_DefineProperties(JSContext *cx, JSObject *objArg, const JSPropertySpec *ps) RootedObject obj(cx, objArg); bool ok; for (ok = true; ps->name; ps++) { - ok = DefineProperty(cx, obj, ps->name, UndefinedValue(), ps->getter, ps->setter, - ps->flags, Shape::HAS_SHORTID, ps->tinyid); + if (ps->selfHostedGetter) { + // If you have self-hosted getter/setter, you can't have a + // native one. + JS_ASSERT(!ps->getter.op && !ps->setter.op); + + ok = DefineSelfHostedProperty(cx, obj, ps->name, + ps->selfHostedGetter, + ps->selfHostedSetter, + ps->flags, Shape::HAS_SHORTID, + ps->tinyid); + } else { + // If you do not have a self-hosted getter, you should + // have a native getter; and you should not have a + // self-hosted setter. + JS_ASSERT(ps->getter.op && !ps->selfHostedSetter); + + ok = DefineProperty(cx, obj, ps->name, UndefinedValue(), ps->getter, ps->setter, + ps->flags, Shape::HAS_SHORTID, ps->tinyid); + } if (!ok) break; } diff --git a/js/src/jsapi.h b/js/src/jsapi.h index bee0b4f9abc3..0802ac9b562e 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2406,6 +2406,8 @@ struct JSPropertySpec { uint8_t flags; JSPropertyOpWrapper getter; JSStrictPropertyOpWrapper setter; + const char *selfHostedGetter; + const char *selfHostedSetter; }; namespace JS { @@ -2436,13 +2438,23 @@ inline int CheckIsNative(JSNative native); {name, 0, \ uint8_t(JS_CHECK_ACCESSOR_FLAGS(flags) | JSPROP_SHARED | JSPROP_NATIVE_ACCESSORS), \ JSOP_WRAPPER(JS_CAST_NATIVE_TO(getter, JSPropertyOp)), \ - JSOP_NULLWRAPPER} + JSOP_NULLWRAPPER, nullptr, nullptr} #define JS_PSGS(name, getter, setter, flags) \ {name, 0, \ uint8_t(JS_CHECK_ACCESSOR_FLAGS(flags) | JSPROP_SHARED | JSPROP_NATIVE_ACCESSORS), \ JSOP_WRAPPER(JS_CAST_NATIVE_TO(getter, JSPropertyOp)), \ - JSOP_WRAPPER(JS_CAST_NATIVE_TO(setter, JSStrictPropertyOp))} -#define JS_PS_END {0, 0, 0, JSOP_NULLWRAPPER, JSOP_NULLWRAPPER} + JSOP_WRAPPER(JS_CAST_NATIVE_TO(setter, JSStrictPropertyOp)), \ + nullptr, nullptr} +#define JS_SELF_HOSTED_GET(name, getterName, flags) \ + {name, 0, \ + uint8_t(JS_CHECK_ACCESSOR_FLAGS(flags) | JSPROP_SHARED | JSPROP_GETTER), \ + JSOP_NULLWRAPPER, JSOP_NULLWRAPPER, getterName, nullptr} +#define JS_SELF_HOSTED_GETSET(name, getterName, setterName, flags) \ + {name, 0, \ + uint8_t(JS_CHECK_ACCESSOR_FLAGS(flags) | JSPROP_SHARED | JSPROP_GETTER | JSPROP_SETTER), \ + JSOP_NULLWRAPPER, JSOP_NULLWRAPPER, getterName, setterName} +#define JS_PS_END {0, 0, 0, JSOP_NULLWRAPPER, JSOP_NULLWRAPPER, \ + nullptr, nullptr} /* * To define a native function, set call to a JSNativeWrapper. To define a diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 65eeb42153e4..f29a1a632172 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -1119,14 +1119,15 @@ JSFunction::createScriptForLazilyInterpretedFunction(JSContext *cx, HandleFuncti if (cx->zone()->needsBarrier()) LazyScript::writeBarrierPre(lazy); - // Suppress GC when lazily compiling functions, to preserve source - // character buffers. + // Suppress GC for now although we should be able to remove this by + // making 'lazy' a Rooted (which requires adding a + // THING_ROOT_LAZY_SCRIPT). AutoSuppressGC suppressGC(cx); fun->flags &= ~INTERPRETED_LAZY; fun->flags |= INTERPRETED; - JSScript *script = lazy->maybeScript(); + RootedScript script(cx, lazy->maybeScript()); if (script) { fun->initScript(script); @@ -1155,13 +1156,12 @@ JSFunction::createScriptForLazilyInterpretedFunction(JSContext *cx, HandleFuncti // has started. if (!lazy->numInnerFunctions() && !JS::IsIncrementalGCInProgress(cx->runtime())) { LazyScriptCache::Lookup lookup(cx, lazy); - cx->runtime()->lazyScriptCache.lookup(lookup, &script); + cx->runtime()->lazyScriptCache.lookup(lookup, script.address()); } if (script) { RootedObject enclosingScope(cx, lazy->enclosingScope()); - RootedScript scriptRoot(cx, script); - RootedScript clonedScript(cx, CloneScript(cx, enclosingScope, fun, scriptRoot)); + RootedScript clonedScript(cx, CloneScript(cx, enclosingScope, fun, script)); if (!clonedScript) { fun->initLazyScript(lazy); return false; @@ -1182,7 +1182,8 @@ JSFunction::createScriptForLazilyInterpretedFunction(JSContext *cx, HandleFuncti JS_ASSERT(lazy->source()->hasSourceData()); // Parse and compile the script from source. - const jschar *chars = lazy->source()->chars(cx); + SourceDataCache::AutoSuppressPurge asp(cx); + const jschar *chars = lazy->source()->chars(cx, asp); if (!chars) { fun->initLazyScript(lazy); return false; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index fffdd8040f7c..d7b80e5730cc 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1560,7 +1560,7 @@ ArenaLists::refillFreeList(ThreadSafeContext *cx, AllocKind thingKind) cx->asJSContext()->runtime()->gcHelperThread.waitBackgroundSweepEnd(); } } else { -#ifdef JS_THREADSAFE +#ifdef JS_WORKER_THREADS /* * If we're off the main thread, we try to allocate once and return * whatever value we get. First, though, we need to ensure the main @@ -1800,10 +1800,9 @@ GCMarker::markDelayedChildren(ArenaHeader *aheader) bool GCMarker::markDelayedChildren(SliceBudget &budget) { - gcstats::Phase phase = runtime->gcIncrementalState == MARK - ? gcstats::PHASE_MARK_DELAYED - : gcstats::PHASE_SWEEP_MARK_DELAYED; - gcstats::AutoPhase ap(runtime->gcStats, phase); + gcstats::MaybeAutoPhase ap; + if (runtime->gcIncrementalState == MARK) + ap.construct(runtime->gcStats, gcstats::PHASE_MARK_DELAYED); JS_ASSERT(unmarkedArenaStackTop); do { @@ -3177,10 +3176,14 @@ js::gc::MarkingValidator::nonIncrementalMark() MarkRuntime(gcmarker, true); } - SliceBudget budget; - runtime->gcIncrementalState = MARK; - runtime->gcMarker.drainMarkStack(budget); + { + gcstats::AutoPhase ap1(runtime->gcStats, gcstats::PHASE_MARK); + SliceBudget budget; + runtime->gcIncrementalState = MARK; + runtime->gcMarker.drainMarkStack(budget); + } + runtime->gcIncrementalState = SWEEP; { gcstats::AutoPhase ap(runtime->gcStats, gcstats::PHASE_SWEEP); MarkAllWeakReferences(runtime, gcstats::PHASE_SWEEP_MARK_WEAK); @@ -4149,7 +4152,7 @@ AutoTraceSession::AutoTraceSession(JSRuntime *rt, js::HeapState heapState) if (rt->exclusiveThreadsPresent()) { // Lock the worker thread state when changing the heap state in the // presence of exclusive threads, to avoid racing with refillFreeList. -#ifdef JS_THREADSAFE +#ifdef JS_WORKER_THREADS AutoLockWorkerThreadState lock(*rt->workerThreadState); rt->heapState = heapState; #else @@ -4165,7 +4168,7 @@ AutoTraceSession::~AutoTraceSession() JS_ASSERT(runtime->isHeapBusy()); if (runtime->exclusiveThreadsPresent()) { -#ifdef JS_THREADSAFE +#ifdef JS_WORKER_THREADS AutoLockWorkerThreadState lock(*runtime->workerThreadState); runtime->heapState = prevState; diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index de5874f003ad..5c7f9e34d611 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -603,9 +603,11 @@ class types::CompilerConstraintList public: CompilerConstraintList(jit::TempAllocator &alloc) - : constraints(alloc), - frozenScripts(alloc), - failed_(false) + : failed_(false) +#ifdef JS_ION + , constraints(alloc) + , frozenScripts(alloc) +#endif {} void add(CompilerConstraint *constraint) { diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h index b0fee8c468fa..00401a0fbe8a 100644 --- a/js/src/jsprototypes.h +++ b/js/src/jsprototypes.h @@ -54,7 +54,7 @@ macro(DataView, 35, js_InitTypedArrayClasses) \ macro(ParallelArray, 36, js_InitParallelArrayClass) \ macro(Intl, 37, js_InitIntlClass) \ - macro(TypedObject, 38, js_InitTypedObjectDummy) \ + macro(TypedObject, 38, js_InitTypedObjectClass) \ macro(GeneratorFunction, 39, js_InitIteratorClasses) \ #endif /* jsprototypes_h */ diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index c83216d75330..1ee0a6f8061d 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1032,9 +1032,22 @@ JSScript::sourceData(JSContext *cx) return scriptSource()->substring(cx, sourceStart, sourceEnd); } -JSStableString * -SourceDataCache::lookup(ScriptSource *ss) +SourceDataCache::AutoSuppressPurge::AutoSuppressPurge(JSContext *cx) + : cache_(cx->runtime()->sourceDataCache) { + oldValue_ = cache_.numSuppressPurges_++; +} + +SourceDataCache::AutoSuppressPurge::~AutoSuppressPurge() +{ + cache_.numSuppressPurges_--; + JS_ASSERT(cache_.numSuppressPurges_ == oldValue_); +} + +const jschar * +SourceDataCache::lookup(ScriptSource *ss, const AutoSuppressPurge &asp) +{ + JS_ASSERT(this == &asp.cache()); if (!map_) return nullptr; if (Map::Ptr p = map_->lookup(ss)) @@ -1042,31 +1055,41 @@ SourceDataCache::lookup(ScriptSource *ss) return nullptr; } -void -SourceDataCache::put(ScriptSource *ss, JSStableString *str) +bool +SourceDataCache::put(ScriptSource *ss, const jschar *str, const AutoSuppressPurge &asp) { + JS_ASSERT(this == &asp.cache()); + if (!map_) { map_ = js_new(); if (!map_) - return; + return false; + if (!map_->init()) { - purge(); - return; + js_delete(map_); + map_ = nullptr; + return false; } } - (void) map_->put(ss, str); + return map_->put(ss, str); } void SourceDataCache::purge() { + if (!map_ || numSuppressPurges_ > 0) + return; + + for (Map::Range r = map_->all(); !r.empty(); r.popFront()) + js_delete(const_cast(r.front().value)); + js_delete(map_); map_ = nullptr; } const jschar * -ScriptSource::chars(JSContext *cx) +ScriptSource::chars(JSContext *cx, const SourceDataCache::AutoSuppressPurge &asp) { if (const jschar *chars = getOffThreadCompressionChars(cx)) return chars; @@ -1074,27 +1097,30 @@ ScriptSource::chars(JSContext *cx) #ifdef USE_ZLIB if (compressed()) { - JSStableString *cached = cx->runtime()->sourceDataCache.lookup(this); - if (!cached) { - const size_t nbytes = sizeof(jschar) * (length_ + 1); - jschar *decompressed = static_cast(js_malloc(nbytes)); - if (!decompressed) - return nullptr; - if (!DecompressString(data.compressed, compressedLength_, - reinterpret_cast(decompressed), nbytes)) { - JS_ReportOutOfMemory(cx); - js_free(decompressed); - return nullptr; - } - decompressed[length_] = 0; - cached = js_NewString(cx, decompressed, length_); - if (!cached) { - js_free(decompressed); - return nullptr; - } - cx->runtime()->sourceDataCache.put(this, cached); + if (const jschar *decompressed = cx->runtime()->sourceDataCache.lookup(this, asp)) + return decompressed; + + const size_t nbytes = sizeof(jschar) * (length_ + 1); + jschar *decompressed = static_cast(js_malloc(nbytes)); + if (!decompressed) + return nullptr; + + if (!DecompressString(data.compressed, compressedLength_, + reinterpret_cast(decompressed), nbytes)) { + JS_ReportOutOfMemory(cx); + js_free(decompressed); + return nullptr; } - return cached->chars().get(); + + decompressed[length_] = 0; + + if (!cx->runtime()->sourceDataCache.put(this, decompressed, asp)) { + JS_ReportOutOfMemory(cx); + js_free(decompressed); + return nullptr; + } + + return decompressed; } #endif return data.source; @@ -1104,7 +1130,8 @@ JSStableString * ScriptSource::substring(JSContext *cx, uint32_t start, uint32_t stop) { JS_ASSERT(start <= stop); - const jschar *chars = this->chars(cx); + SourceDataCache::AutoSuppressPurge asp(cx); + const jschar *chars = this->chars(cx, asp); if (!chars) return nullptr; JSFlatString *flatStr = js_NewStringCopyN(cx, chars + start, stop - start); @@ -3139,14 +3166,13 @@ LazyScriptHashPolicy::match(JSScript *script, const Lookup &lookup) return false; } - // GC activity may destroy the character pointers being compared below. - AutoSuppressGC suppress(cx); + SourceDataCache::AutoSuppressPurge asp(cx); - const jschar *scriptChars = script->scriptSource()->chars(cx); + const jschar *scriptChars = script->scriptSource()->chars(cx, asp); if (!scriptChars) return false; - const jschar *lazyChars = lazy->source()->chars(cx); + const jschar *lazyChars = lazy->source()->chars(cx, asp); if (!lazyChars) return false; diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 21e00a1af318..3809069e4c0a 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -297,6 +297,36 @@ typedef HashMap, SystemAllocPolicy> DebugScriptMap; +class ScriptSource; + +class SourceDataCache +{ + typedef HashMap, + SystemAllocPolicy> Map; + Map *map_; + size_t numSuppressPurges_; + + public: + SourceDataCache() : map_(nullptr), numSuppressPurges_(0) {} + + class AutoSuppressPurge + { + SourceDataCache &cache_; + mozilla::DebugOnly oldValue_; + public: + explicit AutoSuppressPurge(JSContext *cx); + ~AutoSuppressPurge(); + SourceDataCache &cache() const { return cache_; } + }; + + const jschar *lookup(ScriptSource *ss, const AutoSuppressPurge &asp); + bool put(ScriptSource *ss, const jschar *chars, const AutoSuppressPurge &asp); + + void purge(); +}; + class ScriptSource { friend class SourceCompressionTask; @@ -369,7 +399,7 @@ class ScriptSource JS_ASSERT(hasSourceData()); return argumentsNotIncluded_; } - const jschar *chars(JSContext *cx); + const jschar *chars(JSContext *cx, const SourceDataCache::AutoSuppressPurge &asp); JSStableString *substring(JSContext *cx, uint32_t start, uint32_t stop); size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); diff --git a/js/src/jsutil.cpp b/js/src/jsutil.cpp index 5c3ef97aed92..2dcb8a2922ed 100644 --- a/js/src/jsutil.cpp +++ b/js/src/jsutil.cpp @@ -161,6 +161,49 @@ JS_Assert(const char *s, const char *file, int ln) MOZ_CRASH(); } +#ifdef __linux__ + +#include +#include + +namespace js { + +// This function calls all the vanilla heap allocation functions. It is never +// called, and exists purely to help config/check_vanilla_allocations.py. See +// that script for more details. +extern void +AllTheNonBasicVanillaNewAllocations() +{ + // posix_memalign and aligned_alloc aren't available on all Linux + // configurations. + //char *q; + //posix_memalign((void**)&q, 16, 16); + + intptr_t p = + intptr_t(malloc(16)) + + intptr_t(calloc(1, 16)) + + intptr_t(realloc(nullptr, 16)) + + intptr_t(new char) + + intptr_t(new char) + + intptr_t(new char) + + intptr_t(new char[16]) + + intptr_t(memalign(16, 16)) + + //intptr_t(q) + + //intptr_t(aligned_alloc(16, 16)) + + intptr_t(valloc(4096)) + + intptr_t(strdup("dummy")); + + printf("%u\n", uint32_t(p)); // make sure |p| is not optimized away + + free((int*)p); // this would crash if ever actually called + + MOZ_CRASH(); +} + +} // namespace js + +#endif // __linux__ + #ifdef JS_BASIC_STATS #include diff --git a/js/src/moz.build b/js/src/moz.build index ab5ee30d951f..ac6144978db1 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -210,7 +210,7 @@ if CONFIG['ENABLE_TRACE_LOGGING']: ] if CONFIG['ENABLE_ION']: - SOURCES += [ + UNIFIED_SOURCES += [ 'jit/AliasAnalysis.cpp', 'jit/AsmJS.cpp', 'jit/AsmJSLink.cpp', @@ -267,7 +267,7 @@ if CONFIG['ENABLE_ION']: 'jit/VMFunctions.cpp', ] if CONFIG['TARGET_CPU'].find('86') != -1: - SOURCES += [ + UNIFIED_SOURCES += [ 'jit/shared/Assembler-x86-shared.cpp', 'jit/shared/BaselineCompiler-x86-shared.cpp', 'jit/shared/BaselineIC-x86-shared.cpp', @@ -277,7 +277,7 @@ if CONFIG['ENABLE_ION']: 'jit/shared/MoveEmitter-x86-shared.cpp', ] if CONFIG['TARGET_CPU'] == 'x86_64': - SOURCES += [ + UNIFIED_SOURCES += [ 'jit/x64/Assembler-x64.cpp', 'jit/x64/Bailouts-x64.cpp', 'jit/x64/BaselineCompiler-x64.cpp', @@ -288,7 +288,7 @@ if CONFIG['ENABLE_ION']: 'jit/x64/Trampoline-x64.cpp', ] else: - SOURCES += [ + UNIFIED_SOURCES += [ 'jit/x86/Assembler-x86.cpp', 'jit/x86/Bailouts-x86.cpp', 'jit/x86/BaselineCompiler-x86.cpp', @@ -299,7 +299,7 @@ if CONFIG['ENABLE_ION']: 'jit/x86/Trampoline-x86.cpp', ] elif CONFIG['TARGET_CPU'].find('arm') != -1: - SOURCES += [ + UNIFIED_SOURCES += [ 'jit/arm/Architecture-arm.cpp', 'jit/arm/Assembler-arm.cpp', 'jit/arm/Bailouts-arm.cpp', diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 9d64c2575ccc..36a581a8a9e8 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -3604,7 +3604,7 @@ NestedShell(JSContext *cx, unsigned argc, jsval *vp) // As a special case, if the caller passes "--js-cache", replace that // with "--js-cache=$(jsCacheDir)" - if (!strcmp(argv.back(), "--js-cache")) { + if (!strcmp(argv.back(), "--js-cache") && jsCacheDir) { char *newArg = JS_smprintf("--js-cache=%s", jsCacheDir); if (!newArg) return false; diff --git a/js/src/tests/ecma_6/TypedObject/objecttype.js b/js/src/tests/ecma_6/TypedObject/objecttype.js new file mode 100644 index 000000000000..7a8f0a1f7118 --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/objecttype.js @@ -0,0 +1,38 @@ +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")) +var BUGNUMBER = 917454; +var summary = 'objecttype'; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var T = TypedObject; + +function runTests() { + var Point = new T.ArrayType(T.float32, 3); + var Line = new T.StructType({from: Point, to: Point}); + var Lines = new T.ArrayType(Line, 3); + + var lines = new Lines([ + {from: [1, 2, 3], to: [4, 5, 6]}, + {from: [7, 8, 9], to: [10, 11, 12]}, + {from: [13, 14, 15], to: [16, 17, 18]} + ]); + + assertEq(T.objectType(lines), Lines); + assertEq(T.objectType(lines[0]), Line); + assertEq(T.objectType(lines[0].from[0]), T.float64); + assertEq(T.objectType(""), T.String); + assertEq(T.objectType({}), T.Object); + assertEq(T.objectType([]), T.Object); + assertEq(T.objectType(function() { }), T.Object); + assertEq(T.objectType(undefined), T.Any); + + reportCompare(true, true); + print("Tests complete"); +} + +runTests(); + + diff --git a/js/src/tests/ecma_6/TypedObject/referencetypetrace.js b/js/src/tests/ecma_6/TypedObject/referencetypetrace.js index e1b90399d8cb..e0fa221716ec 100644 --- a/js/src/tests/ecma_6/TypedObject/referencetypetrace.js +++ b/js/src/tests/ecma_6/TypedObject/referencetypetrace.js @@ -4,6 +4,11 @@ var summary = 'TypedObjects reference type trace'; var actual = ''; var expect = ''; +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + var ArrayType = TypedObject.ArrayType; var StructType = TypedObject.StructType; var Any = TypedObject.Any; diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4alignment.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4alignment.js new file mode 100644 index 000000000000..6fa650e57f8f --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4alignment.js @@ -0,0 +1,30 @@ +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")) +var BUGNUMBER = 938728; +var summary = 'float32x4 alignment'; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var StructType = TypedObject.StructType; +var uint8 = TypedObject.uint8; +var float32x4 = TypedObject.float32x4; + +function test() { + print(BUGNUMBER + ": " + summary); + + assertEq(float32x4.byteLength, 16); + assertEq(float32x4.byteAlignment, 16); + + var Compound = new StructType({c: uint8, d: uint8, f: float32x4}); + assertEq(Compound.fieldOffsets["c"], 0); + assertEq(Compound.fieldOffsets["d"], 1); + assertEq(Compound.fieldOffsets["f"], 16); + + if (typeof reportCompare === "function") + reportCompare(true, true); + print("Tests complete"); +} + +test(); diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4getters.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4getters.js new file mode 100644 index 000000000000..51b69837465e --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4getters.js @@ -0,0 +1,51 @@ +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")) +var BUGNUMBER = 938728; +var summary = 'float32x4 getters'; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var float32x4 = TypedObject.float32x4; + +function test() { + print(BUGNUMBER + ": " + summary); + + // Create a float32x4 and check that the getters work: + var f = float32x4(11, 22, 33, 44); + assertEq(f.x, 11); + assertEq(f.y, 22); + assertEq(f.z, 33); + assertEq(f.w, 44); + + // Test that the getters work when called reflectively: + var g = f.__lookupGetter__("x"); + assertEq(g.call(f), 11); + + // Test that getters cannot be applied to various incorrect things: + assertThrowsInstanceOf(function() { + g.call({}) + }, TypeError, "Getter applicable to random objects"); + assertThrowsInstanceOf(function() { + g.call(0xDEADBEEF) + }, TypeError, "Getter applicable to integers"); + assertThrowsInstanceOf(function() { + var T = new TypedObject.StructType({x: TypedObject.float32, + y: TypedObject.float32, + z: TypedObject.float32, + w: TypedObject.float32}); + var v = new T({x: 11, y: 22, z: 33, w: 44}); + g.call(v) + }, TypeError, "Getter applicable to structs"); + assertThrowsInstanceOf(function() { + var t = new TypedObject.int32x4(1, 2, 3, 4); + g.call(t) + }, TypeError, "Getter applicable to int32x4"); + + if (typeof reportCompare === "function") + reportCompare(true, true); + print("Tests complete"); +} + +test(); diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4handle.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4handle.js new file mode 100644 index 000000000000..5f5cfc5f3c54 --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4handle.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")) +var BUGNUMBER = 938728; +var summary = 'float32x4 handles'; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var ArrayType = TypedObject.ArrayType; +var float32x4 = TypedObject.float32x4; +var float32 = TypedObject.float32; +var Handle = TypedObject.Handle; + +function test() { + print(BUGNUMBER + ": " + summary); + + var Array = new ArrayType(float32x4, 3); + var array = new Array([float32x4(1, 2, 3, 4), + float32x4(5, 6, 7, 8), + float32x4(9, 10, 11, 12)]); + + // Test that trying to create handle into the interior of a + // float32x4 fails. + + assertThrowsInstanceOf(function() { + var h = float32.handle(array, 1, "w"); + }, TypeError, "Creating a float32 handle to prop via ctor"); + + assertThrowsInstanceOf(function() { + var h = float32.handle(); + Handle.move(h, array, 1, "w"); + }, TypeError, "Creating a float32 handle to prop via move"); + + assertThrowsInstanceOf(function() { + var h = float32.handle(array, 1, 0); + }, TypeError, "Creating a float32 handle to elem via ctor"); + + assertThrowsInstanceOf(function() { + var h = float32.handle(); + Handle.move(h, array, 1, 0); + }, TypeError, "Creating a float32 handle to elem via move"); + + if (typeof reportCompare === "function") + reportCompare(true, true); + print("Tests complete"); +} + +test(); diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4reify.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4reify.js new file mode 100644 index 000000000000..e33813d24e75 --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4reify.js @@ -0,0 +1,36 @@ +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")) +var BUGNUMBER = 938728; +var summary = 'float32x4 reify'; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var ArrayType = TypedObject.ArrayType; +var float32x4 = TypedObject.float32x4; + +function test() { + print(BUGNUMBER + ": " + summary); + + var Array = new ArrayType(float32x4, 3); + var array = new Array([float32x4(1, 2, 3, 4), + float32x4(5, 6, 7, 8), + float32x4(9, 10, 11, 12)]); + + // Test that reading array[1] produces a *copy* of float32x4, not an + // alias into the array. + + var f = array[1]; + assertEq(f.w, 8); + assertEq(array[1].w, 8); + array[1] = float32x4(15, 16, 17, 18); + assertEq(f.w, 8); + assertEq(array[1].w, 18); + + if (typeof reportCompare === "function") + reportCompare(true, true); + print("Tests complete"); +} + +test(); diff --git a/js/src/tests/ecma_6/TypedObject/simd/float32x4setter.js b/js/src/tests/ecma_6/TypedObject/simd/float32x4setter.js new file mode 100644 index 000000000000..0867c18be9fc --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4setter.js @@ -0,0 +1,45 @@ +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")) +var BUGNUMBER = 938728; +var summary = 'float32x4 setting'; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var ArrayType = TypedObject.ArrayType; +var float32x4 = TypedObject.float32x4; + +function test() { + print(BUGNUMBER + ": " + summary); + + var Array = new ArrayType(float32x4, 3); + var array = new Array([float32x4(1, 2, 3, 4), + float32x4(5, 6, 7, 8), + float32x4(9, 10, 11, 12)]); + assertEq(array[1].w, 8); + + // Test that we are allowed to write float32x4 values into array, + // but not other things. + + array[1] = float32x4(15, 16, 17, 18); + assertEq(array[1].w, 18); + + assertThrowsInstanceOf(function() { + array[1] = {x: 15, y: 16, z: 17, w: 18}; + }, TypeError, "Setting float32x4 from an object"); + + assertThrowsInstanceOf(function() { + array[1] = [15, 16, 17, 18]; + }, TypeError, "Setting float32x4 from an array"); + + assertThrowsInstanceOf(function() { + array[1] = 22; + }, TypeError, "Setting float32x4 from a number"); + + if (typeof reportCompare === "function") + reportCompare(true, true); + print("Tests complete"); +} + +test(); diff --git a/js/src/tests/ecma_6/TypedObject/simd/int32x4alignment.js b/js/src/tests/ecma_6/TypedObject/simd/int32x4alignment.js new file mode 100644 index 000000000000..7a427cf10d72 --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4alignment.js @@ -0,0 +1,30 @@ +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")) +var BUGNUMBER = 938728; +var summary = 'int32x4 alignment'; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var StructType = TypedObject.StructType; +var uint8 = TypedObject.uint8; +var int32x4 = TypedObject.int32x4; + +function test() { + print(BUGNUMBER + ": " + summary); + + assertEq(int32x4.byteLength, 16); + assertEq(int32x4.byteAlignment, 16); + + var Compound = new StructType({c: uint8, d: uint8, f: int32x4}); + assertEq(Compound.fieldOffsets["c"], 0); + assertEq(Compound.fieldOffsets["d"], 1); + assertEq(Compound.fieldOffsets["f"], 16); + + if (typeof reportCompare === "function") + reportCompare(true, true); + print("Tests complete"); +} + +test(); diff --git a/js/src/tests/ecma_6/TypedObject/simd/int32x4getters.js b/js/src/tests/ecma_6/TypedObject/simd/int32x4getters.js new file mode 100644 index 000000000000..4b99f13761a6 --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4getters.js @@ -0,0 +1,51 @@ +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")) +var BUGNUMBER = 938728; +var summary = 'int32x4 getters'; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var int32x4 = TypedObject.int32x4; + +function test() { + print(BUGNUMBER + ": " + summary); + + // Create a int32x4 and check that the getters work: + var f = int32x4(11, 22, 33, 44); + assertEq(f.x, 11); + assertEq(f.y, 22); + assertEq(f.z, 33); + assertEq(f.w, 44); + + // Test that the getters work when called reflectively: + var g = f.__lookupGetter__("x"); + assertEq(g.call(f), 11); + + // Test that getters cannot be applied to various incorrect things: + assertThrowsInstanceOf(function() { + g.call({}) + }, TypeError, "Getter applicable to random objects"); + assertThrowsInstanceOf(function() { + g.call(0xDEADBEEF) + }, TypeError, "Getter applicable to integers"); + assertThrowsInstanceOf(function() { + var T = new TypedObject.StructType({x: TypedObject.int32, + y: TypedObject.int32, + z: TypedObject.int32, + w: TypedObject.int32}); + var v = new T({x: 11, y: 22, z: 33, w: 44}); + g.call(v) + }, TypeError, "Getter applicable to structs"); + assertThrowsInstanceOf(function() { + var t = new TypedObject.float32x4(1, 2, 3, 4); + g.call(t) + }, TypeError, "Getter applicable to float32x4"); + + if (typeof reportCompare === "function") + reportCompare(true, true); + print("Tests complete"); +} + +test(); diff --git a/js/src/tests/ecma_6/TypedObject/simd/int32x4handle.js b/js/src/tests/ecma_6/TypedObject/simd/int32x4handle.js new file mode 100644 index 000000000000..bb64c14390c1 --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4handle.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")) +var BUGNUMBER = 938728; +var summary = 'int32x4 handles'; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var ArrayType = TypedObject.ArrayType; +var int32x4 = TypedObject.int32x4; +var int32 = TypedObject.int32; +var Handle = TypedObject.Handle; + +function test() { + print(BUGNUMBER + ": " + summary); + + var Array = new ArrayType(int32x4, 3); + var array = new Array([int32x4(1, 2, 3, 4), + int32x4(5, 6, 7, 8), + int32x4(9, 10, 11, 12)]); + + // Test that trying to create handle into the interior of a + // int32x4 fails. + + assertThrowsInstanceOf(function() { + var h = int32.handle(array, 1, "w"); + }, TypeError, "Creating a int32 handle to prop via ctor"); + + assertThrowsInstanceOf(function() { + var h = int32.handle(); + Handle.move(h, array, 1, "w"); + }, TypeError, "Creating a int32 handle to prop via move"); + + assertThrowsInstanceOf(function() { + var h = int32.handle(array, 1, 0); + }, TypeError, "Creating a int32 handle to elem via ctor"); + + assertThrowsInstanceOf(function() { + var h = int32.handle(); + Handle.move(h, array, 1, 0); + }, TypeError, "Creating a int32 handle to elem via move"); + + if (typeof reportCompare === "function") + reportCompare(true, true); + print("Tests complete"); +} + +test(); diff --git a/js/src/tests/ecma_6/TypedObject/simd/int32x4reify.js b/js/src/tests/ecma_6/TypedObject/simd/int32x4reify.js new file mode 100644 index 000000000000..a7854c8578f5 --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4reify.js @@ -0,0 +1,36 @@ +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")) +var BUGNUMBER = 938728; +var summary = 'int32x4 reify'; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var ArrayType = TypedObject.ArrayType; +var int32x4 = TypedObject.int32x4; + +function test() { + print(BUGNUMBER + ": " + summary); + + var Array = new ArrayType(int32x4, 3); + var array = new Array([int32x4(1, 2, 3, 4), + int32x4(5, 6, 7, 8), + int32x4(9, 10, 11, 12)]); + + // Test that reading array[1] produces a *copy* of int32x4, not an + // alias into the array. + + var f = array[1]; + assertEq(f.w, 8); + assertEq(array[1].w, 8); + array[1] = int32x4(15, 16, 17, 18); + assertEq(f.w, 8); + assertEq(array[1].w, 18); + + if (typeof reportCompare === "function") + reportCompare(true, true); + print("Tests complete"); +} + +test(); diff --git a/js/src/tests/ecma_6/TypedObject/simd/int32x4setter.js b/js/src/tests/ecma_6/TypedObject/simd/int32x4setter.js new file mode 100644 index 000000000000..18bdf79d4edb --- /dev/null +++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4setter.js @@ -0,0 +1,45 @@ +// |reftest| skip-if(!this.hasOwnProperty("TypedObject")) +var BUGNUMBER = 938728; +var summary = 'int32x4 setting'; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var ArrayType = TypedObject.ArrayType; +var int32x4 = TypedObject.int32x4; + +function test() { + print(BUGNUMBER + ": " + summary); + + var Array = new ArrayType(int32x4, 3); + var array = new Array([int32x4(1, 2, 3, 4), + int32x4(5, 6, 7, 8), + int32x4(9, 10, 11, 12)]); + assertEq(array[1].w, 8); + + // Test that we are allowed to write int32x4 values into array, + // but not other things. + + array[1] = int32x4(15, 16, 17, 18); + assertEq(array[1].w, 18); + + assertThrowsInstanceOf(function() { + array[1] = {x: 15, y: 16, z: 17, w: 18}; + }, TypeError, "Setting int32x4 from an object"); + + assertThrowsInstanceOf(function() { + array[1] = [15, 16, 17, 18]; + }, TypeError, "Setting int32x4 from an array"); + + assertThrowsInstanceOf(function() { + array[1] = 22; + }, TypeError, "Setting int32x4 from a number"); + + if (typeof reportCompare === "function") + reportCompare(true, true); + print("Tests complete"); +} + +test(); diff --git a/js/src/tests/ecma_6/TypedObject/simd/shell.js b/js/src/tests/ecma_6/TypedObject/simd/shell.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/js/src/tests/lib/jittests.py b/js/src/tests/lib/jittests.py index fd10c5c5c768..f7ddcb4d0095 100755 --- a/js/src/tests/lib/jittests.py +++ b/js/src/tests/lib/jittests.py @@ -659,7 +659,7 @@ def run_tests_remote(tests, prefix, options): dm.chmodDir(options.remote_test_root) Test.CacheDir = posixpath.join(options.remote_test_root, '.js-cache') - dm.mkDirs(Test.CacheDir) + dm.mkDir(Test.CacheDir) dm.pushDir(ECMA6_DIR, posixpath.join(jit_tests_dir, 'tests', 'ecma_6'), timeout=600) dm.pushDir(os.path.dirname(TEST_DIR), options.remote_test_root, timeout=600) diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 2bf8c7c0e94f..d1fbd61086d7 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -69,6 +69,7 @@ macro(FillTypedArrayWithValue, FillTypedArrayWithValue, "FillTypedArrayWithValue") \ macro(fix, fix, "fix") \ macro(float32, float32, "float32") \ + macro(float32x4, float32x4, "float32x4") \ macro(float64, float64, "float64") \ macro(format, format, "format") \ macro(from, from, "from") \ @@ -90,6 +91,7 @@ macro(InitializeNumberFormat, InitializeNumberFormat, "InitializeNumberFormat") \ macro(innermost, innermost, "innermost") \ macro(input, input, "input") \ + macro(int32x4, int32x4, "int32x4") \ macro(isFinite, isFinite, "isFinite") \ macro(isNaN, isNaN, "isNaN") \ macro(isPrototypeOf, isPrototypeOf, "isPrototypeOf") \ @@ -177,7 +179,11 @@ macro(void0, void0, "(void 0)") \ macro(watch, watch, "watch") \ macro(writable, writable, "writable") \ + macro(w, w, "w") \ + macro(x, x, "x") \ + macro(y, y, "y") \ macro(yield, yield, "yield") \ + macro(z, z, "z") \ /* Type names must be contiguous and ordered; see js::TypeName. */ \ macro(undefined, undefined, "undefined") \ macro(object, object, "object") \ diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index cbe9ee03d224..6cebb57bd068 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -418,6 +418,13 @@ class GlobalObject : public JSObject return getOrCreateObject(cx, APPLICATION_SLOTS + JSProto_Intl, initIntlObject); } + JSObject &getTypedObject() { + Value v = getConstructor(JSProto_TypedObject); + // only gets called from contexts where TypedObject must be initialized + JS_ASSERT(v.isObject()); + return v.toObject(); + } + JSObject *getIteratorPrototype() { return &getPrototype(JSProto_Iterator).toObject(); } diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index 8b2301cea0fb..3175beeb9949 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -94,14 +94,13 @@ InefficientNonFlatteningStringHashPolicy::match(const JSString *const &k, const namespace JS { NotableStringInfo::NotableStringInfo() - : bufferSize(0), - buffer(0) + : buffer(0) {} NotableStringInfo::NotableStringInfo(JSString *str, const StringInfo &info) : StringInfo(info) { - bufferSize = Min(str->length() + 1, size_t(4096)); + size_t bufferSize = Min(str->length() + 1, size_t(4096)); buffer = js_pod_malloc(bufferSize); if (!buffer) { MOZ_CRASH("oom"); @@ -123,17 +122,6 @@ NotableStringInfo::NotableStringInfo(JSString *str, const StringInfo &info) PutEscapedString(buffer, bufferSize, chars, str->length(), /* quote */ 0); } -NotableStringInfo::NotableStringInfo(const NotableStringInfo& info) - : StringInfo(info), - bufferSize(info.bufferSize) -{ - buffer = js_pod_malloc(bufferSize); - if (!buffer) - MOZ_CRASH("oom"); - - strcpy(buffer, info.buffer); -} - NotableStringInfo::NotableStringInfo(NotableStringInfo &&info) : StringInfo(Move(info)) { diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 64b294ea9026..0d5c6e6a44b5 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -591,6 +591,14 @@ JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Runtim rtSizes->scriptData += mallocSizeOf(r.front()); } +static bool +SignalBasedTriggersDisabled() +{ + // Don't bother trying to cache the getenv lookup; this should be called + // infrequently. + return !!getenv("JS_DISABLE_SLOW_SCRIPT_SIGNALS"); +} + void JSRuntime::triggerOperationCallback(OperationCallbackTrigger trigger) { @@ -611,8 +619,10 @@ JSRuntime::triggerOperationCallback(OperationCallbackTrigger trigger) * asm.js and, optionally, normal Ion code use memory protection and signal * handlers to halt running code. */ - TriggerOperationCallbackForAsmJSCode(this); - jit::TriggerOperationCallbackForIonCode(this, trigger); + if (!SignalBasedTriggersDisabled()) { + TriggerOperationCallbackForAsmJSCode(this); + jit::TriggerOperationCallbackForIonCode(this, trigger); + } #endif } @@ -847,7 +857,7 @@ js::CurrentThreadCanAccessZone(Zone *zone) void JSRuntime::assertCanLock(RuntimeLock which) { -#ifdef JS_THREADSAFE +#ifdef JS_WORKER_THREADS // In the switch below, each case falls through to the one below it. None // of the runtime locks are reentrant, and when multiple locks are acquired // it must be done in the order below. diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index eb76993eb61e..f1ae76a8cd1b 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -167,21 +167,6 @@ struct ConservativeGCData } }; -class SourceDataCache -{ - typedef HashMap, - SystemAllocPolicy> Map; - Map *map_; - - public: - SourceDataCache() : map_(nullptr) {} - JSStableString *lookup(ScriptSource *ss); - void put(ScriptSource *ss, JSStableString *); - void purge(); -}; - struct EvalCacheEntry { JSScript *script; diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index d5122bb506c4..48a2f44546e9 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -658,6 +658,9 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FNINFO("Memcpy", JSNativeThreadSafeWrapper, &js::MemcpyJitInfo, 5, 0), + JS_FN("StandardTypeObjectDescriptors", + js::StandardTypeObjectDescriptors, + 0, 0), #define LOAD_AND_STORE_SCALAR_FN_DECLS(_constant, _type, _name) \ JS_FNINFO("Store_" #_name, \ diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp index 6b2313b74623..a86e483727de 100644 --- a/js/src/vm/Shape.cpp +++ b/js/src/vm/Shape.cpp @@ -393,6 +393,7 @@ JSObject::getChildPropertyOnDictionary(ThreadSafeContext *cx, JS::HandleObject o JSObject::getChildProperty(ExclusiveContext *cx, HandleObject obj, HandleShape parent, StackShape &child) { + StackShape::AutoRooter childRoot(cx, &child); RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, child)); if (!shape) { @@ -412,6 +413,7 @@ JSObject::getChildProperty(ExclusiveContext *cx, JSObject::lookupChildProperty(ThreadSafeContext *cx, HandleObject obj, HandleShape parent, StackShape &child) { + StackShape::AutoRooter childRoot(cx, &child); JS_ASSERT(cx->isThreadLocal(obj)); RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, child)); diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index e67cce905ac0..7977ad388844 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -335,7 +335,9 @@ bool ArrayBufferObject::neuterViews(JSContext *cx) { ArrayBufferViewObject *view; + size_t numViews = 0; for (view = GetViewList(this); view; view = view->nextView()) { + numViews++; view->neuter(); // Notify compiled jit code that the base pointer has moved. @@ -349,6 +351,25 @@ ArrayBufferObject::neuterViews(JSContext *cx) return false; } + // Remove buffer from the list of buffers with > 1 view. + if (numViews > 1 && GetViewList(this)->bufferLink() != UNSET_BUFFER_LINK) { + ArrayBufferObject *prev = compartment()->gcLiveArrayBuffers; + if (prev == this) { + compartment()->gcLiveArrayBuffers = GetViewList(prev)->bufferLink(); + } else { + for (ArrayBufferObject *buf = GetViewList(prev)->bufferLink(); + buf; + buf = GetViewList(buf)->bufferLink()) + { + if (buf == this) { + GetViewList(prev)->setBufferLink(GetViewList(buf)->bufferLink()); + break; + } + prev = buf; + } + } + } + return true; } diff --git a/js/xpconnect/idl/nsIXPConnect.idl b/js/xpconnect/idl/nsIXPConnect.idl index f30e1fb4656d..4b476725baca 100644 --- a/js/xpconnect/idl/nsIXPConnect.idl +++ b/js/xpconnect/idl/nsIXPConnect.idl @@ -273,6 +273,7 @@ interface nsIXPCFunctionThisTranslator : nsISupports /***************************************************************************/ + %{ C++ // For use with the service manager // {CB6593E0-F9B2-11d2-BDD6-000064657374} @@ -281,7 +282,7 @@ interface nsIXPCFunctionThisTranslator : nsISupports { 0xbd, 0xd6, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74 } } %} -[uuid(c4d0187c-6a78-4bdf-9cd9-d218644b715a)] +[uuid(880be309-88a1-4e98-8621-7f7e42681b20)] interface nsIXPConnect : nsISupports { %{ C++ @@ -446,6 +447,7 @@ interface nsIXPConnect : nsISupports [noscript,notxpcom,nostdcall] JSContextPtr getCurrentJSContext(); + [noscript,notxpcom,nostdcall] JSContextPtr initSafeJSContext(); [noscript,notxpcom,nostdcall] JSContextPtr getSafeJSContext(); readonly attribute nsIStackFrame CurrentJSStack; diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 16f0aaf52bcb..59616b7b004a 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -3381,8 +3381,8 @@ nsXPCComponents_Utils::BlockScriptForGlobal(const JS::Value &globalArg, JSContext *cx) { NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG); - JSObject *global = UncheckedUnwrap(&globalArg.toObject(), - /* stopAtOuter = */ false); + RootedObject global(cx, UncheckedUnwrap(&globalArg.toObject(), + /* stopAtOuter = */ false)); NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG); if (nsContentUtils::IsSystemPrincipal(GetObjectPrincipal(global))) { JS_ReportError(cx, "Script may not be disabled for system globals"); @@ -3397,8 +3397,8 @@ nsXPCComponents_Utils::UnblockScriptForGlobal(const JS::Value &globalArg, JSContext *cx) { NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG); - JSObject *global = UncheckedUnwrap(&globalArg.toObject(), - /* stopAtOuter = */ false); + RootedObject global(cx, UncheckedUnwrap(&globalArg.toObject(), + /* stopAtOuter = */ false)); NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG); if (nsContentUtils::IsSystemPrincipal(GetObjectPrincipal(global))) { JS_ReportError(cx, "Script may not be disabled for system globals"); diff --git a/js/xpconnect/src/XPCJSContextStack.cpp b/js/xpconnect/src/XPCJSContextStack.cpp index 7171c37c0198..22ae79ee956b 100644 --- a/js/xpconnect/src/XPCJSContextStack.cpp +++ b/js/xpconnect/src/XPCJSContextStack.cpp @@ -134,8 +134,14 @@ const JSClass xpc::SafeJSContextGlobalClass = { JSContext* XPCJSContextStack::GetSafeJSContext() { - if (mSafeJSContext) - return mSafeJSContext; + MOZ_ASSERT(mSafeJSContext); + return mSafeJSContext; +} + +JSContext* +XPCJSContextStack::InitSafeJSContext() +{ + MOZ_ASSERT(!mSafeJSContext); // Start by getting the principal holder and principal for this // context. If we can't manage that, don't bother with the rest. diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 5513a149f495..e3a610287380 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -37,6 +37,7 @@ #include "mozilla/dom/GeneratedAtomList.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/WindowBinding.h" #include "mozilla/Attributes.h" #include "AccessCheck.h" #include "nsGlobalWindow.h" @@ -384,6 +385,20 @@ EnsureCompartmentPrivate(JSCompartment *c) return priv; } +XPCWrappedNativeScope* +MaybeGetObjectScope(JSObject *obj) +{ + MOZ_ASSERT(obj); + JSCompartment *compartment = js::GetObjectCompartment(obj); + + MOZ_ASSERT(compartment); + CompartmentPrivate *priv = GetCompartmentPrivate(compartment); + if (!priv) + return nullptr; + + return priv->scope; +} + static bool PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal) { @@ -566,6 +581,28 @@ GetJunkScopeGlobal() return GetNativeForGlobal(junkScope); } +nsGlobalWindow* +WindowGlobalOrNull(JSObject *aObj) +{ + MOZ_ASSERT(aObj); + JSObject *glob = js::GetGlobalForObjectCrossCompartment(aObj); + MOZ_ASSERT(glob); + + // This will always return null until we have Window on WebIDL bindings, + // at which point it will do the right thing. + if (!IS_WN_CLASS(js::GetObjectClass(glob))) { + nsGlobalWindow* win = nullptr; + UNWRAP_OBJECT(Window, nullptr, glob, win); + return win; + } + + nsISupports* supports = XPCWrappedNative::Get(glob)->GetIdentityObject(); + nsCOMPtr piWin = do_QueryInterface(supports); + if (!piWin) + return nullptr; + return static_cast(piWin.get()); +} + } static void @@ -711,14 +748,22 @@ XPCJSRuntime::PrepareForForgetSkippable() } void -XPCJSRuntime::PrepareForCollection() +XPCJSRuntime::BeginCycleCollectionCallback() { + nsJSContext::BeginCycleCollectionCallback(); + nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->NotifyObservers(nullptr, "cycle-collector-begin", nullptr); } } +void +XPCJSRuntime::EndCycleCollectionCallback(CycleCollectorResults &aResults) +{ + nsJSContext::EndCycleCollectionCallback(aResults); +} + void XPCJSRuntime::DispatchDeferredDeletion(bool aContinuation) { diff --git a/js/xpconnect/src/XPCMaps.h b/js/xpconnect/src/XPCMaps.h index 77bb18f3f947..0cc7c358eaea 100644 --- a/js/xpconnect/src/XPCMaps.h +++ b/js/xpconnect/src/XPCMaps.h @@ -646,7 +646,7 @@ public: return p->value; if (!mTable.add(p, key, value)) return nullptr; - MOZ_ASSERT(xpc::GetObjectScope(key)->mWaiverWrapperMap == this); + MOZ_ASSERT(xpc::GetCompartmentPrivate(key)->scope->mWaiverWrapperMap == this); JS_StoreObjectPostBarrierCallback(cx, KeyMarkCallback, key, this); return value; } diff --git a/js/xpconnect/src/nsCxPusher.cpp b/js/xpconnect/src/nsCxPusher.cpp index 83db41ac962a..2215a2a5f489 100644 --- a/js/xpconnect/src/nsCxPusher.cpp +++ b/js/xpconnect/src/nsCxPusher.cpp @@ -177,6 +177,7 @@ AutoJSContext::AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) void AutoJSContext::Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) { + JS::AutoAssertNoGC nogc; MOZ_ASSERT(!mCx, "mCx should not be initialized!"); MOZ_GUARD_OBJECT_NOTIFIER_INIT; diff --git a/js/xpconnect/src/nsXPConnect.cpp b/js/xpconnect/src/nsXPConnect.cpp index f730f7e2fea0..675fe6e8709b 100644 --- a/js/xpconnect/src/nsXPConnect.cpp +++ b/js/xpconnect/src/nsXPConnect.cpp @@ -1280,6 +1280,13 @@ nsXPConnect::GetCurrentJSContext() return GetRuntime()->GetJSContextStack()->Peek(); } +/* virtual */ +JSContext* +nsXPConnect::InitSafeJSContext() +{ + return GetRuntime()->GetJSContextStack()->InitSafeJSContext(); +} + /* virtual */ JSContext* nsXPConnect::GetSafeJSContext() diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg index eab596ffae17..734f3e1f4cdd 100644 --- a/js/xpconnect/src/xpc.msg +++ b/js/xpconnect/src/xpc.msg @@ -73,13 +73,13 @@ XPC_MSG_DEF(NS_ERROR_ALREADY_INITIALIZED , "Component already initiali XPC_MSG_DEF(NS_ERROR_NOT_IMPLEMENTED , "Method not implemented") XPC_MSG_DEF(NS_NOINTERFACE , "Component does not have requested interface") XPC_MSG_DEF(NS_ERROR_NO_INTERFACE , "Component does not have requested interface") +XPC_MSG_DEF(NS_ERROR_ILLEGAL_VALUE , "Illegal value") XPC_MSG_DEF(NS_ERROR_INVALID_POINTER , "Invalid pointer") XPC_MSG_DEF(NS_ERROR_NULL_POINTER , "Null pointer") XPC_MSG_DEF(NS_ERROR_ABORT , "Abort") XPC_MSG_DEF(NS_ERROR_FAILURE , "Failure") XPC_MSG_DEF(NS_ERROR_UNEXPECTED , "Unexpected error") XPC_MSG_DEF(NS_ERROR_OUT_OF_MEMORY , "Out of Memory") -XPC_MSG_DEF(NS_ERROR_ILLEGAL_VALUE , "Illegal value") XPC_MSG_DEF(NS_ERROR_INVALID_ARG , "Invalid argument") XPC_MSG_DEF(NS_ERROR_NO_AGGREGATION , "Component does not support aggregation") XPC_MSG_DEF(NS_ERROR_NOT_AVAILABLE , "Component is not available") diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index 4985bc16ac57..ecc1adb8f0ed 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -676,7 +676,8 @@ public: void TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback& cb) MOZ_OVERRIDE; void UnmarkSkippableJSHolders(); void PrepareForForgetSkippable() MOZ_OVERRIDE; - void PrepareForCollection() MOZ_OVERRIDE; + void BeginCycleCollectionCallback() MOZ_OVERRIDE; + void EndCycleCollectionCallback(mozilla::CycleCollectorResults &aResults) MOZ_OVERRIDE; void DispatchDeferredDeletion(bool continuation) MOZ_OVERRIDE; void CustomGCCallback(JSGCStatus status) MOZ_OVERRIDE; @@ -3033,6 +3034,7 @@ public: return mStack.IsEmpty() ? nullptr : mStack[mStack.Length() - 1].cx; } + JSContext *InitSafeJSContext(); JSContext *GetSafeJSContext(); bool HasJSContext(JSContext *cx); @@ -3786,6 +3788,9 @@ GetObjectScope(JSObject *obj) return EnsureCompartmentPrivate(obj)->scope; } +// This returns null if a scope doesn't already exist. +XPCWrappedNativeScope* MaybeGetObjectScope(JSObject *obj); + extern bool gDebugMode; extern bool gDesiredDebugMode; diff --git a/js/xpconnect/src/xpcpublic.h b/js/xpconnect/src/xpcpublic.h index 5b279f05976b..9b43aa7dbaeb 100644 --- a/js/xpconnect/src/xpcpublic.h +++ b/js/xpconnect/src/xpcpublic.h @@ -404,6 +404,13 @@ GetJunkScope(); nsIGlobalObject * GetJunkScopeGlobal(); +/** + * If |aObj| has a window for a global, returns the associated nsGlobalWindow. + * Otherwise, returns null. + */ +nsGlobalWindow* +WindowGlobalOrNull(JSObject *aObj); + // Error reporter used when there is no associated DOM window on to which to // report errors and warnings. void diff --git a/js/xpconnect/tests/chrome/chrome.ini b/js/xpconnect/tests/chrome/chrome.ini index 7c909ea8be6d..6279ae989a67 100644 --- a/js/xpconnect/tests/chrome/chrome.ini +++ b/js/xpconnect/tests/chrome/chrome.ini @@ -46,7 +46,9 @@ support-files = [test_bug812415.xul] [test_bug853283.xul] [test_bug853571.xul] +[test_bug858101.xul] [test_bug860494.xul] +[test_bug866823.xul] [test_bug895340.xul] [test_chrometoSource.xul] [test_cows.xul] diff --git a/js/xpconnect/tests/chrome/test_bug858101.xul b/js/xpconnect/tests/chrome/test_bug858101.xul new file mode 100644 index 000000000000..fc1dde056b32 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug858101.xul @@ -0,0 +1,55 @@ + + + + + + + + + +
+
+ + diff --git a/layout/base/moz.build b/layout/base/moz.build index 2c6544c1b2e9..2ed43247b728 100644 --- a/layout/base/moz.build +++ b/layout/base/moz.build @@ -111,4 +111,7 @@ MSVC_ENABLE_PGO = True include('/ipc/chromium/chromium-config.mozbuild') +LOCAL_INCLUDES += [ + '/docshell/base', +] FINAL_LIBRARY = 'gklayout' diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index 0035a2f0c665..50a8976ba882 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -36,6 +36,7 @@ #include "mozilla/BasicEvents.h" #include "mozilla/Preferences.h" #include "mozilla/dom/EncodingUtils.h" +#include "mozilla/WeakPtr.h" #include "nsViewManager.h" #include "nsView.h" @@ -48,7 +49,7 @@ #include "nsIMarkupDocumentViewer.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" -#include "nsIDocShell.h" +#include "nsDocShell.h" #include "nsIBaseWindow.h" #include "nsILayoutHistoryState.h" #include "nsCharsetSource.h" @@ -348,7 +349,7 @@ protected: // (ie, non owning) references. If you add any members to this // class, please make the ownership explicit (pinkerton, scc). - nsWeakPtr mContainer; // it owns me! + WeakPtr mContainer; // it owns me! nsWeakPtr mTopContainerWhilePrinting; nsRefPtr mDeviceContext; // We create and own this baby @@ -576,7 +577,7 @@ nsDocumentViewer::LoadStart(nsISupports *aDoc) nsresult nsDocumentViewer::SyncParentSubDocMap() { - nsCOMPtr item(do_QueryReferent(mContainer)); + nsCOMPtr item(mContainer); nsCOMPtr pwin(do_GetInterface(item)); nsCOMPtr content; @@ -610,9 +611,9 @@ nsDocumentViewer::SyncParentSubDocMap() } NS_IMETHODIMP -nsDocumentViewer::SetContainer(nsISupports* aContainer) +nsDocumentViewer::SetContainer(nsIDocShell* aContainer) { - mContainer = do_GetWeakReference(aContainer); + mContainer = static_cast(aContainer)->asWeakPtr(); if (mPresContext) { mPresContext->SetContainer(aContainer); } @@ -625,12 +626,11 @@ nsDocumentViewer::SetContainer(nsISupports* aContainer) } NS_IMETHODIMP -nsDocumentViewer::GetContainer(nsISupports** aResult) +nsDocumentViewer::GetContainer(nsIDocShell** aResult) { NS_ENSURE_ARG_POINTER(aResult); - *aResult = nullptr; - nsCOMPtr container = do_QueryReferent(mContainer); + nsCOMPtr container(mContainer); container.swap(*aResult); return NS_OK; } @@ -873,7 +873,7 @@ nsDocumentViewer::InitInternal(nsIWidget* aParentWidget, } } - nsCOMPtr requestor(do_QueryReferent(mContainer)); + nsCOMPtr requestor(mContainer); if (requestor) { if (mPresContext) { nsCOMPtr linkHandler; @@ -1094,7 +1094,7 @@ nsDocumentViewer::PermitUnload(bool aCallerClosesWindow, bool *aPermitUnload) // Never permit dialogs from the beforeunload handler nsCOMPtr utils = do_GetInterface(window); - bool dialogsWereEnabled; + bool dialogsWereEnabled = false; utils->AreDialogsEnabled(&dialogsWereEnabled); utils->DisableDialogs(); @@ -1107,7 +1107,7 @@ nsDocumentViewer::PermitUnload(bool aCallerClosesWindow, bool *aPermitUnload) } } - nsCOMPtr docShellNode(do_QueryReferent(mContainer)); + nsCOMPtr docShellNode(mContainer); nsAutoString text; beforeUnload->GetReturnValue(text); if (event->GetInternalNSEvent()->mFlags.mDefaultPrevented || @@ -1198,7 +1198,7 @@ nsDocumentViewer::ResetCloseWindow() { mCallerIsClosingWindow = false; - nsCOMPtr docShellNode(do_QueryReferent(mContainer)); + nsCOMPtr docShellNode(mContainer); if (docShellNode) { int32_t childCount; docShellNode->GetChildCount(&childCount); @@ -1286,7 +1286,7 @@ AttachContainerRecurse(nsIDocShell* aShell) if (viewer) { nsIDocument* doc = viewer->GetDocument(); if (doc) { - doc->SetContainer(aShell); + doc->SetContainer(static_cast(aShell)); } nsRefPtr pc; viewer->GetPresContext(getter_AddRefs(pc)); @@ -1317,7 +1317,7 @@ nsDocumentViewer::Open(nsISupports *aState, nsISHEntry *aSHEntry) NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED); if (mDocument) - mDocument->SetContainer(nsCOMPtr(do_QueryReferent(mContainer))); + mDocument->SetContainer(mContainer); nsresult rv = InitInternal(mParentWidget, aState, mBounds, false); NS_ENSURE_SUCCESS(rv, rv); @@ -1561,8 +1561,11 @@ nsDocumentViewer::Destroy() mPresContext->SetLinkHandler(nullptr); mPresContext->SetContainer(nullptr); } - if (mPresShell) - mPresShell->SetForwardingContainer(mContainer); + if (mPresShell) { + nsWeakPtr container = + do_GetWeakReference(static_cast(mContainer)); + mPresShell->SetForwardingContainer(container); + } // Do the same for our children. Note that we need to get the child // docshells from the SHEntry now; the docshell will have cleared them. @@ -1620,7 +1623,7 @@ nsDocumentViewer::Destroy() mWindow = nullptr; mViewManager = nullptr; - mContainer = nullptr; + mContainer = WeakPtr(); return NS_OK; } @@ -1691,8 +1694,7 @@ nsDocumentViewer::SetDocumentInternal(nsIDocument* aDocument, MOZ_ASSERT(aDocument); // Set new container - nsCOMPtr container = do_QueryReferent(mContainer); - aDocument->SetContainer(container); + aDocument->SetContainer(mContainer); if (mDocument != aDocument) { if (mDocument->IsStaticDocument()) { @@ -1704,7 +1706,8 @@ nsDocumentViewer::SetDocumentInternal(nsIDocument* aDocument, mDocument = aDocument; // Set the script global object on the new document - nsCOMPtr window = do_GetInterface(container); + nsCOMPtr window = + do_GetInterface(static_cast(mContainer.get())); if (window) { window->SetNewDocument(aDocument, nullptr, aForceReuseInnerWindow); } @@ -1712,7 +1715,7 @@ nsDocumentViewer::SetDocumentInternal(nsIDocument* aDocument, // Clear the list of old child docshells. Child docshells for the new // document will be constructed as frames are created. if (!aDocument->IsStaticDocument()) { - nsCOMPtr node = do_QueryInterface(container); + nsCOMPtr node(mContainer); if (node) { int32_t count; node->GetChildCount(&count); @@ -1888,7 +1891,7 @@ nsDocumentViewer::Show(void) prevViewer->Destroy(); // Make sure we don't have too many cached ContentViewers - nsCOMPtr treeItem = do_QueryReferent(mContainer); + nsCOMPtr treeItem(mContainer); if (treeItem) { // We need to find the root DocShell since only that object has an // SHistory and we need the SHistory to evict content viewers @@ -1924,7 +1927,7 @@ nsDocumentViewer::Show(void) if (mDocument && !mPresShell) { NS_ASSERTION(!mWindow, "Window already created but no presshell?"); - nsCOMPtr base_win(do_QueryReferent(mContainer)); + nsCOMPtr base_win(mContainer); if (base_win) { base_win->GetParentWidget(&mParentWidget); if (mParentWidget) { @@ -2014,7 +2017,7 @@ nsDocumentViewer::Hide(void) return NS_OK; } - nsCOMPtr docShell(do_QueryReferent(mContainer)); + nsCOMPtr docShell(mContainer); if (docShell) { nsCOMPtr layoutState; mPresShell->CaptureHistoryState(getter_AddRefs(layoutState)); @@ -2029,7 +2032,7 @@ nsDocumentViewer::Hide(void) mDeviceContext = nullptr; mParentWidget = nullptr; - nsCOMPtr base_win(do_QueryReferent(mContainer)); + nsCOMPtr base_win(mContainer); if (base_win && !mAttachedToParent) { base_win->SetParentWidget(nullptr); @@ -2114,7 +2117,7 @@ nsDocumentViewer::CreateStyleSet(nsIDocument* aDocument, bool shouldOverride = false; // We don't want a docshell here for external resource docs, so just // look at mContainer. - nsCOMPtr ds(do_QueryReferent(mContainer)); + nsCOMPtr ds(mContainer); nsCOMPtr chromeHandler; nsCOMPtr uri; nsRefPtr csssheet; @@ -2303,7 +2306,7 @@ nsDocumentViewer::FindContainerView() nsView* containerView = nullptr; if (mContainer) { - nsCOMPtr docShellItem = do_QueryReferent(mContainer); + nsCOMPtr docShellItem(mContainer); nsCOMPtr pwin(do_GetInterface(docShellItem)); if (pwin) { nsCOMPtr containerElement = do_QueryInterface(pwin->GetFrameElementInternal()); @@ -2500,7 +2503,7 @@ NS_IMETHODIMP nsDocumentViewer::CopyImage(int32_t aCopyFlags) // make noise if we're not in an image NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); - nsCOMPtr loadContext(do_QueryReferent(mContainer)); + nsCOMPtr loadContext(mContainer); return nsCopySupport::ImageCopy(node, loadContext, aCopyFlags); } @@ -2632,7 +2635,7 @@ NS_IMETHODIMP nsDocumentViewer::ScrollToNode(nsIDOMNode* aNode) void nsDocumentViewer::CallChildren(CallChildFunc aFunc, void* aClosure) { - nsCOMPtr docShellNode(do_QueryReferent(mContainer)); + nsCOMPtr docShellNode(mContainer); if (docShellNode) { int32_t i; @@ -3226,7 +3229,7 @@ nsDocumentViewer::GetContentSize(int32_t* aWidth, int32_t* aHeight) NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE); // Skip doing this on docshell-less documents for now - nsCOMPtr docShellAsItem(do_QueryReferent(mContainer)); + nsCOMPtr docShellAsItem(mContainer); NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_NOT_AVAILABLE); nsCOMPtr docShellParent; @@ -3554,7 +3557,7 @@ nsDocumentViewer::Print(nsIPrintSettings* aPrintSettings, return NS_ERROR_FAILURE; } - nsCOMPtr docShell(do_QueryReferent(mContainer)); + nsCOMPtr docShell(mContainer); NS_ENSURE_STATE(docShell); // Check to see if this document is still busy @@ -3664,7 +3667,7 @@ nsDocumentViewer::PrintPreview(nsIPrintSettings* aPrintSettings, } #endif - nsCOMPtr docShell(do_QueryReferent(mContainer)); + nsCOMPtr docShell(mContainer); if (!docShell || !mDeviceContext) { PR_PL(("Can't Print Preview without device context and docshell")); return NS_ERROR_FAILURE; @@ -4030,7 +4033,7 @@ nsDocumentViewer::ShouldAttachToTopLevel() if (!mParentWidget) return false; - nsCOMPtr containerItem = do_QueryReferent(mContainer); + nsCOMPtr containerItem(mContainer); if (!containerItem) return false; @@ -4098,7 +4101,7 @@ nsDocumentViewer::SetIsPrinting(bool aIsPrinting) #ifdef NS_PRINTING // Set all the docShells in the docshell tree to be printing. // that way if anyone of them tries to "navigate" it can't - nsCOMPtr docShellTreeNode(do_QueryReferent(mContainer)); + nsCOMPtr docShellTreeNode(mContainer); if (docShellTreeNode || !aIsPrinting) { SetIsPrintingInDocShellTree(docShellTreeNode, aIsPrinting, true); } else { @@ -4134,7 +4137,7 @@ nsDocumentViewer::SetIsPrintPreview(bool aIsPrintPreview) #ifdef NS_PRINTING // Set all the docShells in the docshell tree to be printing. // that way if anyone of them tries to "navigate" it can't - nsCOMPtr docShellTreeNode(do_QueryReferent(mContainer)); + nsCOMPtr docShellTreeNode(mContainer); if (docShellTreeNode || !aIsPrintPreview) { SetIsPrintingInDocShellTree(docShellTreeNode, aIsPrintPreview, true); } @@ -4208,7 +4211,7 @@ nsDocumentViewer::ReturnToGalleyPresentation() mPrintEngine->Destroy(); mPrintEngine = nullptr; - nsCOMPtr docShell(do_QueryReferent(mContainer)); + nsCOMPtr docShell(mContainer); ResetFocusState(docShell); SetTextZoom(mTextZoom); @@ -4247,8 +4250,8 @@ nsDocumentViewer::OnDonePrinting() // We are done printing, now cleanup if (mDeferredWindowClose) { mDeferredWindowClose = false; - nsCOMPtr container = do_QueryReferent(mContainer); - nsCOMPtr win = do_GetInterface(container); + nsCOMPtr win = + do_GetInterface(static_cast(mContainer)); if (win) win->Close(); } else if (mClosingWhilePrinting) { diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 82dfe56b5f47..07416d35f6c6 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1469,6 +1469,7 @@ nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent, aEvent->eventStructType != NS_WHEEL_EVENT && aEvent->eventStructType != NS_DRAG_EVENT && aEvent->eventStructType != NS_SIMPLE_GESTURE_EVENT && + aEvent->eventStructType != NS_POINTER_EVENT && aEvent->eventStructType != NS_GESTURENOTIFY_EVENT && aEvent->eventStructType != NS_TOUCH_EVENT && aEvent->eventStructType != NS_QUERY_CONTENT_EVENT)) diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index 2b54fe89ffd2..4b9ce74c275a 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -958,9 +958,7 @@ nsPresContext::Init(nsDeviceContext* aDeviceContext) "How did we get a presshell?"); // We don't have our container set yet at this point - nsCOMPtr ourContainer = mDocument->GetContainer(); - - nsCOMPtr ourItem = do_QueryInterface(ourContainer); + nsCOMPtr ourItem = mDocument->GetDocShell(); if (ourItem) { nsCOMPtr parentItem; ourItem->GetSameTypeParent(getter_AddRefs(parentItem)); diff --git a/layout/build/moz.build b/layout/build/moz.build index b0ba967db33a..89deba84c765 100644 --- a/layout/build/moz.build +++ b/layout/build/moz.build @@ -29,4 +29,7 @@ LIBRARY_NAME = 'gklayout' include('/ipc/chromium/chromium-config.mozbuild') +LOCAL_INCLUDES += [ + '/docshell/base', +] FINAL_LIBRARY = 'xul' diff --git a/layout/build/nsContentDLF.cpp b/layout/build/nsContentDLF.cpp index b2e58c46e10e..02c866ce8efc 100644 --- a/layout/build/nsContentDLF.cpp +++ b/layout/build/nsContentDLF.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsCOMPtr.h" #include "nsContentDLF.h" +#include "nsDocShell.h" #include "nsGenericHTMLElement.h" #include "nsGkAtoms.h" #include "nsIComponentManager.h" @@ -135,7 +136,7 @@ nsContentDLF::CreateInstance(const char* aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup, const char* aContentType, - nsISupports* aContainer, + nsIDocShell* aContainer, nsISupports* aExtraInfo, nsIStreamListener** aDocListener, nsIContentViewer** aDocViewer) @@ -370,7 +371,7 @@ nsresult nsContentDLF::CreateDocument(const char* aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup, - nsISupports* aContainer, + nsIDocShell* aContainer, const nsCID& aDocumentCID, nsIStreamListener** aDocListener, nsIContentViewer** aContentViewer) @@ -399,7 +400,7 @@ nsContentDLF::CreateDocument(const char* aCommand, rv = NS_NewContentViewer(getter_AddRefs(contentViewer)); NS_ENSURE_SUCCESS(rv, rv); - doc->SetContainer(aContainer); + doc->SetContainer(static_cast(aContainer)); // Initialize the document to begin loading the data. An // nsIStreamListener connected to the parser is returned in @@ -418,7 +419,7 @@ nsContentDLF::CreateXULDocument(const char* aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup, const char* aContentType, - nsISupports* aContainer, + nsIDocShell* aContainer, nsISupports* aExtraInfo, nsIStreamListener** aDocListener, nsIContentViewer** aContentViewer) @@ -442,7 +443,7 @@ nsContentDLF::CreateXULDocument(const char* aCommand, * aDocListener. */ - doc->SetContainer(aContainer); + doc->SetContainer(static_cast(aContainer)); rv = doc->StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer, aDocListener, true); if (NS_FAILED(rv)) return rv; diff --git a/layout/build/nsContentDLF.h b/layout/build/nsContentDLF.h index 327915cc7c35..e44d6b948d53 100644 --- a/layout/build/nsContentDLF.h +++ b/layout/build/nsContentDLF.h @@ -33,7 +33,7 @@ public: nsresult CreateDocument(const char* aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup, - nsISupports* aContainer, + nsIDocShell* aContainer, const nsCID& aDocumentCID, nsIStreamListener** aDocListener, nsIContentViewer** aContentViewer); @@ -42,7 +42,7 @@ public: nsIChannel* aChannel, nsILoadGroup* aLoadGroup, const char* aContentType, - nsISupports* aContainer, + nsIDocShell* aContainer, nsISupports* aExtraInfo, nsIStreamListener** aDocListener, nsIContentViewer** aContentViewer); diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp index d75992b0a1a4..3dfa4c59e59f 100644 --- a/layout/generic/nsSubDocumentFrame.cpp +++ b/layout/generic/nsSubDocumentFrame.cpp @@ -981,8 +981,7 @@ EndSwapDocShellsForDocument(nsIDocument* aDocument, void*) // Our docshell and view trees have been updated for the new hierarchy. // Now also update all nsDeviceContext::mWidget to that of the // container view in the new hierarchy. - nsCOMPtr container = aDocument->GetContainer(); - nsCOMPtr ds = do_QueryInterface(container); + nsCOMPtr ds = aDocument->GetDocShell(); if (ds) { nsCOMPtr cv; ds->GetContentViewer(getter_AddRefs(cv)); diff --git a/layout/inspector/tests/test_bug462787.html b/layout/inspector/tests/test_bug462787.html index 7f5ccd099a62..88c5c466f64a 100644 --- a/layout/inspector/tests/test_bug462787.html +++ b/layout/inspector/tests/test_bug462787.html @@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=462787 /** Test for Bug 462787 **/ function do_test() { - const INVALID_POINTER = 0x80004003; + const INVALID_POINTER = SpecialPowers.Cr.NS_ERROR_INVALID_POINTER; var utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] .getService(SpecialPowers.Ci.inIDOMUtils); diff --git a/layout/ipc/RenderFrameParent.h b/layout/ipc/RenderFrameParent.h index c5dd72de166b..f06ee52fba2f 100644 --- a/layout/ipc/RenderFrameParent.h +++ b/layout/ipc/RenderFrameParent.h @@ -221,6 +221,12 @@ public: NS_DISPLAY_DECL_NAME("Remote", TYPE_REMOTE) + virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) + { + return GetBounds(aBuilder, aSnap); + } + private: RenderFrameParent* mRemoteFrame; }; diff --git a/layout/printing/nsPrintEngine.cpp b/layout/printing/nsPrintEngine.cpp index f789c0f38785..31445e87face 100644 --- a/layout/printing/nsPrintEngine.cpp +++ b/layout/printing/nsPrintEngine.cpp @@ -293,7 +293,7 @@ void nsPrintEngine::DestroyPrintingData() //-------------------------------------------------------- nsresult nsPrintEngine::Initialize(nsIDocumentViewerPrint* aDocViewerPrint, - nsIWeakReference* aContainer, + nsIDocShell* aContainer, nsIDocument* aDocument, float aScreenDPI, FILE* aDebugFile) @@ -303,7 +303,7 @@ nsresult nsPrintEngine::Initialize(nsIDocumentViewerPrint* aDocViewerPrint, NS_ENSURE_ARG_POINTER(aDocument); mDocViewerPrint = aDocViewerPrint; - mContainer = aContainer; + mContainer = do_GetWeakReference(aContainer); mDocument = aDocument; mScreenDPI = aScreenDPI; @@ -1335,8 +1335,7 @@ nsPrintEngine::MapContentForPO(nsPrintObject* aPO, nsIDocument* subDoc = doc->GetSubDocumentFor(aContent); if (subDoc) { - nsCOMPtr container = subDoc->GetContainer(); - nsCOMPtr docShell(do_QueryInterface(container)); + nsCOMPtr docShell(subDoc->GetDocShell()); if (docShell) { nsPrintObject * po = nullptr; diff --git a/layout/printing/nsPrintEngine.h b/layout/printing/nsPrintEngine.h index 14e1680477ee..ec1b5c5daec8 100644 --- a/layout/printing/nsPrintEngine.h +++ b/layout/printing/nsPrintEngine.h @@ -82,7 +82,7 @@ public: void DestroyPrintingData(); nsresult Initialize(nsIDocumentViewerPrint* aDocViewerPrint, - nsIWeakReference* aContainer, + nsIDocShell* aContainer, nsIDocument* aDocument, float aScreenDPI, FILE* aDebugFile); diff --git a/layout/reftests/bugs/935056-1-ref.html b/layout/reftests/bugs/935056-1-ref.html deleted file mode 100644 index 94cacf33bfc7..000000000000 --- a/layout/reftests/bugs/935056-1-ref.html +++ /dev/null @@ -1,10 +0,0 @@ - - - -

This text should be affected by the minimum font size.

- - This text should not be affected by the minimum font size. - diff --git a/layout/reftests/bugs/935056-1.html b/layout/reftests/bugs/935056-1.html deleted file mode 100644 index 940624deda6c..000000000000 --- a/layout/reftests/bugs/935056-1.html +++ /dev/null @@ -1,9 +0,0 @@ - - - -

This text should be affected by the minimum font size.

- - This text should not be affected by the minimum font size. - diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index b1a108dcf8b3..c3d7a62e454c 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1780,7 +1780,6 @@ fuzzy(1,10000) fuzzy-if(Android&&AndroidVersion>=15,5,10000) == 902330-1.html 90 fuzzy-if(Android,8,400) == 906199-1.html 906199-1-ref.html == 921716-1.html 921716-1-ref.html fuzzy-if(cocoaWidget,1,40) == 928607-1.html 928607-1-ref.html -test-pref(font.minimum-size.x-western,32) == 935056-1.html 935056-1-ref.html == 936670-1.svg 936670-1-ref.svg == 931853.html 931853-ref.html == 931853-quirks.html 931853-quirks-ref.html diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp index b16afe4751e3..7e4de5d0972c 100644 --- a/layout/style/Loader.cpp +++ b/layout/style/Loader.cpp @@ -48,6 +48,7 @@ #include "nsIThreadInternal.h" #include "nsCrossSiteListenerProxy.h" #include "nsINetworkSeer.h" +#include "mozilla/dom/URL.h" #ifdef MOZ_XUL #include "nsXULPrototypeCache.h" @@ -942,16 +943,6 @@ SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader, return result; } -#ifdef MOZ_XUL -static bool IsChromeURI(nsIURI* aURI) -{ - NS_ASSERTION(aURI, "Have to pass in a URI"); - bool isChrome = false; - aURI->SchemeIs("chrome", &isChrome); - return isChrome; -} -#endif - bool Loader::IsAlternate(const nsAString& aTitle, bool aHasAlternateRel) { diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 6ee5359d85c9..92227cd7ab57 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -8525,6 +8525,16 @@ CSSParserImpl::ParseCounterData(nsCSSProperty aPropID) nsCSSValuePairList *cur = value.SetPairListValue(); for (;;) { + // check for "none", "default" and the CSS-wide keywords, + // which can't be used as counter names + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); + if (keyword == eCSSKeyword_inherit || + keyword == eCSSKeyword_default || + keyword == eCSSKeyword_none || + keyword == eCSSKeyword_unset || + keyword == eCSSKeyword_initial) { + return false; + } cur->mXValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident); if (!GetToken(true)) { break; diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 8ccf84b82c17..5a121ece5a2b 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -672,10 +672,15 @@ nsComputedDOMStyle::GetPropertyCSSValue(const nsAString& aPropertyName, ErrorRes while (topWithPseudoElementData->GetParent()->HasPseudoElementData()) { topWithPseudoElementData = topWithPseudoElementData->GetParent(); } - NS_ASSERTION(nsCSSPseudoElements::PseudoElementContainsElements( - topWithPseudoElementData->GetPseudoType()), - "we should be in a pseudo-element that is expected to " - "contain elements"); + nsCSSPseudoElements::Type pseudo = + topWithPseudoElementData->GetPseudoType(); + nsIAtom* pseudoAtom = nsCSSPseudoElements::GetPseudoAtom(pseudo); + nsAutoString assertMsg( + NS_LITERAL_STRING("we should be in a pseudo-element that is expected to contain elements (")); + assertMsg.Append(nsDependentString(pseudoAtom->GetUTF16String())); + assertMsg.Append(NS_LITERAL_STRING(")")); + NS_ASSERTION(nsCSSPseudoElements::PseudoElementContainsElements(pseudo), + NS_LossyConvertUTF16toASCII(assertMsg).get()); } #endif // Need to resolve a style context diff --git a/layout/style/nsFontFaceLoader.cpp b/layout/style/nsFontFaceLoader.cpp index a5a043c09acf..bbd7ddbdf805 100644 --- a/layout/style/nsFontFaceLoader.cpp +++ b/layout/style/nsFontFaceLoader.cpp @@ -876,15 +876,13 @@ nsUserFontSet::CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc, } *aBypassCache = false; - nsCOMPtr container = ps->GetDocument()->GetContainer(); - if (container) { - nsCOMPtr docShell = do_QueryInterface(container); - if (docShell) { - uint32_t loadType; - if (NS_SUCCEEDED(docShell->GetLoadType(&loadType))) { - if ((loadType >> 16) & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) { - *aBypassCache = true; - } + + nsCOMPtr docShell = ps->GetDocument()->GetDocShell(); + if (docShell) { + uint32_t loadType; + if (NS_SUCCEEDED(docShell->GetLoadType(&loadType))) { + if ((loadType >> 16) & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) { + *aBypassCache = true; } } } @@ -982,11 +980,6 @@ nsUserFontSet::GetPrivateBrowsing() return false; } - nsCOMPtr container = ps->GetDocument()->GetContainer(); - if (!container) { - return false; - } - - nsCOMPtr loadContext = do_QueryInterface(container); + nsCOMPtr loadContext = ps->GetDocument()->GetLoadContext(); return loadContext && loadContext->UsePrivateBrowsing(); } diff --git a/layout/style/nsROCSSPrimitiveValue.cpp b/layout/style/nsROCSSPrimitiveValue.cpp index ec5dc4df2db2..1380eb6b3399 100644 --- a/layout/style/nsROCSSPrimitiveValue.cpp +++ b/layout/style/nsROCSSPrimitiveValue.cpp @@ -89,7 +89,7 @@ nsROCSSPrimitiveValue::GetCssText(nsAString& aCssText) case CSS_PX : { float val = nsPresContext::AppUnitsToFloatCSSPixels(mValue.mAppUnits); - tmpStr.AppendFloat(val); + nsStyleUtil::AppendCSSNumber(val, tmpStr); tmpStr.AppendLiteral("px"); break; } @@ -133,36 +133,36 @@ nsROCSSPrimitiveValue::GetCssText(nsAString& aCssText) } case CSS_PERCENTAGE : { - tmpStr.AppendFloat(mValue.mFloat * 100); + nsStyleUtil::AppendCSSNumber(mValue.mFloat * 100, tmpStr); tmpStr.Append(PRUnichar('%')); break; } case CSS_NUMBER : { - tmpStr.AppendFloat(mValue.mFloat); + nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr); break; } case CSS_DEG : { - tmpStr.AppendFloat(mValue.mFloat); + nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr); tmpStr.AppendLiteral("deg"); break; } case CSS_GRAD : { - tmpStr.AppendFloat(mValue.mFloat); + nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr); tmpStr.AppendLiteral("grad"); break; } case CSS_RAD : { - tmpStr.AppendFloat(mValue.mFloat); + nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr); tmpStr.AppendLiteral("rad"); break; } case CSS_TURN : { - tmpStr.AppendFloat(mValue.mFloat); + nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr); tmpStr.AppendLiteral("turn"); break; } @@ -250,7 +250,7 @@ nsROCSSPrimitiveValue::GetCssText(nsAString& aCssText) } case CSS_S : { - tmpStr.AppendFloat(mValue.mFloat); + nsStyleUtil::AppendCSSNumber(mValue.mFloat, tmpStr); tmpStr.AppendLiteral("s"); break; } diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 9d95887bd0c9..9734ab94918a 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -3554,15 +3554,9 @@ nsRuleNode::SetFont(nsPresContext* aPresContext, nsStyleContext* aContext, nscoord fontSize = aFont->mSize; - // Enforce the user's specified minimum font-size on the value that we - // expose (but don't change font-size:0, since that would unhide hidden - // text). - // - // We don't do this for SVG text, which we can tell from the value of - // -x-text-zoom. It's safe to look at mAllowZoom on this style struct - // we're computing, since we know -x-text-zoom is only ever specified - // on an element and is inherited otherwise. - if (fontSize > 0 && aFont->mAllowZoom) { + // enforce the user' specified minimum font-size on the value that we expose + // (but don't change font-size:0, since that would unhide hidden text) + if (fontSize > 0) { nscoord minFontSize = aPresContext->MinFontSize(aFont->mLanguage); if (minFontSize < 0) { minFontSize = 0; diff --git a/layout/style/nsStyleUtil.cpp b/layout/style/nsStyleUtil.cpp index faa268f238fe..a3045156871c 100644 --- a/layout/style/nsStyleUtil.cpp +++ b/layout/style/nsStyleUtil.cpp @@ -163,13 +163,8 @@ nsStyleUtil::AppendAngleValue(const nsStyleCoord& aAngle, nsAString& aResult) { MOZ_ASSERT(aAngle.IsAngleValue(), "Should have angle value"); - nsROCSSPrimitiveValue tmpVal; - nsAutoString tokenString; - // Append number. - tmpVal.SetNumber(aAngle.GetAngleValue()); - tmpVal.GetCssText(tokenString); - aResult.Append(tokenString); + AppendCSSNumber(aAngle.GetAngleValue(), aResult); // Append unit. switch (aAngle.GetUnit()) { diff --git a/layout/style/nsStyleUtil.h b/layout/style/nsStyleUtil.h index 8e0aa3b3b02f..ebf172ce5fbb 100644 --- a/layout/style/nsStyleUtil.h +++ b/layout/style/nsStyleUtil.h @@ -57,6 +57,11 @@ public: static void AppendFontFeatureSettings(const nsCSSValue& src, nsAString& aResult); + static void AppendCSSNumber(float aNumber, nsAString& aResult) + { + aResult.AppendFloat(aNumber); + } + // convert bitmask value to keyword name for a functional alternate static void GetFunctionalAlternatesName(int32_t aFeature, nsAString& aFeatureName); diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index 91d8e897ebb3..97de06a1964c 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -2321,7 +2321,7 @@ var gCSSProperties = { type: CSS_TYPE_LONGHAND, initial_values: [ "none" ], other_values: [ "foo 1", "bar", "foo 3 bar baz 2", "\\32 1", "-\\32 1", "-c 1", "\\32 1", "-\\32 1", "\\2 1", "-\\2 1", "-c 1", "\\2 1", "-\\2 1", "-\\7f \\9e 1" ], - invalid_values: [] + invalid_values: [ "none foo", "none foo 3", "foo none", "foo 3 none" ] }, "counter-reset": { domProp: "counterReset", @@ -2329,7 +2329,7 @@ var gCSSProperties = { type: CSS_TYPE_LONGHAND, initial_values: [ "none" ], other_values: [ "foo 1", "bar", "foo 3 bar baz 2", "\\32 1", "-\\32 1", "-c 1", "\\32 1", "-\\32 1", "\\2 1", "-\\2 1", "-c 1", "\\2 1", "-\\2 1", "-\\7f \\9e 1" ], - invalid_values: [] + invalid_values: [ "none foo", "none foo 3", "foo none", "foo 3 none" ] }, "cursor": { domProp: "cursor", diff --git a/layout/xul/base/src/nsContainerBoxObject.cpp b/layout/xul/base/src/nsContainerBoxObject.cpp index 2e5e1aa43870..9bcac0236ff0 100644 --- a/layout/xul/base/src/nsContainerBoxObject.cpp +++ b/layout/xul/base/src/nsContainerBoxObject.cpp @@ -80,13 +80,8 @@ NS_IMETHODIMP nsContainerBoxObject::GetDocShell(nsIDocShell** aResult) return NS_OK; } - nsCOMPtr container = sub_doc->GetContainer(); - - if (!container) { - return NS_OK; - } - - return CallQueryInterface(container, aResult); + NS_IF_ADDREF(*aResult = sub_doc->GetDocShell()); + return NS_OK; } nsresult diff --git a/layout/xul/base/src/nsXULPopupManager.cpp b/layout/xul/base/src/nsXULPopupManager.cpp index 919e7711c535..e666547c46d3 100644 --- a/layout/xul/base/src/nsXULPopupManager.cpp +++ b/layout/xul/base/src/nsXULPopupManager.cpp @@ -1061,8 +1061,7 @@ nsXULPopupManager::HidePopupsInList(const nsTArray &aFrames, bool nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected) { - nsCOMPtr doc = aDoc->GetContainer(); - nsCOMPtr docShellItem(do_QueryInterface(doc)); + nsCOMPtr docShellItem(aDoc->GetDocShell()); while(docShellItem) { if (docShellItem == aExpected) return true; diff --git a/media/libcubeb/README_MOZILLA b/media/libcubeb/README_MOZILLA index 1ce67e883d8b..28678f96ed46 100644 --- a/media/libcubeb/README_MOZILLA +++ b/media/libcubeb/README_MOZILLA @@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system. The cubeb git repository is: git://github.com/kinetiknz/cubeb.git -The git commit ID used was 93e51e70e978420c745ec22503fa8e121cbb7aa5. +The git commit ID used was 8c78a282aa0320e997436d6832024efe1527ca1c. diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c index f02b235704d2..217e13be2e05 100644 --- a/media/libcubeb/src/cubeb_pulse.c +++ b/media/libcubeb/src/cubeb_pulse.c @@ -452,7 +452,6 @@ pulse_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n cubeb_stream * stm; pa_operation * o; pa_buffer_attr battr; - pa_channel_map map; int r; assert(context); @@ -479,9 +478,6 @@ pulse_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n ss.rate = stream_params.rate; ss.channels = stream_params.channels; - /* XXX check that this does the right thing for Vorbis and WaveEx */ - WRAP(pa_channel_map_init_auto)(&map, ss.channels, PA_CHANNEL_MAP_DEFAULT); - stm = calloc(1, sizeof(*stm)); assert(stm); @@ -500,7 +496,11 @@ pulse_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n battr.fragsize = -1; WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); - stm->stream = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &map); + stm->stream = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL); + if (!stm->stream) { + pulse_stream_destroy(stm); + return CUBEB_ERROR; + } WRAP(pa_stream_set_state_callback)(stm->stream, stream_state_callback, stm); WRAP(pa_stream_set_write_callback)(stm->stream, stream_request_callback, stm); WRAP(pa_stream_connect_playback)(stm->stream, NULL, &battr, diff --git a/media/libnestegg/README_MOZILLA b/media/libnestegg/README_MOZILLA index ecfdfe06cc44..17dafbb5a27f 100644 --- a/media/libnestegg/README_MOZILLA +++ b/media/libnestegg/README_MOZILLA @@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system. The nestegg git repository is: git://github.com/kinetiknz/nestegg.git -The git commit ID used was 26d262114af191b6654cbd5d24ec02e4b6bdb1cd. +The git commit ID used was 0851279ab11f5b2e9e8154ce7880b687b564c760. diff --git a/media/libnestegg/include/nestegg.h b/media/libnestegg/include/nestegg.h index 6f307aa398bf..ff13728f32a3 100644 --- a/media/libnestegg/include/nestegg.h +++ b/media/libnestegg/include/nestegg.h @@ -140,6 +140,8 @@ typedef struct { double rate; /**< Sampling rate in Hz. */ unsigned int channels; /**< Number of audio channels. */ unsigned int depth; /**< Bits per sample. */ + uint64_t codec_delay; /**< Nanoseconds that must be discarded from the start. */ + uint64_t seek_preroll;/**< Nanoseconds that must be discarded after a seek. */ } nestegg_audio_params; /** Logging callback function pointer. */ @@ -321,6 +323,14 @@ int nestegg_packet_count(nestegg_packet * packet, unsigned int * count); int nestegg_packet_data(nestegg_packet * packet, unsigned int item, unsigned char ** data, size_t * length); +/** Returns discard_padding for given packet + @param packet Packet initialized by #nestegg_read_packet. + @param discard_padding pointer to store discard padding in. + @retval 0 Success. + @retval -1 Error. */ +int nestegg_packet_discard_padding(nestegg_packet * packet, + int64_t * discard_padding); + /** Query the presence of cues. @param context Stream context initialized by #nestegg_init. @retval 0 The media has no cues. diff --git a/media/libnestegg/src/nestegg.c b/media/libnestegg/src/nestegg.c index b3bc9cb6dd0c..cd609c9e7b62 100644 --- a/media/libnestegg/src/nestegg.c +++ b/media/libnestegg/src/nestegg.c @@ -49,6 +49,7 @@ #define ID_BLOCK 0xa1 #define ID_BLOCK_DURATION 0x9b #define ID_REFERENCE_BLOCK 0xfb +#define ID_DISCARD_PADDING 0x75a2 /* Tracks Elements */ #define ID_TRACKS 0x1654ae6b @@ -63,6 +64,8 @@ #define ID_LANGUAGE 0x22b59c #define ID_CODEC_ID 0x86 #define ID_CODEC_PRIVATE 0x63a2 +#define ID_CODEC_DELAY 0x56aa +#define ID_SEEK_PREROLL 0x56bb /* Video Elements */ #define ID_VIDEO 0xe0 @@ -194,6 +197,7 @@ struct info { struct block_group { struct ebml_type duration; struct ebml_type reference_block; + struct ebml_type discard_padding; }; struct cluster { @@ -230,6 +234,8 @@ struct track_entry { struct ebml_type language; struct ebml_type codec_id; struct ebml_type codec_private; + struct ebml_type codec_delay; + struct ebml_type seek_preroll; struct video video; struct audio audio; }; @@ -305,6 +311,7 @@ struct nestegg_packet { uint64_t track; uint64_t timecode; struct frame * frame; + int64_t discard_padding; }; /* Element Descriptor */ @@ -368,6 +375,7 @@ static struct ebml_element_desc ne_block_group_elements[] = { E_SUSPEND(ID_BLOCK, TYPE_BINARY), E_FIELD(ID_BLOCK_DURATION, TYPE_UINT, struct block_group, duration), E_FIELD(ID_REFERENCE_BLOCK, TYPE_INT, struct block_group, reference_block), + E_FIELD(ID_DISCARD_PADDING, TYPE_INT, struct block_group, discard_padding), E_LAST }; @@ -409,6 +417,8 @@ static struct ebml_element_desc ne_track_entry_elements[] = { E_FIELD(ID_LANGUAGE, TYPE_STRING, struct track_entry, language), E_FIELD(ID_CODEC_ID, TYPE_STRING, struct track_entry, codec_id), E_FIELD(ID_CODEC_PRIVATE, TYPE_BINARY, struct track_entry, codec_private), + E_FIELD(ID_CODEC_DELAY, TYPE_UINT, struct track_entry, codec_delay), + E_FIELD(ID_SEEK_PREROLL, TYPE_UINT, struct track_entry, seek_preroll), E_SINGLE_MASTER(ID_VIDEO, TYPE_MASTER, struct track_entry, video), E_SINGLE_MASTER(ID_AUDIO, TYPE_MASTER, struct track_entry, audio), E_LAST @@ -1365,6 +1375,35 @@ ne_read_block(nestegg * ctx, uint64_t block_id, uint64_t block_size, nestegg_pac return 1; } +static int +ne_read_discard_padding(nestegg * ctx, nestegg_packet * pkt) +{ + int r; + uint64_t id, size; + struct ebml_element_desc * element; + struct ebml_type * storage; + + r = ne_peek_element(ctx, &id, &size); + if (r != 1) + return r; + + if (id != ID_DISCARD_PADDING) + return 1; + + element = ne_find_element(id, ctx->ancestor->node); + if (!element) + return 1; + + r = ne_read_simple(ctx, element, size); + if (r != 1) + return r; + storage = (struct ebml_type *) (ctx->ancestor->data + element->offset); + pkt->discard_padding = storage->v.i; + + return 1; +} + + static uint64_t ne_buf_read_id(unsigned char const * p, size_t length) { @@ -1994,34 +2033,40 @@ nestegg_track_codec_data(nestegg * ctx, unsigned int track, unsigned int item, if (!entry) return -1; - if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS) + if (nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_VORBIS + && nestegg_track_codec_id(ctx, track) != NESTEGG_CODEC_OPUS) return -1; if (ne_get_binary(entry->codec_private, &codec_private) != 0) return -1; - p = codec_private.data; - count = *p++ + 1; + if (nestegg_track_codec_id(ctx, track) == NESTEGG_CODEC_VORBIS) { + p = codec_private.data; + count = *p++ + 1; - if (count > 3) - return -1; + if (count > 3) + return -1; - i = 0; - total = 0; - while (--count) { - sizes[i] = ne_xiph_lace_value(&p); - total += sizes[i]; - i += 1; + i = 0; + total = 0; + while (--count) { + sizes[i] = ne_xiph_lace_value(&p); + total += sizes[i]; + i += 1; + } + sizes[i] = codec_private.length - total - (p - codec_private.data); + + for (i = 0; i < item; ++i) { + if (sizes[i] > LIMIT_FRAME) + return -1; + p += sizes[i]; + } + *data = p; + *length = sizes[item]; + } else { + *data = codec_private.data; + *length = codec_private.length; } - sizes[i] = codec_private.length - total - (p - codec_private.data); - - for (i = 0; i < item; ++i) { - if (sizes[i] > LIMIT_FRAME) - return -1; - p += sizes[i]; - } - *data = p; - *length = sizes[item]; return 0; } @@ -2110,6 +2155,14 @@ nestegg_track_audio_params(nestegg * ctx, unsigned int track, ne_get_uint(entry->audio.bit_depth, &value); params->depth = value; + value = 0; + ne_get_uint(entry->codec_delay, &value); + params->codec_delay = value; + + value = 0; + ne_get_uint(entry->seek_preroll, &value); + params->seek_preroll = value; + return 0; } @@ -2135,6 +2188,13 @@ nestegg_read_packet(nestegg * ctx, nestegg_packet ** pkt) /* The only DESC_FLAG_SUSPEND fields are Blocks and SimpleBlocks, which we handle directly. */ r = ne_read_block(ctx, id, size, pkt); + if (r != 1) + return r; + + r = ne_read_discard_padding(ctx, *pkt); + if (r != 1) + return r; + return r; } @@ -2175,6 +2235,13 @@ nestegg_packet_tstamp(nestegg_packet * pkt, uint64_t * tstamp) return 0; } +int +nestegg_packet_discard_padding(nestegg_packet * pkt, int64_t * discard_padding) +{ + *discard_padding = pkt->discard_padding; + return 0; +} + int nestegg_packet_count(nestegg_packet * pkt, unsigned int * count) { diff --git a/media/mtransport/nricectx.cpp b/media/mtransport/nricectx.cpp index 6efdb175c633..e3bb621110d5 100644 --- a/media/mtransport/nricectx.cpp +++ b/media/mtransport/nricectx.cpp @@ -675,7 +675,7 @@ void NrIceCtx::SetState(State state) { if (state == state_) return; - MOZ_MTLOG(ML_DEBUG, "NrIceCtx(" << name_ << "): state " << + MOZ_MTLOG(ML_INFO, "NrIceCtx(" << name_ << "): state " << state_ << "->" << state); state_ = state; diff --git a/memory/build/moz.build b/memory/build/moz.build index f933a886a9c4..034f261adfef 100644 --- a/memory/build/moz.build +++ b/memory/build/moz.build @@ -14,18 +14,18 @@ if CONFIG['MOZ_REPLACE_MALLOC']: 'malloc_decls.h', 'replace_malloc.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'jemalloc_config.c', 'mozmemory_wrap.c', ] if CONFIG['MOZ_JEMALLOC3']: - SOURCES += [ + UNIFIED_SOURCES += [ 'mozjemalloc_compat.c', ] if CONFIG['MOZ_REPLACE_MALLOC']: - SOURCES += [ + UNIFIED_SOURCES += [ 'replace_malloc.c', ] diff --git a/memory/jemalloc/moz.build b/memory/jemalloc/moz.build index b40f981b4506..3e246c9fa326 100644 --- a/memory/jemalloc/moz.build +++ b/memory/jemalloc/moz.build @@ -4,7 +4,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -SOURCES += [ +UNIFIED_SOURCES += [ 'src/src/arena.c', 'src/src/atomic.c', 'src/src/base.c', @@ -32,7 +32,7 @@ SOURCES += [ # Only OSX needs the zone allocation implementation, # but only if replace-malloc is not enabled. if CONFIG['OS_TARGET'] == 'Darwin' and not CONFIG['MOZ_REPLACE_MALLOC']: - SOURCES += [ + UNIFIED_SOURCES += [ 'src/src/zone.c', ] diff --git a/memory/mozalloc/moz.build b/memory/mozalloc/moz.build index 0f20f6d51ace..99cfff9630aa 100644 --- a/memory/mozalloc/moz.build +++ b/memory/mozalloc/moz.build @@ -32,7 +32,7 @@ if CONFIG['WRAP_STL_INCLUDES']: 'msvc_throw_wrapper.cpp', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'mozalloc.cpp', 'mozalloc_abort.cpp', 'mozalloc_oom.cpp', diff --git a/mobile/android/base/ANRReporter.java b/mobile/android/base/ANRReporter.java index 8eaf69fa2654..05a87eec5ca1 100644 --- a/mobile/android/base/ANRReporter.java +++ b/mobile/android/base/ANRReporter.java @@ -29,8 +29,6 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; -import java.security.GeneralSecurityException; -import java.security.MessageDigest; import java.util.UUID; import java.util.regex.Pattern; @@ -48,12 +46,8 @@ public final class ANRReporter extends BroadcastReceiver private static final int TRACES_LINE_SIZE = 100; // Size of block to use when processing traces.txt private static final int TRACES_BLOCK_SIZE = 2000; - // we use us-ascii here because when telemetry calculates checksum - // for the ping file, it lossily converts utf-16 to ascii. therefore, - // we have to treat characters in the traces file as ascii rather than - // say utf-8. otherwise, we will get a wrong checksum - private static final String TRACES_CHARSET = "us-ascii"; - private static final String PING_CHARSET = "us-ascii"; + private static final String TRACES_CHARSET = "utf-8"; + private static final String PING_CHARSET = "utf-8"; private static final ANRReporter sInstance = new ANRReporter(); private static int sRegisteredCount; @@ -266,11 +260,11 @@ public final class ANRReporter extends BroadcastReceiver /* a saved telemetry ping file consists of JSON in the following format, { + "reason": "android-anr-report", "slug": "", - "payload": "", - "checksum": "" + "payload": } - for Android ANR, our unescaped JSON payload should look like, + for Android ANR, our JSON payload should look like, { "ver": 1, "simpleMeasurements": { @@ -287,32 +281,27 @@ public final class ANRReporter extends BroadcastReceiver */ private static int writePingPayload(OutputStream ping, - MessageDigest checksum, String payload) throws IOException { - byte [] data = payload.getBytes(PING_CHARSET); - checksum.update(data); - - data = JSONObject.quote(payload).getBytes(PING_CHARSET); - // first and last bytes are quotes inserted by JSONObject.quote; discard them - ping.write(data, 1, data.length - 2); - return data.length - 2; + ping.write(data); + return data.length; } - private static void fillPingHeader(OutputStream ping, MessageDigest checksum, String slug) + private static void fillPingHeader(OutputStream ping, String slug) throws IOException { // ping file header byte [] data = ("{" + + "\"reason\":\"android-anr-report\"," + "\"slug\":" + JSONObject.quote(slug) + "," + - "\"payload\":\"").getBytes(PING_CHARSET); + "\"payload\":").getBytes(PING_CHARSET); ping.write(data); if (DEBUG) { Log.d(LOGTAG, "wrote ping header, size = " + String.valueOf(data.length)); } // payload start - int size = writePingPayload(ping, checksum, ("{" + + int size = writePingPayload(ping, ("{" + "\"ver\":1," + "\"simpleMeasurements\":{" + "\"uptime\":" + String.valueOf(getUptimeMins()) + @@ -392,9 +381,9 @@ public final class ANRReporter extends BroadcastReceiver return 0; } - // Copy the content of reader to ping and update checksum; + // Copy the content of reader to ping; // copying stops when endPattern is found in the input stream - private static int fillPingBlock(OutputStream ping, MessageDigest checksum, + private static int fillPingBlock(OutputStream ping, Reader reader, String endPattern) throws IOException { @@ -409,7 +398,7 @@ public final class ANRReporter extends BroadcastReceiver stringBlock = stringBlock.substring(0, endIndex); } String quoted = JSONObject.quote(stringBlock); - total += writePingPayload(ping, checksum, quoted.substring(1, quoted.length() - 1)); + total += writePingPayload(ping, quoted.substring(1, quoted.length() - 1)); if (endIndex > 0) { // End pattern already found; return now break; @@ -418,13 +407,13 @@ public final class ANRReporter extends BroadcastReceiver return total; } - private static void fillPingFooter(OutputStream ping, MessageDigest checksum, + private static void fillPingFooter(OutputStream ping, boolean haveNativeStack) throws IOException { // We are at the end of ANR data - int total = writePingPayload(ping, checksum, ("\"," + + int total = writePingPayload(ping, ("\"," + "\"androidLogcat\":\"")); try { @@ -435,7 +424,7 @@ public final class ANRReporter extends BroadcastReceiver .start(); try { Reader procOut = new InputStreamReader(proc.getInputStream(), TRACES_CHARSET); - int size = fillPingBlock(ping, checksum, procOut, null); + int size = fillPingBlock(ping, procOut, null); if (DEBUG) { Log.d(LOGTAG, "wrote logcat, size = " + String.valueOf(size)); } @@ -448,25 +437,20 @@ public final class ANRReporter extends BroadcastReceiver } if (haveNativeStack) { - total += writePingPayload(ping, checksum, ("\"," + - "\"androidNativeStack\":\"")); + total += writePingPayload(ping, ("\"," + + "\"androidNativeStack\":")); String nativeStack = String.valueOf(getNativeStack()); - int size = fillPingBlock(ping, checksum, new StringReader(nativeStack), null); + int size = writePingPayload(ping, nativeStack); if (DEBUG) { Log.d(LOGTAG, "wrote native stack, size = " + String.valueOf(size)); } + total += size + writePingPayload(ping, "}"); + } else { + total += writePingPayload(ping, "\"}"); } - total += writePingPayload(ping, checksum, "\"}"); - - String base64Checksum = Base64.encodeToString(checksum.digest(), Base64.NO_WRAP); - if (DEBUG) { - Log.d(LOGTAG, "checksum: " + base64Checksum); - } byte [] data = ( - "\"," + - "\"checksum\":" + JSONObject.quote(base64Checksum) + "}").getBytes(PING_CHARSET); ping.write(data); if (DEBUG) { @@ -481,8 +465,7 @@ public final class ANRReporter extends BroadcastReceiver OutputStream ping = new BufferedOutputStream( new FileOutputStream(pingFile), TRACES_BLOCK_SIZE); try { - MessageDigest checksum = MessageDigest.getInstance("SHA-256"); - fillPingHeader(ping, checksum, pingFile.getName()); + fillPingHeader(ping, pingFile.getName()); // Traces file has the format // ----- pid xxx at xxx ----- // Cmd line: org.mozilla.xxx @@ -494,11 +477,11 @@ public final class ANRReporter extends BroadcastReceiver // ... // If we end the stack dump at the first end marker, // only Fennec stacks will be dumped - int size = fillPingBlock(ping, checksum, traces, "\n----- end"); + int size = fillPingBlock(ping, traces, "\n----- end"); if (DEBUG) { Log.d(LOGTAG, "wrote traces, size = " + String.valueOf(size)); } - fillPingFooter(ping, checksum, haveNativeStack); + fillPingFooter(ping, haveNativeStack); if (DEBUG) { Log.d(LOGTAG, "finished creating ping file"); } @@ -509,8 +492,6 @@ public final class ANRReporter extends BroadcastReceiver releaseNativeStack(); } } - } catch (GeneralSecurityException e) { - Log.w(LOGTAG, e); } catch (IOException e) { Log.w(LOGTAG, e); } diff --git a/mobile/android/base/gfx/GLController.java b/mobile/android/base/gfx/GLController.java index 274a98fe0fa7..780faa6d3244 100644 --- a/mobile/android/base/gfx/GLController.java +++ b/mobile/android/base/gfx/GLController.java @@ -153,6 +153,10 @@ public class GLController { return mServerSurfaceValid; } + public boolean isCompositorCreated() { + return mCompositorCreated; + } + private void initEGL() { if (mEGL != null) { return; diff --git a/mobile/android/base/gfx/LayerView.java b/mobile/android/base/gfx/LayerView.java index cc4c7c3b7452..862c48f44cc8 100644 --- a/mobile/android/base/gfx/LayerView.java +++ b/mobile/android/base/gfx/LayerView.java @@ -520,8 +520,13 @@ public class LayerView extends FrameLayout implements Tabs.OnTabsChangedListener * TextureView instead of a SurfaceView, the first phase is skipped. */ private void onSizeChanged(int width, int height) { - if (!mGLController.isServerSurfaceValid() || mSurfaceView == null) { - surfaceChanged(width, height); + if (!mGLController.isCompositorCreated()) { + return; + } + + surfaceChanged(width, height); + + if (mSurfaceView == null) { return; } diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index a410fdde3a65..cc4b791c6bbb 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -196,6 +196,17 @@ pref("media.windows-media-foundation.play-stand-alone", true); #ifdef MOZ_DIRECTSHOW pref("media.directshow.enabled", true); #endif +#ifdef MOZ_FMP4 +pref("media.fragmented-mp4.enabled", true); +// Denotes that the fragmented MP4 parser can be created by