diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index c48c8a7c38e0..74298a847fce 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index a10becb547fa..17ac2244564f 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 68e3e4076485..3caeb388dbe8 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 0545c5713601..2d389e0ae947 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,10 +17,10 @@ - + - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 4eb62fb93c10..13ad7488a4e4 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 17395660e8dd..af1ab43bb82c 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 68e3e4076485..3caeb388dbe8 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 927bb82b89be..a6343a12b764 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 6d1ef0823b8a..800f35be2a8e 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "39b121515ab8a8c3ea07f26d3ba1dd792e90217c", + "git_revision": "4b09b8824e3c68d8f5208a53f9ae3a8917dc9560", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "545d4c6c1224394885c502aa863f5fc95268a3f2", + "revision": "74ba42b610e520caa4e67264d52ef3c30d3193ae", "repo_path": "integration/gaia-central" } diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 8a44b7e8b6b3..5e90049cd3f0 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -17,10 +17,10 @@ - + - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 2047450b9cda..1efc205e7f46 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index d885463a1831..71ba6f3fb991 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -1186,7 +1186,7 @@ nsContextMenu.prototype = { // Save URL of clicked-on frame. saveFrame: function () { - saveDocument(this.target.ownerDocument); + saveBrowser(this.browser, false, this.frameOuterWindowID); }, // Helper function to wait for appropriate MIME-type headers and diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini index a73788904159..0a33f5249cc7 100644 --- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -55,6 +55,8 @@ support-files = file_mixedContentFromOnunload.html file_mixedContentFromOnunload_test1.html file_mixedContentFromOnunload_test2.html + file_mixedContentFramesOnHttp.html + file_mixedPassiveContent.html file_bug970276_popup1.html file_bug970276_popup2.html file_bug970276_favicon1.ico @@ -271,6 +273,9 @@ tags = mcb tags = mcb skip-if = buildapp == "mulet" || e10s # Bug 1093642 - test manipulates content and relies on content focus [browser_mixedContentFromOnunload.js] +tags = mcb +[browser_mixedContentFramesOnHttp.js] +tags = mcb [browser_bug970746.js] [browser_bug1015721.js] skip-if = os == 'win' || e10s # Bug 1159268 - Need a content-process safe version of synthesizeWheel @@ -488,6 +493,7 @@ skip-if = buildapp == 'mulet' skip-if = e10s # Bug 1094240 - has findbar-related failures [browser_registerProtocolHandler_notification.js] [browser_no_mcb_on_http_site.js] +tags = mcb [browser_bug1104165-switchtab-decodeuri.js] [browser_bug1003461-switchtab-override.js] [browser_bug1024133-switchtab-override-keynav.js] diff --git a/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js b/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js new file mode 100644 index 000000000000..ea1419b19d5c --- /dev/null +++ b/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js @@ -0,0 +1,52 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * Test for Bug 1182551 - + * + * This test has a top level HTTP page with an HTTPS iframe. The HTTPS iframe + * includes an HTTP image. We check that the top level security state is + * STATE_IS_INSECURE. The mixed content from the iframe shouldn't "upgrade" + * the HTTP top level page to broken HTTPS. + */ + +const gHttpTestRoot = "http://example.com/browser/browser/base/content/test/general/"; + +let gTestBrowser = null; + +function SecStateTestsCompleted() { + gBrowser.removeCurrentTab(); + window.focus(); + finish(); +} + +function test() { + waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({"set": [ + ["security.mixed_content.block_active_content", true], + ["security.mixed_content.block_display_content", false] + ]}, SecStateTests); +} + +function SecStateTests() { + let url = gHttpTestRoot + "file_mixedContentFramesOnHttp.html"; + gBrowser.selectedTab = gBrowser.addTab(); + gTestBrowser = gBrowser.selectedBrowser; + whenLoaded(gTestBrowser, SecStateTest1); + gTestBrowser.contentWindow.location = url; +} + +// The http page loads an https frame with an http image. +function SecStateTest1() { + // check security state is insecure + isSecurityState("insecure"); + + SecStateTestsCompleted(); +} + +function whenLoaded(aElement, aCallback) { + aElement.addEventListener("load", function onLoad() { + aElement.removeEventListener("load", onLoad, true); + executeSoon(aCallback); + }, true); +} diff --git a/browser/base/content/test/general/browser_mixedContentFromOnunload.js b/browser/base/content/test/general/browser_mixedContentFromOnunload.js index 2aa0f9358124..4efda362c22b 100644 --- a/browser/base/content/test/general/browser_mixedContentFromOnunload.js +++ b/browser/base/content/test/general/browser_mixedContentFromOnunload.js @@ -70,35 +70,6 @@ function SecStateTest2B() { SecStateTestsCompleted(); } -// Compares the security state of the page with what is expected -function isSecurityState(expectedState) { - let ui = gTestBrowser.securityUI; - if (!ui) { - ok(false, "No security UI to get the security state"); - return; - } - - const wpl = Components.interfaces.nsIWebProgressListener; - - // determine the security state - let isSecure = ui.state & wpl.STATE_IS_SECURE; - let isBroken = ui.state & wpl.STATE_IS_BROKEN; - let isInsecure = ui.state & wpl.STATE_IS_INSECURE; - - let actualState; - if (isSecure && !(isBroken || isInsecure)) { - actualState = "secure"; - } else if (isBroken && !(isSecure || isInsecure)) { - actualState = "broken"; - } else if (isInsecure && !(isSecure || isBroken)) { - actualState = "insecure"; - } else { - actualState = "unknown"; - } - - is(expectedState, actualState, "Expected state " + expectedState + " and the actual state is " + actualState + "."); -} - function whenLoaded(aElement, aCallback) { aElement.addEventListener("load", function onLoad() { aElement.removeEventListener("load", onLoad, true); diff --git a/browser/base/content/test/general/file_mixedContentFramesOnHttp.html b/browser/base/content/test/general/file_mixedContentFramesOnHttp.html new file mode 100644 index 000000000000..3bd16aea51fa --- /dev/null +++ b/browser/base/content/test/general/file_mixedContentFramesOnHttp.html @@ -0,0 +1,14 @@ + + + + + + Test for Bug 1182551 + + +

Test for Bug 1182551. This is an HTTP top level page. We include an HTTPS iframe that loads mixed passive content.

+ + + diff --git a/browser/base/content/test/general/file_mixedPassiveContent.html b/browser/base/content/test/general/file_mixedPassiveContent.html new file mode 100644 index 000000000000..a60ac94e8bda --- /dev/null +++ b/browser/base/content/test/general/file_mixedPassiveContent.html @@ -0,0 +1,13 @@ + + + + + + HTTPS page with HTTP image + + + + + diff --git a/browser/base/content/test/general/head.js b/browser/base/content/test/general/head.js index effe154f25eb..654fee2c4ed5 100644 --- a/browser/base/content/test/general/head.js +++ b/browser/base/content/test/general/head.js @@ -961,3 +961,32 @@ function promiseNewSearchEngine(basename) { }); }); } + +// Compares the security state of the page with what is expected +function isSecurityState(expectedState) { + let ui = gTestBrowser.securityUI; + if (!ui) { + ok(false, "No security UI to get the security state"); + return; + } + + const wpl = Components.interfaces.nsIWebProgressListener; + + // determine the security state + let isSecure = ui.state & wpl.STATE_IS_SECURE; + let isBroken = ui.state & wpl.STATE_IS_BROKEN; + let isInsecure = ui.state & wpl.STATE_IS_INSECURE; + + let actualState; + if (isSecure && !(isBroken || isInsecure)) { + actualState = "secure"; + } else if (isBroken && !(isSecure || isInsecure)) { + actualState = "broken"; + } else if (isInsecure && !(isSecure || isBroken)) { + actualState = "insecure"; + } else { + actualState = "unknown"; + } + + is(expectedState, actualState, "Expected state " + expectedState + " and the actual state is " + actualState + "."); +} diff --git a/browser/base/content/test/newtab/browser.ini b/browser/base/content/test/newtab/browser.ini index b2a13608568a..047f1ed20b70 100644 --- a/browser/base/content/test/newtab/browser.ini +++ b/browser/base/content/test/newtab/browser.ini @@ -7,6 +7,7 @@ support-files = [browser_newtab_block.js] [browser_newtab_bug721442.js] [browser_newtab_bug722273.js] +skip-if = true # Bug 1119906 [browser_newtab_bug723102.js] [browser_newtab_bug723121.js] [browser_newtab_bug725996.js] diff --git a/browser/devtools/styleinspector/test/browser.ini b/browser/devtools/styleinspector/test/browser.ini index 7d69fb8ca86c..afee1c15850f 100644 --- a/browser/devtools/styleinspector/test/browser.ini +++ b/browser/devtools/styleinspector/test/browser.ini @@ -198,7 +198,7 @@ skip-if = e10s || os == 'linux' # Bug 1111546 (e10s), bug 1093431 (linux) [browser_styleinspector_tooltip-multiple-background-images.js] [browser_styleinspector_tooltip-shorthand-fontfamily.js] [browser_styleinspector_tooltip-size.js] -skip-if = e10s # Bug 1111546 +skip-if = e10s || os == 'linux' # Bug 1111546 (e10s), bug 1093431 (linux) [browser_styleinspector_transform-highlighter-01.js] [browser_styleinspector_transform-highlighter-02.js] [browser_styleinspector_transform-highlighter-03.js] diff --git a/build/annotationProcessors/CodeGenerator.java b/build/annotationProcessors/CodeGenerator.java index b5a21d67f89b..0bba34e3af0a 100644 --- a/build/annotationProcessors/CodeGenerator.java +++ b/build/annotationProcessors/CodeGenerator.java @@ -48,7 +48,7 @@ public class CodeGenerator { " \"" + cls.getName().replace('.', '/') + "\";\n" + "\n" + "protected:\n" + - " using Class::Class;\n" + + " " + unqualifiedName + "(jobject instance) : Class(instance) {}\n" + "\n"); cpp.append( diff --git a/dom/apps/tests/file_test_widget.js b/dom/apps/tests/file_test_widget.js index b2a42c2b0e78..0fc86bdcc587 100644 --- a/dom/apps/tests/file_test_widget.js +++ b/dom/apps/tests/file_test_widget.js @@ -170,7 +170,9 @@ function checkIsWidgetScript(testMozbrowserEvent) { request.onerror = onError; if (testMozbrowserEvent) { - content.window.open("about:blank"); /* test mozbrowseropenwindow */ + var win = content.window.open("about:blank"); /* test mozbrowseropenwindow */ + /*Close new window to avoid mochitest "unable to restore focus" failures.*/ + win.close(); content.window.scrollTo(4000, 4000); /* test mozbrowser(async)scroll */ } } diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 4153df458607..f1f3cb144c86 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -3849,6 +3849,30 @@ nsContentUtils::MatchElementId(nsIContent *aContent, const nsAString& aId) return MatchElementId(aContent, id); } +/* static */ +nsIDocument* +nsContentUtils::GetSubdocumentWithOuterWindowId(nsIDocument *aDocument, + uint64_t aOuterWindowId) +{ + if (!aDocument || !aOuterWindowId) { + return nullptr; + } + + nsCOMPtr window = nsGlobalWindow::GetOuterWindowWithId(aOuterWindowId); + if (!window) { + return nullptr; + } + + nsCOMPtr foundDoc = window->GetDoc(); + if (nsContentUtils::ContentIsCrossDocDescendantOf(foundDoc, aDocument)) { + // Note that ContentIsCrossDocDescendantOf will return true if + // foundDoc == aDocument. + return foundDoc; + } + + return nullptr; +} + // Convert the string from the given encoding to Unicode. /* static */ nsresult diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 9c541a75716c..519c2a5117ea 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -325,6 +325,23 @@ public: */ static uint16_t ReverseDocumentPosition(uint16_t aDocumentPosition); + /** + * Returns a subdocument for aDocument with a particular outer window ID. + * + * @param aDocument + * The document whose subdocuments will be searched. + * @param aOuterWindowID + * The outer window ID for the subdocument to be found. This must + * be a value greater than 0. + * @return nsIDocument* + * A pointer to the found nsIDocument. nullptr if the subdocument + * cannot be found, or if either aDocument or aOuterWindowId were + * invalid. If the outer window ID belongs to aDocument itself, this + * will return a pointer to aDocument. + */ + static nsIDocument* GetSubdocumentWithOuterWindowId(nsIDocument *aDocument, + uint64_t aOuterWindowId); + static uint32_t CopyNewlineNormalizedUnicodeTo(const nsAString& aSource, uint32_t aSrcOffset, char16_t* aDest, diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp index aa037e2bace8..7265c4df0276 100644 --- a/dom/base/nsFrameLoader.cpp +++ b/dom/base/nsFrameLoader.cpp @@ -2885,18 +2885,31 @@ nsFrameLoader::InitializeBrowserAPI() } NS_IMETHODIMP -nsFrameLoader::StartPersistence(nsIWebBrowserPersistDocumentReceiver* aRecv) +nsFrameLoader::StartPersistence(uint64_t aOuterWindowID, + nsIWebBrowserPersistDocumentReceiver* aRecv) { + if (!aRecv) { + return NS_ERROR_INVALID_POINTER; + } + if (mRemoteBrowser) { - return mRemoteBrowser->StartPersistence(aRecv); + return mRemoteBrowser->StartPersistence(aOuterWindowID, aRecv); } - if (mDocShell) { - nsCOMPtr doc = do_GetInterface(mDocShell); - NS_ENSURE_STATE(doc); + + nsCOMPtr rootDoc = do_GetInterface(mDocShell); + nsCOMPtr foundDoc; + if (aOuterWindowID) { + foundDoc = nsContentUtils::GetSubdocumentWithOuterWindowId(rootDoc, aOuterWindowID); + } else { + foundDoc = rootDoc; + } + + if (!foundDoc) { + aRecv->OnError(NS_ERROR_NO_CONTENT); + } else { nsCOMPtr pdoc = - new mozilla::WebBrowserPersistLocalDocument(doc); + new mozilla::WebBrowserPersistLocalDocument(foundDoc); aRecv->OnDocumentReady(pdoc); - return NS_OK; } - return NS_ERROR_NO_CONTENT; + return NS_OK; } diff --git a/dom/browser-element/BrowserElementParent.cpp b/dom/browser-element/BrowserElementParent.cpp index ba1989d21ceb..c90df55949db 100644 --- a/dom/browser-element/BrowserElementParent.cpp +++ b/dom/browser-element/BrowserElementParent.cpp @@ -117,7 +117,7 @@ DispatchCustomDOMEvent(Element* aFrameElement, const nsAString& aEventName, } event->SetTrusted(true); // Dispatch the event. - *aStatus = nsEventStatus_eConsumeNoDefault; + // We don't initialize aStatus here, as our callers have already done so. nsresult rv = EventDispatcher::DispatchDOMEvent(aFrameElement, nullptr, static_cast(event), @@ -177,7 +177,7 @@ BrowserElementParent::DispatchOpenWindowEvent(Element* aOpenerFrameElement, return BrowserElementParent::OPEN_WINDOW_CANCELLED; } - nsEventStatus status; + nsEventStatus status = nsEventStatus_eIgnore; bool dispatchSucceeded = DispatchCustomDOMEvent(aOpenerFrameElement, NS_LITERAL_STRING("mozbrowseropenwindow"), diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index df1dab6916e6..6df1a10d1baf 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -121,7 +121,7 @@ both: */ PRenderFrame(); - PWebBrowserPersistDocument(); + PWebBrowserPersistDocument(uint64_t aOuterWindowID); parent: /** @@ -524,6 +524,7 @@ parent: child: NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse); + parent: /** diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 395238f1b3ae..8b45f60e7d6e 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -3118,16 +3118,28 @@ TabChildGlobal::GetGlobalJSObject() } PWebBrowserPersistDocumentChild* -TabChild::AllocPWebBrowserPersistDocumentChild() +TabChild::AllocPWebBrowserPersistDocumentChild(const uint64_t& aOuterWindowID) { return new WebBrowserPersistDocumentChild(); } bool -TabChild::RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor) +TabChild::RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor, + const uint64_t& aOuterWindowID) { - nsCOMPtr doc = GetDocument(); - static_cast(aActor)->Start(doc); + nsCOMPtr rootDoc = GetDocument(); + nsCOMPtr foundDoc; + if (aOuterWindowID) { + foundDoc = nsContentUtils::GetSubdocumentWithOuterWindowId(rootDoc, aOuterWindowID); + } else { + foundDoc = rootDoc; + } + + if (!foundDoc) { + aActor->SendInitFailure(NS_ERROR_NO_CONTENT); + } else { + static_cast(aActor)->Start(foundDoc); + } return true; } diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h index b323266ff0c3..521b93c30a0e 100644 --- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -495,8 +495,9 @@ public: virtual ScreenIntSize GetInnerSize() override; - virtual PWebBrowserPersistDocumentChild* AllocPWebBrowserPersistDocumentChild() override; - virtual bool RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor) override; + virtual PWebBrowserPersistDocumentChild* AllocPWebBrowserPersistDocumentChild(const uint64_t& aOuterWindowID) override; + virtual bool RecvPWebBrowserPersistDocumentConstructor(PWebBrowserPersistDocumentChild *aActor, + const uint64_t& aOuterWindowID) override; virtual bool DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor) override; protected: diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 0ac0cee51d76..39cd0dfec770 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -3368,7 +3368,7 @@ TabParent::AsyncPanZoomEnabled() const } PWebBrowserPersistDocumentParent* -TabParent::AllocPWebBrowserPersistDocumentParent() +TabParent::AllocPWebBrowserPersistDocumentParent(const uint64_t& aOuterWindowID) { return new WebBrowserPersistDocumentParent(); } @@ -3381,11 +3381,12 @@ TabParent::DeallocPWebBrowserPersistDocumentParent(PWebBrowserPersistDocumentPar } NS_IMETHODIMP -TabParent::StartPersistence(nsIWebBrowserPersistDocumentReceiver* aRecv) +TabParent::StartPersistence(uint64_t aOuterWindowID, + nsIWebBrowserPersistDocumentReceiver* aRecv) { auto* actor = new WebBrowserPersistDocumentParent(); actor->SetOnReady(aRecv); - return SendPWebBrowserPersistDocumentConstructor(actor) + return SendPWebBrowserPersistDocumentConstructor(actor, aOuterWindowID) ? NS_OK : NS_ERROR_FAILURE; // (The actor will be destroyed on constructor failure.) } diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index 4791e899dda8..5ccdecbff86a 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -435,7 +435,7 @@ public: int32_t& aDragAreaX, int32_t& aDragAreaY); layout::RenderFrameParent* GetRenderFrame(); - virtual PWebBrowserPersistDocumentParent* AllocPWebBrowserPersistDocumentParent() override; + virtual PWebBrowserPersistDocumentParent* AllocPWebBrowserPersistDocumentParent(const uint64_t& aOuterWindowID) override; virtual bool DeallocPWebBrowserPersistDocumentParent(PWebBrowserPersistDocumentParent* aActor) override; protected: diff --git a/dom/jsurl/nsJSProtocolHandler.cpp b/dom/jsurl/nsJSProtocolHandler.cpp index e024f4c53311..6f5b3688e2ea 100644 --- a/dom/jsurl/nsJSProtocolHandler.cpp +++ b/dom/jsurl/nsJSProtocolHandler.cpp @@ -565,6 +565,15 @@ nsJSChannel::Open2(nsIInputStream** aStream) NS_IMETHODIMP nsJSChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) { +#ifdef DEBUG + { + nsCOMPtr loadInfo = nsIChannel::GetLoadInfo(); + MOZ_ASSERT(!loadInfo || loadInfo->GetSecurityMode() == 0 || + loadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + } +#endif + NS_ENSURE_ARG(aListener); // First make sure that we have a usable inner window; we'll want to make diff --git a/dom/media/MediaEventSource.h b/dom/media/MediaEventSource.h index be13898031ca..8ef2742738e9 100644 --- a/dom/media/MediaEventSource.h +++ b/dom/media/MediaEventSource.h @@ -47,6 +47,15 @@ private: Atomic mRevoked; }; +enum class ListenerMode : int8_t { + // Allow at most one listener. Move will be used when possible + // to pass the event data to save copy. + Exclusive, + // This is the default. Event data will always be copied when passed + // to the listeners. + NonExclusive +}; + namespace detail { /** @@ -111,7 +120,7 @@ private: } // namespace detail -template class MediaEventSource; +template class MediaEventSource; /** * Not thread-safe since this is not meant to be shared and therefore only @@ -120,7 +129,7 @@ template class MediaEventSource; * listener from an event source. */ class MediaEventListener { - template + template friend class MediaEventSource; public: @@ -154,7 +163,7 @@ private: /** * A generic and thread-safe class to implement the observer pattern. */ -template +template class MediaEventSource { static_assert(!IsReference::value, "Ref-type not supported!"); typedef typename detail::EventTypeTraits::ArgType ArgType; @@ -246,6 +255,7 @@ class MediaEventSource { MediaEventListener ConnectInternal(Target* aTarget, const Function& aFunction) { MutexAutoLock lock(mMutex); + MOZ_ASSERT(Mode == ListenerMode::NonExclusive || mListeners.IsEmpty()); auto l = mListeners.AppendElement(); l->reset(new ListenerImpl(aTarget, aFunction)); return MediaEventListener((*l)->Token()); @@ -346,8 +356,8 @@ private: * and event publisher. Mostly used as a member variable to publish events * to the listeners. */ -template -class MediaEventProducer : public MediaEventSource { +template +class MediaEventProducer : public MediaEventSource { public: void Notify(const EventType& aEvent) { this->NotifyInternal(aEvent); diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index aaa3d08af5a4..bc49534e41da 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -805,7 +805,6 @@ MediaFormatReader::NotifyError(TrackType aTrack) LOGV("%s Decoding error", TrackTypeToStr(aTrack)); auto& decoder = GetDecoderData(aTrack); decoder.mError = true; - decoder.mNeedDraining = true; ScheduleUpdate(aTrack); } @@ -1144,7 +1143,7 @@ MediaFormatReader::Update(TrackType aTrack) } else if (decoder.mDemuxEOS) { decoder.RejectPromise(END_OF_STREAM, __func__); } - } else if (decoder.mError && !decoder.mDecoder) { + } else if (decoder.mError) { decoder.RejectPromise(DECODE_ERROR, __func__); return; } else if (decoder.mWaitingForData) { diff --git a/dom/media/MediaResource.cpp b/dom/media/MediaResource.cpp index c3b096929d93..70c7932d6958 100644 --- a/dom/media/MediaResource.cpp +++ b/dom/media/MediaResource.cpp @@ -1008,13 +1008,13 @@ ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume) mOffset = aOffset; + // Don't report close of the channel because the channel is not closed for + // download ended, but for internal changes in the read position. + mIgnoreClose = true; + + // Don't create a new channel if we are still suspended. The channel will + // be recreated when we are resumed. if (mSuspendCount > 0) { - // Close the existing channel to force the channel to be recreated at - // the correct offset upon resume. - if (mChannel) { - mIgnoreClose = true; - CloseChannel(); - } return NS_OK; } diff --git a/dom/media/eme/MediaKeySession.cpp b/dom/media/eme/MediaKeySession.cpp index fb979757f495..0e32dcb15b7e 100644 --- a/dom/media/eme/MediaKeySession.cpp +++ b/dom/media/eme/MediaKeySession.cpp @@ -16,7 +16,7 @@ #include "mozilla/Move.h" #include "nsContentUtils.h" #include "mozilla/EMEUtils.h" -#include "mozilla/Base64.h" +#include "GMPUtils.h" #include "nsPrintfCString.h" namespace mozilla { @@ -143,14 +143,7 @@ MediaKeySession::UpdateKeyStatusMap() nsPrintfCString("MediaKeySession[%p,'%s'] key statuses change {", this, NS_ConvertUTF16toUTF8(mSessionId).get())); for (const CDMCaps::KeyStatus& status : keyStatuses) { - nsAutoCString base64KeyId; - nsDependentCSubstring rawKeyId(reinterpret_cast(status.mId.Elements()), - status.mId.Length()); - nsresult rv = Base64Encode(rawKeyId, base64KeyId); - if (NS_WARN_IF(NS_FAILED(rv))) { - continue; - } - message.Append(nsPrintfCString(" (%s,%s)", base64KeyId.get(), + message.Append(nsPrintfCString(" (%s,%s)", ToBase64(status.mId).get(), MediaKeyStatusValues::strings[status.mStatus].value)); } message.Append(" }"); @@ -197,17 +190,9 @@ MediaKeySession::GenerateRequest(const nsAString& aInitDataType, } // Convert initData to base64 for easier logging. - // Note: UpdateSession() Move()s the data out of the array, so we have + // Note: CreateSession() Move()s the data out of the array, so we have // to copy it here. - nsAutoCString base64InitData; - if (EME_LOG_ENABLED()) { - nsDependentCSubstring rawInitData(reinterpret_cast(data.Elements()), - data.Length()); - if (NS_FAILED(Base64Encode(rawInitData, base64InitData))) { - NS_WARNING("Failed to base64 encode initData for logging"); - } - } - + nsAutoCString base64InitData(ToBase64(data)); PromiseId pid = mKeys->StorePromise(promise); mKeys->GetCDMProxy()->CreateSession(Token(), mSessionType, @@ -296,14 +281,7 @@ MediaKeySession::Update(const ArrayBufferViewOrArrayBuffer& aResponse, ErrorResu // Convert response to base64 for easier logging. // Note: UpdateSession() Move()s the data out of the array, so we have // to copy it here. - nsAutoCString base64Response; - if (EME_LOG_ENABLED()) { - nsDependentCSubstring rawResponse(reinterpret_cast(data.Elements()), - data.Length()); - if (NS_FAILED(Base64Encode(rawResponse, base64Response))) { - NS_WARNING("Failed to base64 encode response for logging"); - } - } + nsAutoCString base64Response(ToBase64(data)); PromiseId pid = mKeys->StorePromise(promise); mKeys->GetCDMProxy()->UpdateSession(mSessionId, @@ -400,16 +378,10 @@ MediaKeySession::DispatchKeyMessage(MediaKeyMessageType aMessageType, const nsTArray& aMessage) { if (EME_LOG_ENABLED()) { - nsAutoCString base64MsgData; - nsDependentCSubstring rawMsgData(reinterpret_cast(aMessage.Elements()), - aMessage.Length()); - if (NS_FAILED(Base64Encode(rawMsgData, base64MsgData))) { - NS_WARNING("Failed to base64 encode message for logging"); - } EME_LOG("MediaKeySession[%p,'%s'] DispatchKeyMessage() type=%s message(base64)='%s'", this, NS_ConvertUTF16toUTF8(mSessionId).get(), MediaKeyMessageTypeValues::strings[uint32_t(aMessageType)].value, - base64MsgData.get()); + ToBase64(aMessage).get()); } nsRefPtr event( diff --git a/dom/media/gmp-plugin/gmp-test-output-protection.h b/dom/media/gmp-plugin/gmp-test-output-protection.h index 54b4fa739174..57855ca71f91 100644 --- a/dom/media/gmp-plugin/gmp-test-output-protection.h +++ b/dom/media/gmp-plugin/gmp-test-output-protection.h @@ -35,13 +35,6 @@ static BOOL CALLBACK EnumDisplayMonitorsCallback(HMONITOR hMonitor, HDC hdc, failureMsgs->push_back("FAIL GetMonitorInfoA call failed"); } - DISPLAY_DEVICEA dd; - ZeroMemory(&dd, sizeof(dd)); - dd.cb = sizeof(dd); - if (!EnumDisplayDevicesA(miex.szDevice, 0, &dd, 1)) { - failureMsgs->push_back("FAIL EnumDisplayDevicesA call failed"); - } - ULONG numVideoOutputs = 0; IOPMVideoOutput** opmVideoOutputArray = nullptr; HRESULT hr = sOPMGetVideoOutputsFromHMONITORProc(hMonitor, @@ -57,6 +50,13 @@ static BOOL CALLBACK EnumDisplayMonitorsCallback(HMONITOR hMonitor, HDC hdc, return true; } + DISPLAY_DEVICEA dd; + ZeroMemory(&dd, sizeof(dd)); + dd.cb = sizeof(dd); + if (!EnumDisplayDevicesA(miex.szDevice, 0, &dd, 1)) { + failureMsgs->push_back("FAIL EnumDisplayDevicesA call failed"); + } + for (ULONG i = 0; i < numVideoOutputs; ++i) { OPM_RANDOM_NUMBER opmRandomNumber; BYTE* certificate = nullptr; diff --git a/dom/media/gmp/GMPAudioDecoderParent.cpp b/dom/media/gmp/GMPAudioDecoderParent.cpp index 72facefc3fbf..b5e4d4db9631 100644 --- a/dom/media/gmp/GMPAudioDecoderParent.cpp +++ b/dom/media/gmp/GMPAudioDecoderParent.cpp @@ -19,6 +19,7 @@ namespace mozilla { extern PRLogModuleInfo* GetGMPLog(); +#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg) #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) @@ -48,6 +49,8 @@ GMPAudioDecoderParent::InitDecode(GMPAudioCodecType aCodecType, nsTArray& aExtraData, GMPAudioDecoderCallbackProxy* aCallback) { + LOGD(("GMPAudioDecoderParent[%p]::InitDecode()", this)); + if (mIsOpen) { NS_WARNING("Trying to re-init an in-use GMP audio decoder!"); return NS_ERROR_FAILURE; @@ -78,6 +81,8 @@ GMPAudioDecoderParent::InitDecode(GMPAudioCodecType aCodecType, nsresult GMPAudioDecoderParent::Decode(GMPAudioSamplesImpl& aEncodedSamples) { + LOGV(("GMPAudioDecoderParent[%p]::Decode() timestamp=%lld", + this, aEncodedSamples.TimeStamp())); if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP Audio decoder!"); @@ -100,6 +105,8 @@ GMPAudioDecoderParent::Decode(GMPAudioSamplesImpl& aEncodedSamples) nsresult GMPAudioDecoderParent::Reset() { + LOGD(("GMPAudioDecoderParent[%p]::Reset()", this)); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP Audio decoder!"); return NS_ERROR_FAILURE; @@ -120,6 +127,8 @@ GMPAudioDecoderParent::Reset() nsresult GMPAudioDecoderParent::Drain() { + LOGD(("GMPAudioDecoderParent[%p]::Drain()", this)); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP Audio decoder!"); return NS_ERROR_FAILURE; @@ -141,7 +150,7 @@ GMPAudioDecoderParent::Drain() nsresult GMPAudioDecoderParent::Close() { - LOGD(("%s: %p", __FUNCTION__, this)); + LOGD(("GMPAudioDecoderParent[%p]::Close()", this)); MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread()); // Ensure if we've received a Close while waiting for a ResetComplete @@ -166,7 +175,7 @@ GMPAudioDecoderParent::Close() nsresult GMPAudioDecoderParent::Shutdown() { - LOGD(("%s: %p", __FUNCTION__, this)); + LOGD(("GMPAudioDecoderParent[%p]::Shutdown()", this)); MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread()); if (mShuttingDown) { @@ -197,6 +206,8 @@ GMPAudioDecoderParent::Shutdown() void GMPAudioDecoderParent::ActorDestroy(ActorDestroyReason aWhy) { + LOGD(("GMPAudioDecoderParent[%p]::ActorDestroy(reason=%d)", this, aWhy)); + mIsOpen = false; mActorDestroyed = true; @@ -220,6 +231,9 @@ GMPAudioDecoderParent::ActorDestroy(ActorDestroyReason aWhy) bool GMPAudioDecoderParent::RecvDecoded(const GMPAudioDecodedSampleData& aDecoded) { + LOGV(("GMPAudioDecoderParent[%p]::RecvDecoded() timestamp=%lld", + this, aDecoded.mTimeStamp())); + if (!mCallback) { return false; } @@ -235,6 +249,8 @@ GMPAudioDecoderParent::RecvDecoded(const GMPAudioDecodedSampleData& aDecoded) bool GMPAudioDecoderParent::RecvInputDataExhausted() { + LOGV(("GMPAudioDecoderParent[%p]::RecvInputDataExhausted()", this)); + if (!mCallback) { return false; } @@ -248,6 +264,8 @@ GMPAudioDecoderParent::RecvInputDataExhausted() bool GMPAudioDecoderParent::RecvDrainComplete() { + LOGD(("GMPAudioDecoderParent[%p]::RecvDrainComplete()", this)); + if (!mCallback) { return false; } @@ -266,6 +284,8 @@ GMPAudioDecoderParent::RecvDrainComplete() bool GMPAudioDecoderParent::RecvResetComplete() { + LOGD(("GMPAudioDecoderParent[%p]::RecvResetComplete()", this)); + if (!mCallback) { return false; } @@ -284,6 +304,8 @@ GMPAudioDecoderParent::RecvResetComplete() bool GMPAudioDecoderParent::RecvError(const GMPErr& aError) { + LOGD(("GMPAudioDecoderParent[%p]::RecvError(error=%d)", this, aError)); + if (!mCallback) { return false; } @@ -302,6 +324,8 @@ GMPAudioDecoderParent::RecvError(const GMPErr& aError) bool GMPAudioDecoderParent::RecvShutdown() { + LOGD(("GMPAudioDecoderParent[%p]::RecvShutdown()", this)); + Shutdown(); return true; } @@ -309,6 +333,8 @@ GMPAudioDecoderParent::RecvShutdown() bool GMPAudioDecoderParent::Recv__delete__() { + LOGD(("GMPAudioDecoderParent[%p]::Recv__delete__()", this)); + if (mPlugin) { // Ignore any return code. It is OK for this to fail without killing the process. mPlugin->AudioDecoderDestroyed(this); @@ -321,6 +347,8 @@ GMPAudioDecoderParent::Recv__delete__() void GMPAudioDecoderParent::UnblockResetAndDrain() { + LOGD(("GMPAudioDecoderParent[%p]::UnblockResetAndDrain()", this)); + if (!mCallback) { MOZ_ASSERT(!mIsAwaitingResetComplete); MOZ_ASSERT(!mIsAwaitingDrainComplete); diff --git a/dom/media/gmp/GMPDecryptorParent.cpp b/dom/media/gmp/GMPDecryptorParent.cpp index 6c127461821e..501aec124102 100644 --- a/dom/media/gmp/GMPDecryptorParent.cpp +++ b/dom/media/gmp/GMPDecryptorParent.cpp @@ -9,6 +9,17 @@ #include "mozilla/unused.h" namespace mozilla { + +#ifdef LOG +#undef LOG +#endif + +extern PRLogModuleInfo* GetGMPLog(); + +#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg) +#define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) +#define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) + namespace gmp { GMPDecryptorParent::GMPDecryptorParent(GMPContentParent* aPlugin) @@ -32,6 +43,8 @@ GMPDecryptorParent::~GMPDecryptorParent() nsresult GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback) { + LOGD(("GMPDecryptorParent[%p]::Init()", this)); + if (mIsOpen) { NS_WARNING("Trying to re-use an in-use GMP decrypter!"); return NS_ERROR_FAILURE; @@ -51,6 +64,9 @@ GMPDecryptorParent::CreateSession(uint32_t aCreateSessionToken, const nsTArray& aInitData, GMPSessionType aSessionType) { + LOGD(("GMPDecryptorParent[%p]::CreateSession(token=%u, promiseId=%u, aInitData='%s')", + this, aCreateSessionToken, aPromiseId, ToBase64(aInitData).get())); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return; @@ -64,6 +80,8 @@ void GMPDecryptorParent::LoadSession(uint32_t aPromiseId, const nsCString& aSessionId) { + LOGD(("GMPDecryptorParent[%p]::LoadSession(sessionId='%s', promiseId=%u)", + this, aSessionId.get(), aPromiseId)); if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return; @@ -78,6 +96,9 @@ GMPDecryptorParent::UpdateSession(uint32_t aPromiseId, const nsCString& aSessionId, const nsTArray& aResponse) { + LOGD(("GMPDecryptorParent[%p]::UpdateSession(sessionId='%s', promiseId=%u response='%s')", + this, aSessionId.get(), aPromiseId, ToBase64(aResponse).get())); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return; @@ -91,6 +112,9 @@ void GMPDecryptorParent::CloseSession(uint32_t aPromiseId, const nsCString& aSessionId) { + LOGD(("GMPDecryptorParent[%p]::CloseSession(sessionId='%s', promiseId=%u)", + this, aSessionId.get(), aPromiseId)); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return; @@ -104,6 +128,9 @@ void GMPDecryptorParent::RemoveSession(uint32_t aPromiseId, const nsCString& aSessionId) { + LOGD(("GMPDecryptorParent[%p]::RemoveSession(sessionId='%s', promiseId=%u)", + this, aSessionId.get(), aPromiseId)); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return; @@ -117,6 +144,9 @@ void GMPDecryptorParent::SetServerCertificate(uint32_t aPromiseId, const nsTArray& aServerCert) { + LOGD(("GMPDecryptorParent[%p]::SetServerCertificate(promiseId=%u)", + this, aPromiseId)); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return; @@ -131,6 +161,8 @@ GMPDecryptorParent::Decrypt(uint32_t aId, const CryptoSample& aCrypto, const nsTArray& aBuffer) { + LOGV(("GMPDecryptorParent[%p]::Decrypt(id=%d)", this, aId)); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return; @@ -152,6 +184,9 @@ bool GMPDecryptorParent::RecvSetSessionId(const uint32_t& aCreateSessionId, const nsCString& aSessionId) { + LOGD(("GMPDecryptorParent[%p]::RecvSetSessionId(token=%u, sessionId='%s')", + this, aCreateSessionId, aSessionId.get())); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return false; @@ -164,6 +199,9 @@ bool GMPDecryptorParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId, const bool& aSuccess) { + LOGD(("GMPDecryptorParent[%p]::RecvResolveLoadSessionPromise(promiseId=%u)", + this, aPromiseId)); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return false; @@ -175,6 +213,9 @@ GMPDecryptorParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId, bool GMPDecryptorParent::RecvResolvePromise(const uint32_t& aPromiseId) { + LOGD(("GMPDecryptorParent[%p]::RecvResolvePromise(promiseId=%u)", + this, aPromiseId)); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return false; @@ -206,6 +247,9 @@ GMPDecryptorParent::RecvRejectPromise(const uint32_t& aPromiseId, const GMPDOMException& aException, const nsCString& aMessage) { + LOGD(("GMPDecryptorParent[%p]::RecvRejectPromise(promiseId=%u, exception=%d, msg='%s')", + this, aException, aMessage.get())); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return false; @@ -219,6 +263,9 @@ GMPDecryptorParent::RecvSessionMessage(const nsCString& aSessionId, const GMPSessionMessageType& aMessageType, nsTArray&& aMessage) { + LOGD(("GMPDecryptorParent[%p]::RecvSessionMessage(sessionId='%s', type=%d, msg='%s')", + this, aSessionId.get(), aMessageType, ToBase64(aMessage).get())); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return false; @@ -231,6 +278,9 @@ bool GMPDecryptorParent::RecvExpirationChange(const nsCString& aSessionId, const double& aExpiryTime) { + LOGD(("GMPDecryptorParent[%p]::RecvExpirationChange(sessionId='%s', expiry=%lf)", + this, aSessionId.get(), aExpiryTime)); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return false; @@ -242,6 +292,9 @@ GMPDecryptorParent::RecvExpirationChange(const nsCString& aSessionId, bool GMPDecryptorParent::RecvSessionClosed(const nsCString& aSessionId) { + LOGD(("GMPDecryptorParent[%p]::RecvSessionClosed(sessionId='%s')", + this, aSessionId.get())); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return false; @@ -256,6 +309,10 @@ GMPDecryptorParent::RecvSessionError(const nsCString& aSessionId, const uint32_t& aSystemCode, const nsCString& aMessage) { + LOGD(("GMPDecryptorParent[%p]::RecvSessionError(sessionId='%s', exception=%d, sysCode=%d, msg='%s')", + this, aSessionId.get(), + aException, aSystemCode, aMessage.get())); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return false; @@ -272,6 +329,9 @@ GMPDecryptorParent::RecvKeyStatusChanged(const nsCString& aSessionId, InfallibleTArray&& aKeyId, const GMPMediaKeyStatus& aStatus) { + LOGD(("GMPDecryptorParent[%p]::RecvKeyStatusChanged(sessionId='%s', keyId=%s, status=%d)", + this, aSessionId.get(), ToBase64(aKeyId).get(), aStatus)); + if (mIsOpen) { mCallback->KeyStatusChanged(aSessionId, aKeyId, aStatus); } @@ -281,6 +341,8 @@ GMPDecryptorParent::RecvKeyStatusChanged(const nsCString& aSessionId, bool GMPDecryptorParent::RecvSetCaps(const uint64_t& aCaps) { + LOGD(("GMPDecryptorParent[%p]::RecvSetCaps(caps=0x%llx)", this, aCaps)); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return false; @@ -294,6 +356,9 @@ GMPDecryptorParent::RecvDecrypted(const uint32_t& aId, const GMPErr& aErr, InfallibleTArray&& aBuffer) { + LOGV(("GMPDecryptorParent[%p]::RecvDecrypted(id=%d, err=%d)", + this, aId, aErr)); + if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return false; @@ -305,6 +370,8 @@ GMPDecryptorParent::RecvDecrypted(const uint32_t& aId, bool GMPDecryptorParent::RecvShutdown() { + LOGD(("GMPDecryptorParent[%p]::RecvShutdown()", this)); + Shutdown(); return true; } @@ -313,7 +380,9 @@ GMPDecryptorParent::RecvShutdown() void GMPDecryptorParent::Close() { + LOGD(("GMPDecryptorParent[%p]::Close()", this)); MOZ_ASSERT(mGMPThread == NS_GetCurrentThread()); + // Consumer is done with us; we can shut down. No more callbacks should // be made to mCallback. Note: do this before Shutdown()! mCallback = nullptr; @@ -328,6 +397,7 @@ GMPDecryptorParent::Close() void GMPDecryptorParent::Shutdown() { + LOGD(("GMPDecryptorParent[%p]::Shutdown()", this)); MOZ_ASSERT(mGMPThread == NS_GetCurrentThread()); if (mShuttingDown) { @@ -351,6 +421,8 @@ GMPDecryptorParent::Shutdown() void GMPDecryptorParent::ActorDestroy(ActorDestroyReason aWhy) { + LOGD(("GMPDecryptorParent[%p]::ActorDestroy(reason=%d)", this, aWhy)); + mIsOpen = false; mActorDestroyed = true; if (mCallback) { @@ -367,6 +439,8 @@ GMPDecryptorParent::ActorDestroy(ActorDestroyReason aWhy) bool GMPDecryptorParent::Recv__delete__() { + LOGD(("GMPDecryptorParent[%p]::Recv__delete__()", this)); + if (mPlugin) { mPlugin->DecryptorDestroyed(this); mPlugin = nullptr; diff --git a/dom/media/gmp/GMPStorageParent.cpp b/dom/media/gmp/GMPStorageParent.cpp index e93fef042328..c4f04ede0863 100644 --- a/dom/media/gmp/GMPStorageParent.cpp +++ b/dom/media/gmp/GMPStorageParent.cpp @@ -30,11 +30,6 @@ extern PRLogModuleInfo* GetGMPLog(); #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) -#ifdef __CLASS__ -#undef __CLASS__ -#endif -#define __CLASS__ "GMPStorageParent" - namespace gmp { // We store the records in files in the profile dir. @@ -165,7 +160,7 @@ public: { MOZ_ASSERT(!IsOpen(aRecordName)); nsresult rv; - Record* record = nullptr; + Record* record = nullptr; if (!mRecords.Get(aRecordName, &record)) { // New file. nsAutoString filename; @@ -254,7 +249,7 @@ public: return GMPClosedErr; } - Record* record = nullptr; + Record* record = nullptr; mRecords.Get(aRecordName, &record); MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this. @@ -581,6 +576,8 @@ GMPStorageParent::GMPStorageParent(const nsCString& aNodeId, nsresult GMPStorageParent::Init() { + LOGD(("GMPStorageParent[%p]::Init()", this)); + if (NS_WARN_IF(mNodeId.IsEmpty())) { return NS_ERROR_FAILURE; } @@ -611,6 +608,9 @@ GMPStorageParent::Init() bool GMPStorageParent::RecvOpen(const nsCString& aRecordName) { + LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s')", + this, aRecordName.get())); + if (mShutdown) { return false; } @@ -618,23 +618,30 @@ GMPStorageParent::RecvOpen(const nsCString& aRecordName) if (mNodeId.EqualsLiteral("null")) { // Refuse to open storage if the page is opened from local disk, // or shared across origin. - NS_WARNING("Refusing to open storage for null NodeId"); + LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; null nodeId", + this, aRecordName.get())); unused << SendOpenComplete(aRecordName, GMPGenericErr); return true; } if (aRecordName.IsEmpty()) { + LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record name empty", + this, aRecordName.get())); unused << SendOpenComplete(aRecordName, GMPGenericErr); return true; } if (mStorage->IsOpen(aRecordName)) { + LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') failed; record in use", + this, aRecordName.get())); unused << SendOpenComplete(aRecordName, GMPRecordInUse); return true; } auto err = mStorage->Open(aRecordName); MOZ_ASSERT(GMP_FAILED(err) || mStorage->IsOpen(aRecordName)); + LOGD(("GMPStorageParent[%p]::RecvOpen(record='%s') complete; rv=%d", + this, aRecordName.get(), err)); unused << SendOpenComplete(aRecordName, err); return true; @@ -643,7 +650,8 @@ GMPStorageParent::RecvOpen(const nsCString& aRecordName) bool GMPStorageParent::RecvRead(const nsCString& aRecordName) { - LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get())); + LOGD(("GMPStorageParent[%p]::RecvRead(record='%s')", + this, aRecordName.get())); if (mShutdown) { return false; @@ -651,9 +659,14 @@ GMPStorageParent::RecvRead(const nsCString& aRecordName) nsTArray data; if (!mStorage->IsOpen(aRecordName)) { + LOGD(("GMPStorageParent[%p]::RecvRead(record='%s') failed; record not open", + this, aRecordName.get())); unused << SendReadComplete(aRecordName, GMPClosedErr, data); } else { - unused << SendReadComplete(aRecordName, mStorage->Read(aRecordName, data), data); + GMPErr rv = mStorage->Read(aRecordName, data); + LOGD(("GMPStorageParent[%p]::RecvRead(record='%s') read %d bytes rv=%d", + this, aRecordName.get(), data.Length(), rv)); + unused << SendReadComplete(aRecordName, rv, data); } return true; @@ -663,23 +676,32 @@ bool GMPStorageParent::RecvWrite(const nsCString& aRecordName, InfallibleTArray&& aBytes) { - LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get())); + LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') %d bytes", + this, aRecordName.get(), aBytes.Length())); if (mShutdown) { return false; } if (!mStorage->IsOpen(aRecordName)) { + LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record not open", + this, aRecordName.get())); unused << SendWriteComplete(aRecordName, GMPClosedErr); return true; } if (aBytes.Length() > GMP_MAX_RECORD_SIZE) { + LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') failed record too big", + this, aRecordName.get())); unused << SendWriteComplete(aRecordName, GMPQuotaExceededErr); return true; } - unused << SendWriteComplete(aRecordName, mStorage->Write(aRecordName, aBytes)); + GMPErr rv = mStorage->Write(aRecordName, aBytes); + LOGD(("GMPStorageParent[%p]::RecvWrite(record='%s') write complete rv=%d", + this, aRecordName.get(), rv)); + + unused << SendWriteComplete(aRecordName, rv); return true; } @@ -687,14 +709,16 @@ GMPStorageParent::RecvWrite(const nsCString& aRecordName, bool GMPStorageParent::RecvGetRecordNames() { - LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this)); - if (mShutdown) { return true; } nsTArray recordNames; GMPErr status = mStorage->GetRecordNames(recordNames); + + LOGD(("GMPStorageParent[%p]::RecvGetRecordNames() status=%d numRecords=%d", + this, status, recordNames.Length())); + unused << SendRecordNames(recordNames, status); return true; @@ -703,7 +727,8 @@ GMPStorageParent::RecvGetRecordNames() bool GMPStorageParent::RecvClose(const nsCString& aRecordName) { - LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get())); + LOGD(("GMPStorageParent[%p]::RecvClose(record='%s')", + this, aRecordName.get())); if (mShutdown) { return true; @@ -717,14 +742,14 @@ GMPStorageParent::RecvClose(const nsCString& aRecordName) void GMPStorageParent::ActorDestroy(ActorDestroyReason aWhy) { - LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this)); + LOGD(("GMPStorageParent[%p]::ActorDestroy(reason=%d)", this, aWhy)); Shutdown(); } void GMPStorageParent::Shutdown() { - LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this)); + LOGD(("GMPStorageParent[%p]::Shutdown()", this)); if (mShutdown) { return; diff --git a/dom/media/gmp/GMPUtils.cpp b/dom/media/gmp/GMPUtils.cpp index bb9b621d535d..44732d05076f 100644 --- a/dom/media/gmp/GMPUtils.cpp +++ b/dom/media/gmp/GMPUtils.cpp @@ -10,6 +10,7 @@ #include "nsCOMPtr.h" #include "nsLiteralString.h" #include "nsCRTGlue.h" +#include "mozilla/Base64.h" namespace mozilla { @@ -50,4 +51,17 @@ SplitAt(const char* aDelims, } } +nsCString +ToBase64(const nsTArray& aBytes) +{ + nsAutoCString base64; + nsDependentCSubstring raw(reinterpret_cast(aBytes.Elements()), + aBytes.Length()); + nsresult rv = Base64Encode(raw, base64); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_LITERAL_CSTRING("[Base64EncodeFailed]"); + } + return base64; +} + } // namespace mozilla diff --git a/dom/media/gmp/GMPUtils.h b/dom/media/gmp/GMPUtils.h index bb0a22e21dd7..6f50a6d7a7ce 100644 --- a/dom/media/gmp/GMPUtils.h +++ b/dom/media/gmp/GMPUtils.h @@ -34,6 +34,9 @@ SplitAt(const char* aDelims, const nsACString& aInput, nsTArray& aOutTokens); +nsCString +ToBase64(const nsTArray& aBytes); + } // namespace mozilla #endif diff --git a/dom/media/gmp/GMPVideoDecoderParent.cpp b/dom/media/gmp/GMPVideoDecoderParent.cpp index 68e633f8701a..0209ecd4fd5d 100644 --- a/dom/media/gmp/GMPVideoDecoderParent.cpp +++ b/dom/media/gmp/GMPVideoDecoderParent.cpp @@ -23,6 +23,7 @@ namespace mozilla { extern PRLogModuleInfo* GetGMPLog(); +#define LOGV(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Verbose, msg) #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg) #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg) @@ -67,7 +68,7 @@ GMPVideoDecoderParent::Host() void GMPVideoDecoderParent::Close() { - LOGD(("%s: %p", __FUNCTION__, this)); + LOGD(("GMPVideoDecoderParent[%p]::Close()", this)); MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread()); // Ensure if we've received a Close while waiting for a ResetComplete @@ -92,6 +93,8 @@ GMPVideoDecoderParent::InitDecode(const GMPVideoCodec& aCodecSettings, GMPVideoDecoderCallbackProxy* aCallback, int32_t aCoreCount) { + LOGD(("GMPVideoDecoderParent[%p]::InitDecode()", this)); + if (mActorDestroyed) { NS_WARNING("Trying to use a destroyed GMP video decoder!"); return NS_ERROR_FAILURE; @@ -123,6 +126,10 @@ GMPVideoDecoderParent::Decode(GMPUniquePtr aInputFrame, const nsTArray& aCodecSpecificInfo, int64_t aRenderTimeMs) { + LOGV(("GMPVideoDecoderParent[%p]::Decode() timestamp=%lld keyframe=%d", + this, aInputFrame->TimeStamp(), + aInputFrame->FrameType() == kGMPKeyFrame)); + if (!mIsOpen) { NS_WARNING("Trying to use an dead GMP video decoder"); return NS_ERROR_FAILURE; @@ -158,6 +165,8 @@ GMPVideoDecoderParent::Decode(GMPUniquePtr aInputFrame, nsresult GMPVideoDecoderParent::Reset() { + LOGD(("GMPVideoDecoderParent[%p]::Reset()", this)); + if (!mIsOpen) { NS_WARNING("Trying to use an dead GMP video decoder"); return NS_ERROR_FAILURE; @@ -178,6 +187,8 @@ GMPVideoDecoderParent::Reset() nsresult GMPVideoDecoderParent::Drain() { + LOGD(("GMPVideoDecoderParent[%p]::Drain()", this)); + if (!mIsOpen) { NS_WARNING("Trying to use an dead GMP video decoder"); return NS_ERROR_FAILURE; @@ -211,7 +222,7 @@ GMPVideoDecoderParent::GetDisplayName() const nsresult GMPVideoDecoderParent::Shutdown() { - LOGD(("%s: %p", __FUNCTION__, this)); + LOGD(("GMPVideoDecoderParent[%p]::Shutdown()", this)); MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread()); if (mShuttingDown) { @@ -242,6 +253,8 @@ GMPVideoDecoderParent::Shutdown() void GMPVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy) { + LOGD(("GMPVideoDecoderParent[%p]::ActorDestroy reason=%d", this, aWhy)); + mIsOpen = false; mActorDestroyed = true; mVideoHost.DoneWithAPI(); @@ -267,12 +280,17 @@ GMPVideoDecoderParent::ActorDestroy(ActorDestroyReason aWhy) bool GMPVideoDecoderParent::RecvDecoded(const GMPVideoi420FrameData& aDecodedFrame) { + LOGV(("GMPVideoDecoderParent[%p]::RecvDecoded() timestamp=%lld", + this, aDecodedFrame.mTimestamp())); + if (!mCallback) { return false; } if (!GMPVideoi420FrameImpl::CheckFrameData(aDecodedFrame)) { - LOG(LogLevel::Error, ("%s: Decoded frame corrupt, ignoring", __FUNCTION__)); + LOG(LogLevel::Error, + ("GMPVideoDecoderParent[%p]::RecvDecoded() " + "timestamp=%lld decoded frame corrupt, ignoring")); return false; } auto f = new GMPVideoi420FrameImpl(aDecodedFrame, &mVideoHost); @@ -312,6 +330,8 @@ GMPVideoDecoderParent::RecvReceivedDecodedFrame(const uint64_t& aPictureId) bool GMPVideoDecoderParent::RecvInputDataExhausted() { + LOGV(("GMPVideoDecoderParent[%p]::RecvInputDataExhausted()", this)); + if (!mCallback) { return false; } @@ -325,6 +345,8 @@ GMPVideoDecoderParent::RecvInputDataExhausted() bool GMPVideoDecoderParent::RecvDrainComplete() { + LOGD(("GMPVideoDecoderParent[%p]::RecvDrainComplete()", this)); + if (!mCallback) { return false; } @@ -343,6 +365,8 @@ GMPVideoDecoderParent::RecvDrainComplete() bool GMPVideoDecoderParent::RecvResetComplete() { + LOGD(("GMPVideoDecoderParent[%p]::RecvResetComplete()", this)); + if (!mCallback) { return false; } @@ -361,6 +385,8 @@ GMPVideoDecoderParent::RecvResetComplete() bool GMPVideoDecoderParent::RecvError(const GMPErr& aError) { + LOGD(("GMPVideoDecoderParent[%p]::RecvError(error=%d)", this, aError)); + if (!mCallback) { return false; } @@ -379,6 +405,8 @@ GMPVideoDecoderParent::RecvError(const GMPErr& aError) bool GMPVideoDecoderParent::RecvShutdown() { + LOGD(("GMPVideoDecoderParent[%p]::RecvShutdown()", this)); + Shutdown(); return true; } @@ -415,6 +443,8 @@ GMPVideoDecoderParent::AnswerNeedShmem(const uint32_t& aFrameBufferSize, bool GMPVideoDecoderParent::Recv__delete__() { + LOGD(("GMPVideoDecoderParent[%p]::Recv__delete__()", this)); + if (mPlugin) { // Ignore any return code. It is OK for this to fail without killing the process. mPlugin->VideoDecoderDestroyed(this); @@ -427,6 +457,8 @@ GMPVideoDecoderParent::Recv__delete__() void GMPVideoDecoderParent::UnblockResetAndDrain() { + LOGD(("GMPVideoDecoderParent[%p]::UnblockResetAndDrain()", this)); + if (!mCallback) { MOZ_ASSERT(!mIsAwaitingResetComplete); MOZ_ASSERT(!mIsAwaitingDrainComplete); diff --git a/dom/media/mediasource/MediaSource.cpp b/dom/media/mediasource/MediaSource.cpp index a2268d315db6..1cdf847f2beb 100644 --- a/dom/media/mediasource/MediaSource.cpp +++ b/dom/media/mediasource/MediaSource.cpp @@ -308,14 +308,10 @@ MediaSource::EndOfStream(const Optional& aError, Er } switch (aError.Value()) { case MediaSourceEndOfStreamError::Network: - // TODO: If media element has a readyState of: - // HAVE_NOTHING -> run resource fetch algorithm - // > HAVE_NOTHING -> run "interrupted" steps of resource fetch + mDecoder->NetworkError(); break; case MediaSourceEndOfStreamError::Decode: - // TODO: If media element has a readyState of: - // HAVE_NOTHING -> run "unsupported" steps of resource fetch - // > HAVE_NOTHING -> run "corrupted" steps of resource fetch + mDecoder->DecodeError(); break; default: aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); diff --git a/dom/media/mediasource/SourceBuffer.cpp b/dom/media/mediasource/SourceBuffer.cpp index 7976c8d8aefc..679e730c2224 100644 --- a/dom/media/mediasource/SourceBuffer.cpp +++ b/dom/media/mediasource/SourceBuffer.cpp @@ -550,6 +550,15 @@ SourceBuffer::PrepareAppend(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } + + // If the HTMLMediaElement.error attribute is not null, then throw an + // InvalidStateError exception and abort these steps. + if (!mMediaSource->GetDecoder() || + mMediaSource->GetDecoder()->IsEndedOrShutdown()) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { mMediaSource->SetReadyState(MediaSourceReadyState::Open); } diff --git a/dom/media/mediasource/TrackBuffersManager.cpp b/dom/media/mediasource/TrackBuffersManager.cpp index 61dc83fd99a4..26d4a1b0a2db 100644 --- a/dom/media/mediasource/TrackBuffersManager.cpp +++ b/dom/media/mediasource/TrackBuffersManager.cpp @@ -978,6 +978,13 @@ TrackBuffersManager::OnDemuxerInitDone(nsresult) // 6. Set first initialization segment received flag to true. mFirstInitializationSegmentReceived = true; } else { + // Check that audio configuration hasn't changed as this is something + // we do not support yet (bug 1185827). + if (mAudioTracks.mNumTracks && + (info.mAudio.mChannels != mAudioTracks.mInfo->GetAsAudioInfo()->mChannels || + info.mAudio.mRate != mAudioTracks.mInfo->GetAsAudioInfo()->mRate)) { + RejectAppend(NS_ERROR_FAILURE, __func__); + } mAudioTracks.mLastInfo = new SharedTrackInfo(info.mAudio, streamID); mVideoTracks.mLastInfo = new SharedTrackInfo(info.mVideo, streamID); } diff --git a/dom/media/platforms/android/AndroidDecoderModule.cpp b/dom/media/platforms/android/AndroidDecoderModule.cpp index a90aeea8a6e6..28e9b17112d7 100644 --- a/dom/media/platforms/android/AndroidDecoderModule.cpp +++ b/dom/media/platforms/android/AndroidDecoderModule.cpp @@ -393,6 +393,10 @@ nsresult MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface) #define HANDLE_DECODER_ERROR() \ if (NS_FAILED(res)) { \ NS_WARNING("exiting decoder loop due to exception"); \ + if (mDraining) { \ + ENVOKE_CALLBACK(DrainComplete); \ + mDraining = false; \ + } \ ENVOKE_CALLBACK(Error); \ break; \ } diff --git a/dom/security/nsMixedContentBlocker.cpp b/dom/security/nsMixedContentBlocker.cpp index 901191d49d01..5f6c47dddc93 100644 --- a/dom/security/nsMixedContentBlocker.cpp +++ b/dom/security/nsMixedContentBlocker.cpp @@ -55,8 +55,8 @@ bool nsMixedContentBlocker::sBlockMixedDisplay = false; class nsMixedContentEvent : public nsRunnable { public: - nsMixedContentEvent(nsISupports *aContext, MixedContentTypes aType) - : mContext(aContext), mType(aType) + nsMixedContentEvent(nsISupports *aContext, MixedContentTypes aType, bool aRootHasSecureConnection) + : mContext(aContext), mType(aType), mRootHasSecureConnection(aRootHasSecureConnection) {} NS_IMETHOD Run() @@ -85,6 +85,21 @@ public: nsCOMPtr rootDoc = do_GetInterface(sameTypeRoot); NS_ASSERTION(rootDoc, "No root document from document shell root tree item."); + // Get eventSink and the current security state from the docShell + nsCOMPtr eventSink = do_QueryInterface(docShell); + NS_ASSERTION(eventSink, "No eventSink from docShell."); + nsCOMPtr rootShell = do_GetInterface(sameTypeRoot); + NS_ASSERTION(rootShell, "No root docshell from document shell root tree item."); + uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN; + nsCOMPtr securityUI; + rootShell->GetSecurityUI(getter_AddRefs(securityUI)); + // If there is no securityUI, document doesn't have a security state to + // update. But we still want to set the document flags, so we don't return + // early. + nsresult stateRV; + if (securityUI) { + stateRV = securityUI->GetState(&state); + } if (mType == eMixedScript) { // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI. @@ -94,18 +109,26 @@ public: rootDoc->SetHasMixedActiveContentLoaded(true); // Update the security UI in the tab with the allowed mixed active content - nsCOMPtr eventSink = do_QueryInterface(docShell); - if (eventSink) { - // If mixed display content is loaded, make sure to include that in the state. - if (rootDoc->GetHasMixedDisplayContentLoaded()) { - eventSink->OnSecurityChange(mContext, - (nsIWebProgressListener::STATE_IS_BROKEN | - nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | - nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); + if (securityUI) { + // Bug 1182551 - before changing the security state to broken, check + // that the root is actually secure. + if (mRootHasSecureConnection) { + // If mixed display content is loaded, make sure to include that in the state. + if (rootDoc->GetHasMixedDisplayContentLoaded()) { + eventSink->OnSecurityChange(mContext, + (nsIWebProgressListener::STATE_IS_BROKEN | + nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); + } else { + eventSink->OnSecurityChange(mContext, + (nsIWebProgressListener::STATE_IS_BROKEN | + nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); + } } else { - eventSink->OnSecurityChange(mContext, - (nsIWebProgressListener::STATE_IS_BROKEN | - nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); + // root not secure, mixed active content loaded in an https subframe + if (NS_SUCCEEDED(stateRV)) { + eventSink->OnSecurityChange(mContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); + } } } @@ -117,17 +140,25 @@ public: rootDoc->SetHasMixedDisplayContentLoaded(true); // Update the security UI in the tab with the allowed mixed display content. - nsCOMPtr eventSink = do_QueryInterface(docShell); - if (eventSink) { + if (securityUI) { + // Bug 1182551 - before changing the security state to broken, check + // that the root is actually secure. + if (mRootHasSecureConnection) { // If mixed active content is loaded, make sure to include that in the state. - if (rootDoc->GetHasMixedActiveContentLoaded()) { - eventSink->OnSecurityChange(mContext, - (nsIWebProgressListener::STATE_IS_BROKEN | - nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT | - nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); + if (rootDoc->GetHasMixedActiveContentLoaded()) { + eventSink->OnSecurityChange(mContext, + (nsIWebProgressListener::STATE_IS_BROKEN | + nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); + } else { + eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN | + nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); + } } else { - eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN | - nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); + // root not secure, mixed display content loaded in an https subframe + if (NS_SUCCEEDED(stateRV)) { + eventSink->OnSecurityChange(mContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); + } } } } @@ -141,6 +172,9 @@ private: // The type of mixed content detected, e.g. active or display const MixedContentTypes mType; + + // Indicates whether the top level load is https or not. + bool mRootHasSecureConnection; }; @@ -673,7 +707,7 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, NS_ASSERTION(eventSink, "No eventSink from docShell."); nsCOMPtr rootShell = do_GetInterface(sameTypeRoot); NS_ASSERTION(rootShell, "No root docshell from document shell root tree item."); - uint32_t State = nsIWebProgressListener::STATE_IS_BROKEN; + uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN; nsCOMPtr securityUI; rootShell->GetSecurityUI(getter_AddRefs(securityUI)); // If there is no securityUI, document doesn't have a security state. @@ -682,7 +716,7 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, *aDecision = nsIContentPolicy::ACCEPT; return NS_OK; } - nsresult stateRV = securityUI->GetState(&State); + nsresult stateRV = securityUI->GetState(&state); // If the content is display content, and the pref says display content should be blocked, block it. if (sBlockMixedDisplay && classification == eMixedDisplay) { @@ -712,7 +746,7 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, // User has overriden the pref and the root is not https; // mixed display content was allowed on an https subframe. if (NS_SUCCEEDED(stateRV)) { - eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); + eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT)); } } } else { @@ -720,7 +754,7 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked); if (!rootDoc->GetHasMixedDisplayContentBlocked() && NS_SUCCEEDED(stateRV)) { rootDoc->SetHasMixedDisplayContentBlocked(true); - eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT)); + eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT)); } } return NS_OK; @@ -755,7 +789,7 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, // User has already overriden the pref and the root is not https; // mixed active content was allowed on an https subframe. if (NS_SUCCEEDED(stateRV)) { - eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); + eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT)); } return NS_OK; } @@ -772,7 +806,7 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, // The user has not overriden the pref, so make sure they still have an option by calling eventSink // which will invoke the doorhanger if (NS_SUCCEEDED(stateRV)) { - eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT)); + eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT)); } return NS_OK; } @@ -786,7 +820,7 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, // Fire the event from a script runner as it is unsafe to run script // from within ShouldLoad nsContentUtils::AddScriptRunner( - new nsMixedContentEvent(aRequestingContext, classification)); + new nsMixedContentEvent(aRequestingContext, classification, rootHasSecureConnection)); *aDecision = ACCEPT; return NS_OK; } diff --git a/dom/security/test/mixedcontentblocker/mochitest.ini b/dom/security/test/mixedcontentblocker/mochitest.ini index f3a16e3e6dcf..f2595e856978 100644 --- a/dom/security/test/mixedcontentblocker/mochitest.ini +++ b/dom/security/test/mixedcontentblocker/mochitest.ini @@ -1,4 +1,5 @@ [DEFAULT] +tags = mcb support-files = file_bug803225_test_mailto.html file_frameNavigation.html diff --git a/dom/xbl/nsXBLPrototypeBinding.cpp b/dom/xbl/nsXBLPrototypeBinding.cpp index 94244d73dc88..8a060152214b 100644 --- a/dom/xbl/nsXBLPrototypeBinding.cpp +++ b/dom/xbl/nsXBLPrototypeBinding.cpp @@ -901,7 +901,8 @@ nsXBLPrototypeBinding::Read(nsIObjectInputStream* aStream, for (; interfaceCount > 0; interfaceCount--) { nsIID iid; - aStream->ReadID(&iid); + rv = aStream->ReadID(&iid); + NS_ENSURE_SUCCESS(rv, rv); mInterfaceTable.Put(iid, mBinding); } diff --git a/dom/xul/XULDocument.cpp b/dom/xul/XULDocument.cpp index 77a5b4553d19..12397a69dcca 100644 --- a/dom/xul/XULDocument.cpp +++ b/dom/xul/XULDocument.cpp @@ -240,9 +240,6 @@ NS_NewXULDocument(nsIXULDocument** result) return NS_ERROR_NULL_POINTER; XULDocument* doc = new XULDocument(); - if (! doc) - return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(doc); nsresult rv; @@ -449,8 +446,6 @@ XULDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, // the interesting work happens below XULDocument::EndLoad, from // the call there to mCurrentPrototype->NotifyLoadDone(). *aDocListener = new CachedChromeStreamListener(this, loaded); - if (! *aDocListener) - return NS_ERROR_OUT_OF_MEMORY; } else { bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled(); @@ -1635,9 +1630,6 @@ XULDocument::AddElementToDocumentPre(Element* aElement) // later. if (listener && !resolved && (mResolutionPhase != nsForwardReference::eDone)) { BroadcasterHookup* hookup = new BroadcasterHookup(this, aElement); - if (! hookup) - return NS_ERROR_OUT_OF_MEMORY; - rv = AddForwardReference(hookup); if (NS_FAILED(rv)) return rv; } @@ -1668,9 +1660,6 @@ XULDocument::AddElementToDocumentPost(Element* aElement) } else { TemplateBuilderHookup* hookup = new TemplateBuilderHookup(aElement); - if (! hookup) - return NS_ERROR_OUT_OF_MEMORY; - rv = AddForwardReference(hookup); if (NS_FAILED(rv)) return rv; @@ -1875,7 +1864,6 @@ XULDocument::Init() // Create our command dispatcher and hook it up. mCommandDispatcher = new nsXULCommandDispatcher(this); - NS_ENSURE_TRUE(mCommandDispatcher, NS_ERROR_OUT_OF_MEMORY); if (gRefCnt++ == 0) { // ensure that the XUL prototype cache is instantiated successfully, @@ -2007,7 +1995,6 @@ XULDocument::PrepareToLoadPrototype(nsIURI* aURI, const char* aCommand, // Create a XUL content sink, a parser, and kick off a load for // the overlay. nsRefPtr sink = new XULContentSinkImpl(); - if (!sink) return NS_ERROR_OUT_OF_MEMORY; rv = sink->Init(this, mCurrentPrototype); NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink"); @@ -2195,9 +2182,6 @@ XULDocument::ContextStack::Push(nsXULPrototypeElement* aPrototype, nsIContent* aElement) { Entry* entry = new Entry; - if (! entry) - return NS_ERROR_OUT_OF_MEMORY; - entry->mPrototype = aPrototype; entry->mElement = aElement; NS_IF_ADDREF(entry->mElement); @@ -2652,9 +2636,6 @@ XULDocument::LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic, // and will let us recover from a missing overlay. ParserObserver* parserObserver = new ParserObserver(this, mCurrentPrototype); - if (! parserObserver) - return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(parserObserver); parser->Parse(aURI, parserObserver); NS_RELEASE(parserObserver); @@ -3632,8 +3613,6 @@ XULDocument::CreateOverlayElement(nsXULPrototypeElement* aPrototype, OverlayForwardReference* fwdref = new OverlayForwardReference(this, element); - if (! fwdref) - return NS_ERROR_OUT_OF_MEMORY; // transferring ownership to ya... rv = AddForwardReference(fwdref); diff --git a/dom/xul/nsXULCommandDispatcher.cpp b/dom/xul/nsXULCommandDispatcher.cpp index e91ab667669a..da104d2d25bc 100644 --- a/dom/xul/nsXULCommandDispatcher.cpp +++ b/dom/xul/nsXULCommandDispatcher.cpp @@ -310,11 +310,7 @@ nsXULCommandDispatcher::AddCommandUpdater(nsIDOMElement* aElement, #endif // If we get here, this is a new updater. Append it to the list. - updater = new Updater(aElement, aEvents, aTargets); - if (! updater) - return NS_ERROR_OUT_OF_MEMORY; - - *link = updater; + *link = new Updater(aElement, aEvents, aTargets); return NS_OK; } diff --git a/dom/xul/nsXULControllers.cpp b/dom/xul/nsXULControllers.cpp index 57869faf29b4..6b795b29a68a 100644 --- a/dom/xul/nsXULControllers.cpp +++ b/dom/xul/nsXULControllers.cpp @@ -52,9 +52,6 @@ NS_NewXULControllers(nsISupports* aOuter, REFNSIID aIID, void** aResult) return NS_ERROR_NO_AGGREGATION; nsXULControllers* controllers = new nsXULControllers(); - if (! controllers) - return NS_ERROR_OUT_OF_MEMORY; - nsresult rv; NS_ADDREF(controllers); rv = controllers->QueryInterface(aIID, aResult); @@ -121,7 +118,6 @@ NS_IMETHODIMP nsXULControllers::InsertControllerAt(uint32_t aIndex, nsIController *controller) { nsXULControllerData* controllerData = new nsXULControllerData(++mCurControllerID, controller); - if (!controllerData) return NS_ERROR_OUT_OF_MEMORY; #ifdef DEBUG nsXULControllerData** inserted = #endif @@ -165,7 +161,6 @@ nsXULControllers::AppendController(nsIController *controller) { // This assigns controller IDs starting at 1 so we can use 0 to test if an ID was obtained nsXULControllerData* controllerData = new nsXULControllerData(++mCurControllerID, controller); - if (!controllerData) return NS_ERROR_OUT_OF_MEMORY; #ifdef DEBUG nsXULControllerData** appended = diff --git a/dom/xul/nsXULElement.cpp b/dom/xul/nsXULElement.cpp index 04a019c68551..3abc01943be6 100644 --- a/dom/xul/nsXULElement.cpp +++ b/dom/xul/nsXULElement.cpp @@ -2278,16 +2278,15 @@ nsXULPrototypeElement::Deserialize(nsIObjectInputStream* aStream, // Read Node Info uint32_t number = 0; nsresult rv = aStream->Read32(&number); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; mNodeInfo = aNodeInfos->SafeElementAt(number, nullptr); if (!mNodeInfo) { return NS_ERROR_UNEXPECTED; } // Read Attributes - nsresult tmp = aStream->Read32(&number); - if (NS_FAILED(tmp)) { - rv = tmp; - } + rv = aStream->Read32(&number); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; mNumAttributes = int32_t(number); if (mNumAttributes > 0) { @@ -2298,10 +2297,8 @@ nsXULPrototypeElement::Deserialize(nsIObjectInputStream* aStream, nsAutoString attributeValue; for (uint32_t i = 0; i < mNumAttributes; ++i) { - tmp = aStream->Read32(&number); - if (NS_FAILED(tmp)) { - rv = tmp; - } + rv = aStream->Read32(&number); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; mozilla::dom::NodeInfo* ni = aNodeInfos->SafeElementAt(number, nullptr); if (!ni) { return NS_ERROR_UNEXPECTED; @@ -2309,31 +2306,25 @@ nsXULPrototypeElement::Deserialize(nsIObjectInputStream* aStream, mAttributes[i].mName.SetTo(ni); - tmp = aStream->ReadString(attributeValue); - if (NS_FAILED(tmp)) { - rv = tmp; - } - tmp = SetAttrAt(i, attributeValue, aDocumentURI); - if (NS_FAILED(tmp)) { - rv = tmp; - } + rv = aStream->ReadString(attributeValue); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + rv = SetAttrAt(i, attributeValue, aDocumentURI); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; } } - tmp = aStream->Read32(&number); - if (NS_FAILED(tmp)) { - rv = tmp; - } + rv = aStream->Read32(&number); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; uint32_t numChildren = int32_t(number); if (numChildren > 0) { - mChildren.SetCapacity(numChildren); + if (!mChildren.SetCapacity(numChildren, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } for (uint32_t i = 0; i < numChildren; i++) { - tmp = aStream->Read32(&number); - if (NS_FAILED(tmp)) { - rv = tmp; - } + rv = aStream->Read32(&number); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; Type childType = (Type)number; nsRefPtr child; @@ -2341,64 +2332,43 @@ nsXULPrototypeElement::Deserialize(nsIObjectInputStream* aStream, switch (childType) { case eType_Element: child = new nsXULPrototypeElement(); - child->mType = childType; - - tmp = child->Deserialize(aStream, aProtoDoc, aDocumentURI, - aNodeInfos); - if (NS_FAILED(tmp)) { - rv = tmp; - } + rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, + aNodeInfos); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; break; case eType_Text: child = new nsXULPrototypeText(); - child->mType = childType; - - tmp = child->Deserialize(aStream, aProtoDoc, aDocumentURI, - aNodeInfos); - if (NS_FAILED(tmp)) { - rv = tmp; - } + rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, + aNodeInfos); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; break; case eType_PI: child = new nsXULPrototypePI(); - child->mType = childType; - - tmp = child->Deserialize(aStream, aProtoDoc, aDocumentURI, - aNodeInfos); - if (NS_FAILED(tmp)) { - rv = tmp; - } + rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, + aNodeInfos); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; break; case eType_Script: { // language version/options obtained during deserialization. - nsXULPrototypeScript* script = new nsXULPrototypeScript(0, 0); - child = script; - child->mType = childType; + nsRefPtr script = new nsXULPrototypeScript(0, 0); - tmp = aStream->ReadBoolean(&script->mOutOfLine); - if (NS_FAILED(tmp)) { - rv = tmp; - } - if (! script->mOutOfLine) { - tmp = script->Deserialize(aStream, aProtoDoc, aDocumentURI, - aNodeInfos); - if (NS_FAILED(tmp)) { - rv = tmp; - } + rv = aStream->ReadBoolean(&script->mOutOfLine); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + if (!script->mOutOfLine) { + rv = script->Deserialize(aStream, aProtoDoc, aDocumentURI, + aNodeInfos); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; } else { nsCOMPtr supports; - tmp = aStream->ReadObject(true, getter_AddRefs(supports)); + rv = aStream->ReadObject(true, getter_AddRefs(supports)); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; script->mSrcURI = do_QueryInterface(supports); - if (NS_FAILED(tmp)) { - rv = tmp; - } - tmp = script->DeserializeOutOfLine(aStream, aProtoDoc); - if (NS_FAILED(tmp)) { - rv = tmp; - } + rv = script->DeserializeOutOfLine(aStream, aProtoDoc); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; } - // If we failed to deserialize, consider deleting 'script'? + + child = script.forget(); break; } default: @@ -2407,6 +2377,7 @@ nsXULPrototypeElement::Deserialize(nsIObjectInputStream* aStream, } MOZ_ASSERT(child, "Don't append null to mChildren"); + MOZ_ASSERT(child->mType == childType); mChildren.AppendElement(child); // Oh dear. Something failed during the deserialization. @@ -2417,7 +2388,7 @@ nsXULPrototypeElement::Deserialize(nsIObjectInputStream* aStream, // death. So, let's just fail now, and propagate that failure // upward so that the ChromeProtocolHandler knows it can't use // a cached chrome channel for this. - if (NS_FAILED(rv)) + if (NS_WARN_IF(NS_FAILED(rv))) return rv; } } @@ -2617,13 +2588,16 @@ nsXULPrototypeScript::Deserialize(nsIObjectInputStream* aStream, nsIURI* aDocumentURI, const nsTArray> *aNodeInfos) { + nsresult rv; NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mScriptObject, "prototype script not well-initialized when deserializing?!"); // Read basic prototype data - aStream->Read32(&mLineNo); - aStream->Read32(&mLangVersion); + rv = aStream->Read32(&mLineNo); + if (NS_FAILED(rv)) return rv; + rv = aStream->Read32(&mLangVersion); + if (NS_FAILED(rv)) return rv; AutoSafeJSContext cx; JS::Rooted global(cx, xpc::CompilationScope()); @@ -2631,8 +2605,8 @@ nsXULPrototypeScript::Deserialize(nsIObjectInputStream* aStream, JSAutoCompartment ac(cx, global); JS::Rooted newScriptObject(cx); - nsresult rv = nsContentUtils::XPConnect()->ReadScript(aStream, cx, - newScriptObject.address()); + rv = nsContentUtils::XPConnect()->ReadScript(aStream, cx, + newScriptObject.address()); NS_ENSURE_SUCCESS(rv, rv); Set(newScriptObject); return NS_OK; @@ -2862,11 +2836,11 @@ nsXULPrototypeText::Deserialize(nsIObjectInputStream* aStream, nsIURI* aDocumentURI, const nsTArray> *aNodeInfos) { - nsresult rv; - - rv = aStream->ReadString(mValue); - - return rv; + nsresult rv = aStream->ReadString(mValue); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; } //---------------------------------------------------------------------- @@ -2905,10 +2879,9 @@ nsXULPrototypePI::Deserialize(nsIObjectInputStream* aStream, nsresult rv; rv = aStream->ReadString(mTarget); - nsresult tmp = aStream->ReadString(mData); - if (NS_FAILED(tmp)) { - rv = tmp; - } + if (NS_FAILED(rv)) return rv; + rv = aStream->ReadString(mData); + if (NS_FAILED(rv)) return rv; return rv; } diff --git a/dom/xul/nsXULPrototypeDocument.cpp b/dom/xul/nsXULPrototypeDocument.cpp index 5e086958154f..1b4b411f1f21 100644 --- a/dom/xul/nsXULPrototypeDocument.cpp +++ b/dom/xul/nsXULPrototypeDocument.cpp @@ -54,8 +54,6 @@ nsresult nsXULPrototypeDocument::Init() { mNodeInfoManager = new nsNodeInfoManager(); - NS_ENSURE_TRUE(mNodeInfoManager, NS_ERROR_OUT_OF_MEMORY); - return mNodeInfoManager->Init(nullptr); } @@ -149,8 +147,6 @@ nsXULPrototypeDocument::Read(nsIObjectInputStream* aStream) mNodeInfoManager->SetDocumentPrincipal(principal); mRoot = new nsXULPrototypeElement(); - if (! mRoot) - return NS_ERROR_OUT_OF_MEMORY; // mozilla::dom::NodeInfo table nsTArray> nodeInfos; @@ -208,10 +204,6 @@ nsXULPrototypeDocument::Read(nsIObjectInputStream* aStream) if ((nsXULPrototypeNode::Type)type == nsXULPrototypeNode::eType_PI) { nsRefPtr pi = new nsXULPrototypePI(); - if (! pi) { - rv = NS_ERROR_OUT_OF_MEMORY; - break; - } tmp = pi->Deserialize(aStream, this, mURI, &nodeInfos); if (NS_FAILED(tmp)) { diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp index c384671d6929..51edbda5c4af 100644 --- a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp +++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp @@ -163,24 +163,34 @@ NS_IMETHODIMP WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD) { nsCOMPtr window = mDocument->GetDefaultView(); - NS_ENSURE_STATE(window); + if (NS_WARN_IF(!window)) { + aCD.SetIsVoid(true); + return NS_OK; + } nsCOMPtr utils = do_GetInterface(window); - NS_ENSURE_STATE(utils); - return utils->GetDocumentMetadata( + if (NS_WARN_IF(!utils)) { + aCD.SetIsVoid(true); + return NS_OK; + } + nsresult rv = utils->GetDocumentMetadata( NS_LITERAL_STRING("content-disposition"), aCD); + if (NS_WARN_IF(NS_FAILED(rv))) { + aCD.SetIsVoid(true); + } + return NS_OK; } NS_IMETHODIMP WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey) { - nsCOMPtr history; - nsresult rv = GetHistory(getter_AddRefs(history)); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_STATE(history); + nsCOMPtr history = GetHistory(); + if (!history) { + *aKey = 0; + return NS_OK; + } nsCOMPtr abstractKey; - rv = history->GetCacheKey(getter_AddRefs(abstractKey)); - NS_ENSURE_SUCCESS(rv, rv); - if (!abstractKey) { + nsresult rv = history->GetCacheKey(getter_AddRefs(abstractKey)); + if (NS_WARN_IF(NS_FAILED(rv)) || !abstractKey) { *aKey = 0; return NS_OK; } @@ -195,29 +205,37 @@ WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey) NS_IMETHODIMP WebBrowserPersistLocalDocument::GetPostData(nsIInputStream** aStream) { - nsCOMPtr history; - nsresult rv = GetHistory(getter_AddRefs(history)); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_STATE(history); + nsCOMPtr history = GetHistory(); + if (!history) { + *aStream = nullptr; + return NS_OK; + } return history->GetPostData(aStream); } -nsresult -WebBrowserPersistLocalDocument::GetHistory(nsISHEntry** aHistory) +already_AddRefed +WebBrowserPersistLocalDocument::GetHistory() { nsCOMPtr window = mDocument->GetDefaultView(); - NS_ENSURE_STATE(window); + if (NS_WARN_IF(!window)) { + return nullptr; + } nsCOMPtr webNav = do_GetInterface(window); - NS_ENSURE_STATE(webNav); + if (NS_WARN_IF(!webNav)) { + return nullptr; + } nsCOMPtr desc = do_QueryInterface(webNav); - NS_ENSURE_STATE(desc); + if (NS_WARN_IF(!desc)) { + return nullptr; + } nsCOMPtr curDesc; nsresult rv = desc->GetCurrentDescriptor(getter_AddRefs(curDesc)); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_STATE(curDesc); + // This can fail if, e.g., the document is a Print Preview. + if (NS_FAILED(rv) || NS_WARN_IF(!curDesc)) { + return nullptr; + } nsCOMPtr history = do_QueryInterface(curDesc); - history.forget(aHistory); - return NS_OK; + return history.forget(); } const nsCString& @@ -328,7 +346,10 @@ ResourceReader::OnWalkSubframe(nsIDOMNode* aNode) NS_ENSURE_STATE(loader); ++mOutstandingDocuments; - nsresult rv = loader->StartPersistence(this); + // Pass in 0 as the outer window ID so that we start + // persisting the root of this subframe, and not some other + // subframe child of this subframe. + nsresult rv = loader->StartPersistence(0, this); if (NS_FAILED(rv)) { if (rv == NS_ERROR_NO_CONTENT) { // Just ignore frames with no content document. diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h index 23176ef306fe..9110d6c35553 100644 --- a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h +++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h @@ -41,7 +41,7 @@ private: nsresult GetDocEncoder(const nsACString& aContentType, uint32_t aEncoderFlags, nsIDocumentEncoder** aEncoder); - nsresult GetHistory(nsISHEntry** aHistory); + already_AddRefed GetHistory(); virtual ~WebBrowserPersistLocalDocument(); }; diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp index 443f26f4faef..fed58f73fa0d 100644 --- a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp @@ -37,7 +37,11 @@ WebBrowserPersistResourcesChild::VisitDocument(nsIWebBrowserPersistDocument* aDo { auto* subActor = new WebBrowserPersistDocumentChild(); dom::PBrowserChild* grandManager = Manager()->Manager(); - if (!grandManager->SendPWebBrowserPersistDocumentConstructor(subActor)) { + // As a consequence of how PWebBrowserPersistDocumentConstructor can be + // sent by both the parent and the child, we must pass the outerWindowID + // argument here. We pass 0, though note that this argument is actually + // just ignored when passed up to the parent from the child. + if (!grandManager->SendPWebBrowserPersistDocumentConstructor(subActor, 0)) { // NOTE: subActor is freed at this point. return NS_ERROR_FAILURE; } diff --git a/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl b/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl index f57b383b830c..39ea6b33b034 100644 --- a/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl +++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl @@ -22,9 +22,20 @@ interface nsIWebBrowserPersistDocumentReceiver; * @see nsIWebBrowserPersistDocumentReceiver * @see nsIWebBrowserPersistDocument * @see nsIWebBrowserPersist + * + * @param aOuterWindowID + * The outer window ID of the subframe we'd like to persist. + * If set at 0, nsIWebBrowserPersistable will attempt to persist + * the top-level document. If the outer window ID is for a subframe + * that does not exist, or is not held beneath the nsIWebBrowserPersistable, + * aRecv's onError method will be called with NS_ERROR_NO_CONTENT. + * @param aRecv + * The nsIWebBrowserPersistDocumentReceiver is a callback that + * will be fired once the document is ready for persisting. */ -[scriptable, function, uuid(24d0dc9e-b970-4cca-898f-cbba03abaa73)] +[scriptable, uuid(f4c3fa8e-83e9-49f8-ac6f-951fc7541fe4)] interface nsIWebBrowserPersistable : nsISupports { - void startPersistence(in nsIWebBrowserPersistDocumentReceiver aRecv); + void startPersistence(in unsigned long long aOuterWindowID, + in nsIWebBrowserPersistDocumentReceiver aRecv); }; diff --git a/gfx/2d/moz.build b/gfx/2d/moz.build index 21eb5a9e3f3b..e2618bc89f39 100644 --- a/gfx/2d/moz.build +++ b/gfx/2d/moz.build @@ -37,6 +37,7 @@ EXPORTS.mozilla.gfx += [ 'ScaleFactor.h', 'ScaleFactors2D.h', 'SourceSurfaceCairo.h', + 'StackArray.h', 'Tools.h', 'Types.h', 'UserData.h', diff --git a/gfx/layers/composite/ImageHost.cpp b/gfx/layers/composite/ImageHost.cpp index d38fdf2d54cd..536623a8bb01 100644 --- a/gfx/layers/composite/ImageHost.cpp +++ b/gfx/layers/composite/ImageHost.cpp @@ -281,6 +281,9 @@ ImageHost::Composite(LayerComposite* aLayer, TimedImage* img = &mImages[imageIndex]; // Make sure the front buffer has a compositor img->mFrontBuffer->SetCompositor(GetCompositor()); + if (img->mTextureSource) { + img->mTextureSource->SetCompositor(GetCompositor()); + } { AutoLockCompositableHost autoLock(this); diff --git a/gfx/layers/d3d11/CompositorD3D11.cpp b/gfx/layers/d3d11/CompositorD3D11.cpp index 278234a0e885..87eac4d19c3c 100644 --- a/gfx/layers/d3d11/CompositorD3D11.cpp +++ b/gfx/layers/d3d11/CompositorD3D11.cpp @@ -18,6 +18,7 @@ #include "gfxPrefs.h" #include "gfxCrashReporterUtils.h" #include "gfxVR.h" +#include "mozilla/gfx/StackArray.h" #include "mozilla/EnumeratedArray.h" @@ -1091,23 +1092,20 @@ CompositorD3D11::EndFrame() DXGI_PRESENT_PARAMETERS params; PodZero(¶ms); params.DirtyRectsCount = mInvalidRegion.GetNumRects(); - std::vector rects; - rects.reserve(params.DirtyRectsCount); + StackArray rects(params.DirtyRectsCount); nsIntRegionRectIterator iter(mInvalidRegion); const IntRect* r; uint32_t i = 0; while ((r = iter.Next()) != nullptr) { - RECT rect; - rect.left = r->x; - rect.top = r->y; - rect.bottom = r->YMost(); - rect.right = r->XMost(); - - rects.push_back(rect); + rects[i].left = r->x; + rects[i].top = r->y; + rects[i].bottom = r->YMost(); + rects[i].right = r->XMost(); + i++; } - params.pDirtyRects = &rects.front(); + params.pDirtyRects = rects.data(); chain->Present1(presentInterval, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0, ¶ms); } else { mSwapChain->Present(presentInterval, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0); diff --git a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp index db4f7bbdb42e..bea27ebd1765 100644 --- a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp +++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp @@ -136,6 +136,9 @@ void MacIOSurfaceTextureSourceOGL::SetCompositor(Compositor* aCompositor) { mCompositor = static_cast(aCompositor); + if (mNextSibling) { + mNextSibling->SetCompositor(aCompositor); + } } gl::GLContext* diff --git a/gfx/layers/opengl/TextureHostOGL.cpp b/gfx/layers/opengl/TextureHostOGL.cpp index ea8b66a62221..e8f7d56409d1 100644 --- a/gfx/layers/opengl/TextureHostOGL.cpp +++ b/gfx/layers/opengl/TextureHostOGL.cpp @@ -335,6 +335,9 @@ GLTextureSource::SetCompositor(Compositor* aCompositor) { MOZ_ASSERT(aCompositor); mCompositor = static_cast(aCompositor); + if (mNextSibling) { + mNextSibling->SetCompositor(aCompositor); + } } bool diff --git a/gfx/src/DriverCrashGuard.cpp b/gfx/src/DriverCrashGuard.cpp index 448fb1e300ba..a4384e113a65 100644 --- a/gfx/src/DriverCrashGuard.cpp +++ b/gfx/src/DriverCrashGuard.cpp @@ -157,8 +157,6 @@ DriverCrashGuard::GetGuardFile() void DriverCrashGuard::ActivateGuard() { - MOZ_ASSERT(XRE_IsParentProcess()); - mGuardActivated = true; #ifdef MOZ_CRASHREPORTER diff --git a/image/DecodePool.cpp b/image/DecodePool.cpp index 09ed7e965f05..209b481bf069 100644 --- a/image/DecodePool.cpp +++ b/image/DecodePool.cpp @@ -451,7 +451,10 @@ DecodePool::Decode(Decoder* aDecoder) nsresult rv = aDecoder->Decode(); if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) { - if (aDecoder->HasProgress()) { + // If this isn't a metadata decode, notify for the progress we've made so + // far. It's important that metadata decode results are delivered + // atomically, so for those decodes we wait until NotifyDecodeComplete. + if (aDecoder->HasProgress() && !aDecoder->IsMetadataDecode()) { NotifyProgress(aDecoder); } // The decoder will ensure that a new worker gets enqueued to continue diff --git a/image/Decoder.cpp b/image/Decoder.cpp index 1d95d65f86e4..d663dcb4c27d 100644 --- a/image/Decoder.cpp +++ b/image/Decoder.cpp @@ -37,10 +37,8 @@ Decoder::Decoder(RasterImage* aImage) , mMetadataDecode(false) , mSendPartialInvalidations(false) , mImageIsTransient(false) - , mImageIsLocked(false) , mFirstFrameDecode(false) , mInFrame(false) - , mIsAnimated(false) , mDataDone(false) , mDecodeDone(false) , mDataError(false) @@ -237,7 +235,7 @@ Decoder::CompleteDecode() // If this image wasn't animated and isn't a transient image, mark its frame // as optimizable. We don't support optimizing animated images and // optimizing transient images isn't worth it. - if (!mIsAnimated && !mImageIsTransient && mCurrentFrame) { + if (!HasAnimation() && !mImageIsTransient && mCurrentFrame) { mCurrentFrame->SetOptimizable(); } } @@ -260,7 +258,12 @@ Decoder::AllocateFrame(uint32_t aFrameNum, mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize); if (aFrameNum + 1 == mFrameCount) { - PostFrameStart(); + // If we're past the first frame, PostIsAnimated() should've been called. + MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation()); + + // Update our state to reflect the new frame + MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!"); + mInFrame = true; } } else { PostDataError(); @@ -406,19 +409,11 @@ Decoder::PostHasTransparency() } void -Decoder::PostFrameStart() +Decoder::PostIsAnimated(int32_t aFirstFrameTimeout) { - // We shouldn't already be mid-frame - MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!"); - - // Update our state to reflect the new frame - mInFrame = true; - - // If we just became animated, record that fact. - if (mFrameCount > 1) { - mIsAnimated = true; - mProgress |= FLAG_IS_ANIMATED; - } + mProgress |= FLAG_IS_ANIMATED; + mImageMetadata.SetHasAnimation(); + mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout); } void @@ -442,7 +437,7 @@ Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */, // If we're not sending partial invalidations, then we send an invalidation // here when the first frame is complete. - if (!mSendPartialInvalidations && !mIsAnimated) { + if (!mSendPartialInvalidations && !HasAnimation()) { mInvalidRect.UnionRect(mInvalidRect, gfx::IntRect(gfx::IntPoint(0, 0), GetSize())); } @@ -459,7 +454,7 @@ Decoder::PostInvalidation(const nsIntRect& aRect, // Record this invalidation, unless we're not sending partial invalidations // or we're past the first frame. - if (mSendPartialInvalidations && !mIsAnimated) { + if (mSendPartialInvalidations && !HasAnimation()) { mInvalidRect.UnionRect(mInvalidRect, aRect); mCurrentFrame->ImageUpdated(aRectAtTargetSize.valueOr(aRect)); } diff --git a/image/Decoder.h b/image/Decoder.h index 9ec512c9e497..7e8f7624f0d6 100644 --- a/image/Decoder.h +++ b/image/Decoder.h @@ -181,20 +181,6 @@ public: mImageIsTransient = aIsTransient; } - /** - * Set whether the image is locked for the lifetime of this decoder. We lock - * the image during our initial decode to ensure that we don't evict any - * surfaces before we realize that the image is animated. - */ - void SetImageIsLocked() - { - MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet"); - mImageIsLocked = true; - } - - bool ImageIsLocked() const { return mImageIsLocked; } - - /** * Set whether we should stop decoding after the first frame. */ @@ -225,7 +211,7 @@ public: } // Did we discover that the image we're decoding is animated? - bool HasAnimation() const { return mIsAnimated; } + bool HasAnimation() const { return mImageMetadata.HasAnimation(); } // Error tracking bool HasError() const { return HasDataError() || HasDecoderError(); } @@ -344,9 +330,11 @@ protected: // actual contents of the frame and give a more accurate result. void PostHasTransparency(); - // Called by decoders when they begin a frame. Informs the image, sends - // notifications, and does internal book-keeping. - void PostFrameStart(); + // Called by decoders if they determine that the image is animated. + // + // @param aTimeout The time for which the first frame should be shown before + // we advance to the next frame. + void PostIsAnimated(int32_t aFirstFrameTimeout); // Called by decoders when they end a frame. Informs the image, sends // notifications, and does internal book-keeping. @@ -451,10 +439,8 @@ private: bool mMetadataDecode : 1; bool mSendPartialInvalidations : 1; bool mImageIsTransient : 1; - bool mImageIsLocked : 1; bool mFirstFrameDecode : 1; bool mInFrame : 1; - bool mIsAnimated : 1; bool mDataDone : 1; bool mDecodeDone : 1; bool mDataError : 1; diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp index 932a2c9e3647..6aecfa1fe64b 100644 --- a/image/DecoderFactory.cpp +++ b/image/DecoderFactory.cpp @@ -113,8 +113,7 @@ DecoderFactory::CreateDecoder(DecoderType aType, int aSampleSize, const IntSize& aResolution, bool aIsRedecode, - bool aImageIsTransient, - bool aImageIsLocked) + bool aImageIsTransient) { if (aType == DecoderType::UNKNOWN) { return nullptr; @@ -131,10 +130,7 @@ DecoderFactory::CreateDecoder(DecoderType aType, decoder->SetResolution(aResolution); decoder->SetSendPartialInvalidations(!aIsRedecode); decoder->SetImageIsTransient(aImageIsTransient); - - if (aImageIsLocked) { - decoder->SetImageIsLocked(); - } + decoder->SetIsFirstFrameDecode(); // Set a target size for downscale-during-decode if applicable. if (aTargetSize) { @@ -152,6 +148,39 @@ DecoderFactory::CreateDecoder(DecoderType aType, return decoder.forget(); } +/* static */ already_AddRefed +DecoderFactory::CreateAnimationDecoder(DecoderType aType, + RasterImage* aImage, + SourceBuffer* aSourceBuffer, + uint32_t aFlags, + const IntSize& aResolution) +{ + if (aType == DecoderType::UNKNOWN) { + return nullptr; + } + + MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG, + "Calling CreateAnimationDecoder for non-animating DecoderType"); + + nsRefPtr decoder = + GetDecoder(aType, aImage, /* aIsRedecode = */ true); + MOZ_ASSERT(decoder, "Should have a decoder now"); + + // Initialize the decoder. + decoder->SetMetadataDecode(false); + decoder->SetIterator(aSourceBuffer->Iterator()); + decoder->SetFlags(aFlags); + decoder->SetResolution(aResolution); + decoder->SetSendPartialInvalidations(false); + + decoder->Init(); + if (NS_FAILED(decoder->GetDecoderError())) { + return nullptr; + } + + return decoder.forget(); +} + /* static */ already_AddRefed DecoderFactory::CreateMetadataDecoder(DecoderType aType, RasterImage* aImage, diff --git a/image/DecoderFactory.h b/image/DecoderFactory.h index 358c47212aff..01a5e78ca1ae 100644 --- a/image/DecoderFactory.h +++ b/image/DecoderFactory.h @@ -38,12 +38,13 @@ public: static DecoderType GetDecoderType(const char* aMimeType); /** - * Creates and initializes a decoder of type @aType. The decoder will send - * notifications to @aImage. + * Creates and initializes a decoder for non-animated images of type @aType. + * (If the image *is* animated, only the first frame will be decoded.) The + * decoder will send notifications to @aImage. * - * XXX(seth): @aIsRedecode, @aImageIsTransient, and @aImageIsLocked should - * really be part of @aFlags. This requires changes to the way that decoder - * flags work, though. See bug 1185800. + * XXX(seth): @aIsRedecode and @aImageIsTransient should really be part of + * @aFlags. This requires changes to the way that decoder flags work, though. + * See bug 1185800. * * @param aType Which type of decoder to create - JPEG, PNG, etc. * @param aImage The image will own the decoder and which should receive @@ -62,9 +63,6 @@ public: * empty rect if none). * @param aIsRedecode Specify 'true' if this image has been decoded before. * @param aImageIsTransient Specify 'true' if this image is transient. - * @param aImageIsLocked Specify 'true' if this image is locked for the - * lifetime of this decoder, and should be unlocked - * when the decoder finishes. */ static already_AddRefed CreateDecoder(DecoderType aType, @@ -75,8 +73,28 @@ public: int aSampleSize, const gfx::IntSize& aResolution, bool aIsRedecode, - bool aImageIsTransient, - bool aImageIsLocked); + bool aImageIsTransient); + + /** + * Creates and initializes a decoder for animated images of type @aType. + * The decoder will send notifications to @aImage. + * + * @param aType Which type of decoder to create - JPEG, PNG, etc. + * @param aImage The image will own the decoder and which should receive + * notifications as decoding progresses. + * @param aSourceBuffer The SourceBuffer which the decoder will read its data + * from. + * @param aFlags Flags specifying what type of output the decoder should + * produce; see GetDecodeFlags() in RasterImage.h. + * @param aResolution The resolution requested using #-moz-resolution (or an + * empty rect if none). + */ + static already_AddRefed + CreateAnimationDecoder(DecoderType aType, + RasterImage* aImage, + SourceBuffer* aSourceBuffer, + uint32_t aFlags, + const gfx::IntSize& aResolution); /** * Creates and initializes a metadata decoder of type @aType. This decoder diff --git a/image/FrameAnimator.cpp b/image/FrameAnimator.cpp index 6fdaa15731ac..316e2e115192 100644 --- a/image/FrameAnimator.cpp +++ b/image/FrameAnimator.cpp @@ -291,14 +291,19 @@ FrameAnimator::GetCompositedFrame(uint32_t aFrameNum) int32_t FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const { + int32_t rawTimeout = 0; + RawAccessFrameRef frame = GetRawFrame(aFrameNum); - if (!frame) { + if (frame) { + AnimationData data = frame->GetAnimationData(); + rawTimeout = data.mRawTimeout; + } else if (aFrameNum == 0) { + rawTimeout = mFirstFrameTimeout; + } else { NS_WARNING("No frame; called GetTimeoutForFrame too early?"); return 100; } - AnimationData data = frame->GetAnimationData(); - // Ensure a minimal time between updates so we don't throttle the UI thread. // consider 0 == unspecified and make it fast but not too fast. Unless we // have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug @@ -312,11 +317,11 @@ FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const // It seems that there are broken tools out there that set a 0ms or 10ms // timeout when they really want a "default" one. So munge values in that // range. - if (data.mRawTimeout >= 0 && data.mRawTimeout <= 10) { + if (rawTimeout >= 0 && rawTimeout <= 10) { return 100; } - return data.mRawTimeout; + return rawTimeout; } static void diff --git a/image/FrameAnimator.h b/image/FrameAnimator.h index a404d01c714f..95a2a13b910b 100644 --- a/image/FrameAnimator.h +++ b/image/FrameAnimator.h @@ -33,6 +33,7 @@ public: , mLoopRemainingCount(-1) , mLastCompositedFrameIndex(-1) , mLoopCount(-1) + , mFirstFrameTimeout(0) , mAnimationMode(aAnimationMode) , mDoneDecoding(false) { } @@ -148,6 +149,12 @@ public: void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; } int32_t LoopCount() const { return mLoopCount; } + /* + * Set the timeout for the first frame. This is used to allow animation + * scheduling even before a full decode runs for this image. + */ + void SetFirstFrameTimeout(int32_t aTimeout) { mFirstFrameTimeout = aTimeout; } + /** * Collect an accounting of the memory occupied by the compositing surfaces we * use during animation playback. All of the actual animation frames are @@ -277,6 +284,9 @@ private: // data //! The total number of loops for the image. int32_t mLoopCount; + //! The timeout for the first frame of this image. + int32_t mFirstFrameTimeout; + //! The animation mode of this image. Constants defined in imgIContainer. uint16_t mAnimationMode; diff --git a/image/ImageMetadata.cpp b/image/ImageMetadata.cpp deleted file mode 100644 index 236228520a3e..000000000000 --- a/image/ImageMetadata.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* -*- Mode: C++; 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/. */ - -#include "ImageMetadata.h" - -#include "RasterImage.h" -#include "nsComponentManagerUtils.h" -#include "nsISupportsPrimitives.h" -#include "nsXPCOMCID.h" - -namespace mozilla { -namespace image { - -nsresult -ImageMetadata::SetOnImage(RasterImage* aImage) -{ - nsresult rv = NS_OK; - - if (mHotspotX != -1 && mHotspotY != -1) { - nsCOMPtr intwrapx = - do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID); - nsCOMPtr intwrapy = - do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID); - intwrapx->SetData(mHotspotX); - intwrapy->SetData(mHotspotY); - aImage->Set("hotspotX", intwrapx); - aImage->Set("hotspotY", intwrapy); - } - - aImage->SetLoopCount(mLoopCount); - - if (HasSize()) { - MOZ_ASSERT(HasOrientation(), "Should have orientation"); - rv = aImage->SetSize(GetWidth(), GetHeight(), GetOrientation()); - } - - return rv; -} - -} // namespace image -} // namespace mozilla diff --git a/image/ImageMetadata.h b/image/ImageMetadata.h index c257612e0213..defa56af4c00 100644 --- a/image/ImageMetadata.h +++ b/image/ImageMetadata.h @@ -22,23 +22,26 @@ class ImageMetadata { public: ImageMetadata() - : mHotspotX(-1) - , mHotspotY(-1) - , mLoopCount(-1) + : mLoopCount(-1) + , mFirstFrameTimeout(0) + , mHasAnimation(false) { } - // Set the metadata this object represents on an image. - nsresult SetOnImage(RasterImage* aImage); - - void SetHotspot(uint16_t hotspotx, uint16_t hotspoty) + void SetHotspot(uint16_t aHotspotX, uint16_t aHotspotY) { - mHotspotX = hotspotx; - mHotspotY = hotspoty; + mHotspot = Some(gfx::IntPoint(aHotspotX, aHotspotY)); } + gfx::IntPoint GetHotspot() const { return *mHotspot; } + bool HasHotspot() const { return mHotspot.isSome(); } + void SetLoopCount(int32_t loopcount) { mLoopCount = loopcount; } + int32_t GetLoopCount() const { return mLoopCount; } + + void SetFirstFrameTimeout(int32_t aTimeout) { mFirstFrameTimeout = aTimeout; } + int32_t GetFirstFrameTimeout() const { return mFirstFrameTimeout; } void SetSize(int32_t width, int32_t height, Orientation orientation) { @@ -47,25 +50,28 @@ public: mOrientation.emplace(orientation); } } - + nsIntSize GetSize() const { return *mSize; } + Orientation GetOrientation() const { return *mOrientation; } bool HasSize() const { return mSize.isSome(); } bool HasOrientation() const { return mOrientation.isSome(); } - int32_t GetWidth() const { return mSize->width; } - int32_t GetHeight() const { return mSize->height; } - nsIntSize GetSize() const { return *mSize; } - Orientation GetOrientation() const { return *mOrientation; } + void SetHasAnimation() { mHasAnimation = true; } + bool HasAnimation() const { return mHasAnimation; } private: - // The hotspot found on cursors, or -1 if none was found. - int32_t mHotspotX; - int32_t mHotspotY; + /// The hotspot found on cursors, if present. + Maybe mHotspot; - // The loop count for animated images, or -1 for infinite loop. + /// The loop count for animated images, or -1 for infinite loop. int32_t mLoopCount; + /// The timeout of an animated image's first frame. + int32_t mFirstFrameTimeout; + Maybe mSize; Maybe mOrientation; + + bool mHasAnimation : 1; }; } // namespace image diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp index da062530a912..22168ab9961e 100644 --- a/image/RasterImage.cpp +++ b/image/RasterImage.cpp @@ -24,6 +24,7 @@ #include "nsIConsoleService.h" #include "nsIInputStream.h" #include "nsIScriptError.h" +#include "nsISupportsPrimitives.h" #include "nsPresContext.h" #include "SourceBuffer.h" #include "SurfaceCache.h" @@ -478,7 +479,7 @@ RasterImage::LookupFrame(uint32_t aFrameNum, // We don't have a copy of this frame, and there's no decoder working on // one. (Or we're sync decoding and the existing decoder hasn't even started // yet.) Trigger decoding so it'll be available next time. - MOZ_ASSERT(!mAnim, "Animated frames should be locked"); + MOZ_ASSERT(!mAnim || GetNumFrames() < 1, "Animated frames should be locked"); Decode(requestedSize, aFlags); @@ -529,7 +530,7 @@ RasterImage::GetRequestedFrameIndex(uint32_t aWhichFrame) const IntRect RasterImage::GetFirstFrameRect() { - if (mAnim) { + if (mAnim && mHasBeenDecoded) { return mAnim->GetFirstFrameRefreshArea(); } @@ -582,7 +583,10 @@ RasterImage::GetAnimated(bool* aAnimated) } // Otherwise, we need to have been decoded to know for sure, since if we were - // decoded at least once mAnim would have been created for animated images + // decoded at least once mAnim would have been created for animated images. + // This is true even though we check for animation during the metadata decode, + // because we may still discover animation only during the full decode for + // corrupt images. if (!mHasBeenDecoded) { return NS_ERROR_NOT_AVAILABLE; } @@ -921,21 +925,9 @@ RasterImage::OnAddedFrame(uint32_t aNewFrameCount, mFrameCount = aNewFrameCount; if (aNewFrameCount == 2) { - // We're becoming animated, so initialize animation stuff. - MOZ_ASSERT(!mAnim, "Already have animation state?"); - mAnim = MakeUnique(this, mSize, mAnimationMode); - - // We don't support discarding animated images (See bug 414259). - // Lock the image and throw away the key. - // - // Note that this is inefficient, since we could get rid of the source - // data too. However, doing this is actually hard, because we're probably - // mid-decode, and thus we're decoding out of the source buffer. Since - // we're going to fix this anyway later, and since we didn't kill the - // source data in the old world either, locking is acceptable for the - // moment. - LockImage(); + MOZ_ASSERT(mAnim, "Should already have animation state"); + // We may be able to start animating. if (mPendingAnimation && ShouldAnimate()) { StartAnimation(); } @@ -947,7 +939,8 @@ RasterImage::OnAddedFrame(uint32_t aNewFrameCount, } nsresult -RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation) +RasterImage::SetMetadata(const ImageMetadata& aMetadata, + bool aFromMetadataDecode) { MOZ_ASSERT(NS_IsMainThread()); @@ -955,26 +948,64 @@ RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation) return NS_ERROR_FAILURE; } - // Ensure that we have positive values - // XXX - Why isn't the size unsigned? Should this be changed? - if ((aWidth < 0) || (aHeight < 0)) { - return NS_ERROR_INVALID_ARG; + if (aMetadata.HasSize()) { + IntSize size = aMetadata.GetSize(); + if (size.width < 0 || size.height < 0) { + return NS_ERROR_INVALID_ARG; + } + + MOZ_ASSERT(aMetadata.HasOrientation()); + Orientation orientation = aMetadata.GetOrientation(); + + // If we already have a size, check the new size against the old one. + if (mHasSize && (size != mSize || orientation != mOrientation)) { + NS_WARNING("Image changed size or orientation on redecode! " + "This should not happen!"); + DoError(); + return NS_ERROR_UNEXPECTED; + } + + // Set the size and flag that we have it. + mSize = size; + mOrientation = orientation; + mHasSize = true; } - // if we already have a size, check the new size against the old one - if (mHasSize && - ((aWidth != mSize.width) || - (aHeight != mSize.height) || - (aOrientation != mOrientation))) { - NS_WARNING("Image changed size on redecode! This should not happen!"); - DoError(); - return NS_ERROR_UNEXPECTED; + if (mHasSize && aMetadata.HasAnimation() && !mAnim) { + // We're becoming animated, so initialize animation stuff. + mAnim = MakeUnique(this, mSize, mAnimationMode); + + // We don't support discarding animated images (See bug 414259). + // Lock the image and throw away the key. + LockImage(); + + if (!aFromMetadataDecode) { + // The metadata decode reported that this image isn't animated, but we + // discovered that it actually was during the full decode. This is a + // rare failure that only occurs for corrupt images. To recover, we need + // to discard all existing surfaces and redecode. + RecoverFromLossOfFrames(mSize, DECODE_FLAGS_DEFAULT); + } } - // Set the size and flag that we have it - mSize.SizeTo(aWidth, aHeight); - mOrientation = aOrientation; - mHasSize = true; + if (mAnim) { + mAnim->SetLoopCount(aMetadata.GetLoopCount()); + mAnim->SetFirstFrameTimeout(aMetadata.GetFirstFrameTimeout()); + } + + if (aMetadata.HasHotspot()) { + IntPoint hotspot = aMetadata.GetHotspot(); + + nsCOMPtr intwrapx = + do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID); + nsCOMPtr intwrapy = + do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID); + intwrapx->SetData(hotspot.x); + intwrapy->SetData(hotspot.y); + + Set("hotspotX", intwrapx); + Set("hotspotY", intwrapy); + } return NS_OK; } @@ -999,10 +1030,9 @@ RasterImage::StartAnimation() MOZ_ASSERT(ShouldAnimate(), "Should not animate!"); - // If we don't have mAnim yet, then we're not ready to animate. Setting - // mPendingAnimation will cause us to start animating as soon as we have a - // second frame, which causes mAnim to be constructed. - mPendingAnimation = !mAnim; + // If we're not ready to animate, then set mPendingAnimation, which will cause + // us to start animating if and when we do become ready. + mPendingAnimation = !mAnim || GetNumFrames() < 2; if (mPendingAnimation) { return NS_OK; } @@ -1091,19 +1121,6 @@ RasterImage::GetFrameIndex(uint32_t aWhichFrame) : mAnim->GetCurrentAnimationFrameIndex(); } -void -RasterImage::SetLoopCount(int32_t aLoopCount) -{ - if (mError) { - return; - } - - // No need to set this if we're not an animation. - if (mAnim) { - mAnim->SetLoopCount(aLoopCount); - } -} - NS_IMETHODIMP_(IntRect) RasterImage::GetImageSpaceInvalidationRect(const IntRect& aRect) { @@ -1391,20 +1408,19 @@ RasterImage::Decode(const IntSize& aSize, uint32_t aFlags) Maybe targetSize = mSize != aSize ? Some(aSize) : Nothing(); - bool imageIsLocked = false; - if (!mHasBeenDecoded) { - // Lock the image while we're decoding, so that it doesn't get evicted from - // the SurfaceCache before we have a chance to realize that it's animated. - // The corresponding unlock happens in FinalizeDecoder. - LockImage(); - imageIsLocked = true; - } - // Create a decoder. - nsRefPtr decoder = - DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer, targetSize, - aFlags, mRequestedSampleSize, mRequestedResolution, - mHasBeenDecoded, mTransient, imageIsLocked); + nsRefPtr decoder; + if (mAnim) { + decoder = DecoderFactory::CreateAnimationDecoder(mDecoderType, this, + mSourceBuffer, aFlags, + mRequestedResolution); + } else { + decoder = DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer, + targetSize, aFlags, + mRequestedSampleSize, + mRequestedResolution, + mHasBeenDecoded, mTransient); + } // Make sure DecoderFactory was able to create a decoder successfully. if (!decoder) { @@ -1483,6 +1499,11 @@ RasterImage::RecoverFromLossOfFrames(const IntSize& aSize, uint32_t aFlags) // Discard all existing frames, since they're probably all now invalid. SurfaceCache::RemoveImage(ImageKey(this)); + // Relock the image if it's supposed to be locked. + if (mLockCount > 0) { + SurfaceCache::LockImage(ImageKey(this)); + } + // Animated images require some special handling, because we normally require // that they never be discarded. if (mAnim) { @@ -1948,7 +1969,8 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder) } // Record all the metadata the decoder gathered about this image. - nsresult rv = aDecoder->GetImageMetadata().SetOnImage(this); + nsresult rv = SetMetadata(aDecoder->GetImageMetadata(), + aDecoder->IsMetadataDecode()); if (NS_FAILED(rv)) { aDecoder->PostResizeError(); } @@ -1959,17 +1981,8 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder) if (aDecoder->GetDecodeTotallyDone() && !mError) { // Flag that we've been decoded before. mHasBeenDecoded = true; - - if (aDecoder->HasAnimation()) { - if (mAnim) { - mAnim->SetDoneDecoding(true); - } else { - // The OnAddedFrame event that will create mAnim is still in the event - // queue. Wait for it. - nsCOMPtr runnable = - NS_NewRunnableMethod(this, &RasterImage::MarkAnimationDecoded); - NS_DispatchToMainThread(runnable); - } + if (mAnim) { + mAnim->SetDoneDecoding(true); } } @@ -2017,11 +2030,6 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder) } } - if (aDecoder->ImageIsLocked()) { - // Unlock the image, balancing the LockImage call we made in CreateDecoder. - UnlockImage(); - } - // If we were a metadata decode and a full decode was requested, do it. if (done && wasMetadata && mWantFullDecode) { mWantFullDecode = false; @@ -2029,17 +2037,6 @@ RasterImage::FinalizeDecoder(Decoder* aDecoder) } } -void -RasterImage::MarkAnimationDecoded() -{ - MOZ_ASSERT(mAnim, "Should have an animation now"); - if (!mAnim) { - return; - } - - mAnim->SetDoneDecoding(true); -} - void RasterImage::ReportDecoderError(Decoder* aDecoder) { diff --git a/image/RasterImage.h b/image/RasterImage.h index 13a1392b2974..4d591e52166d 100644 --- a/image/RasterImage.h +++ b/image/RasterImage.h @@ -132,6 +132,7 @@ namespace image { class Decoder; class FrameAnimator; +class ImageMetadata; class SourceBuffer; /** @@ -188,18 +189,6 @@ public: void OnAddedFrame(uint32_t aNewFrameCount, const nsIntRect& aNewRefreshArea); - /** Sets the size and inherent orientation of the container. This should only - * be called by the decoder. This function may be called multiple times, but - * will throw an error if subsequent calls do not match the first. - */ - nsresult SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation); - - /** - * Number of times to loop the image. - * @note -1 means forever. - */ - void SetLoopCount(int32_t aLoopCount); - /** * Sends the provided progress notifications to ProgressTracker. * @@ -222,8 +211,7 @@ public: */ void FinalizeDecoder(Decoder* aDecoder); - // Helper methods for FinalizeDecoder. - void MarkAnimationDecoded(); + // Helper method for FinalizeDecoder. void ReportDecoderError(Decoder* aDecoder); @@ -339,6 +327,19 @@ private: */ NS_IMETHOD DecodeMetadata(uint32_t aFlags); + /** + * Sets the size, inherent orientation, animation metadata, and other + * information about the image gathered during decoding. + * + * This function may be called multiple times, but will throw an error if + * subsequent calls do not match the first. + * + * @param aMetadata The metadata to set on this image. + * @param aFromMetadataDecode True if this metadata came from a metadata + * decode; false if it came from a full decode. + */ + nsresult SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode); + /** * In catastrophic circumstances like a GPU driver crash, we may lose our * frames even if they're locked. RecoverFromLossOfFrames discards all diff --git a/image/decoders/icon/mac/nsIconChannelCocoa.mm b/image/decoders/icon/mac/nsIconChannelCocoa.mm index 77e0c31bb020..c65effe7eb28 100644 --- a/image/decoders/icon/mac/nsIconChannelCocoa.mm +++ b/image/decoders/icon/mac/nsIconChannelCocoa.mm @@ -225,6 +225,10 @@ NS_IMETHODIMP nsIconChannel::AsyncOpen(nsIStreamListener* aListener, nsISupports* ctxt) { + MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + nsCOMPtr inStream; nsresult rv = MakeInputStream(getter_AddRefs(inStream), true); NS_ENSURE_SUCCESS(rv, rv); diff --git a/image/decoders/icon/win/nsIconChannel.cpp b/image/decoders/icon/win/nsIconChannel.cpp index f180aa73b4e2..367d46beae46 100644 --- a/image/decoders/icon/win/nsIconChannel.cpp +++ b/image/decoders/icon/win/nsIconChannel.cpp @@ -239,6 +239,10 @@ NS_IMETHODIMP nsIconChannel::AsyncOpen(nsIStreamListener* aListener, nsISupports* ctxt) { + MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + nsCOMPtr inStream; nsresult rv = MakeInputStream(getter_AddRefs(inStream), true); if (NS_FAILED(rv)) { diff --git a/image/decoders/nsGIFDecoder2.cpp b/image/decoders/nsGIFDecoder2.cpp index 898436e11814..a3a4ab86ae9c 100644 --- a/image/decoders/nsGIFDecoder2.cpp +++ b/image/decoders/nsGIFDecoder2.cpp @@ -857,6 +857,11 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) } mGIFStruct.delay_time = GETINT16(q + 1) * 10; + + if (mGIFStruct.delay_time > 0) { + PostIsAnimated(mGIFStruct.delay_time); + } + GETN(1, gif_consume_block); break; @@ -921,11 +926,20 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) break; case gif_image_header: { - if (mGIFStruct.images_decoded > 0 && IsFirstFrameDecode()) { - // We're about to get a second frame, but we only want the first. Stop - // decoding now. - mGIFStruct.state = gif_done; - break; + if (mGIFStruct.images_decoded == 1) { + if (!HasAnimation()) { + // We should've already called PostIsAnimated(); this must be a + // corrupt animated image with a first frame timeout of zero. Signal + // that we're animated now, before the first-frame decode early exit + // below, so that RasterImage can detect that this happened. + PostIsAnimated(/* aFirstFrameTimeout = */ 0); + } + if (IsFirstFrameDecode()) { + // We're about to get a second frame, but we only want the first. Stop + // decoding now. + mGIFStruct.state = gif_done; + break; + } } // Get image offsets, with respect to the screen origin diff --git a/image/decoders/nsICODecoder.cpp b/image/decoders/nsICODecoder.cpp index 643a09b54a2f..8d0fc6511728 100644 --- a/image/decoders/nsICODecoder.cpp +++ b/image/decoders/nsICODecoder.cpp @@ -378,8 +378,8 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount) } if (!HasSize() && mContainedDecoder->HasSize()) { - PostSize(mContainedDecoder->GetImageMetadata().GetWidth(), - mContainedDecoder->GetImageMetadata().GetHeight()); + nsIntSize size = mContainedDecoder->GetSize(); + PostSize(size.width, size.height); } mPos += aCount; @@ -479,8 +479,8 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount) return; } - PostSize(mContainedDecoder->GetImageMetadata().GetWidth(), - mContainedDecoder->GetImageMetadata().GetHeight()); + nsIntSize size = mContainedDecoder->GetSize(); + PostSize(size.width, size.height); // We have the size. If we're doing a metadata decode, we're done. if (IsMetadataDecode()) { diff --git a/image/decoders/nsPNGDecoder.cpp b/image/decoders/nsPNGDecoder.cpp index 9e15fab88b2c..742229b64dcb 100644 --- a/image/decoders/nsPNGDecoder.cpp +++ b/image/decoders/nsPNGDecoder.cpp @@ -56,32 +56,33 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameInfo() { } #ifdef PNG_APNG_SUPPORTED + +int32_t GetNextFrameDelay(png_structp aPNG, png_infop aInfo) +{ + // Delay, in seconds, is delayNum / delayDen. + png_uint_16 delayNum = png_get_next_frame_delay_num(aPNG, aInfo); + png_uint_16 delayDen = png_get_next_frame_delay_den(aPNG, aInfo); + + if (delayNum == 0) { + return 0; // SetFrameTimeout() will set to a minimum. + } + + if (delayDen == 0) { + delayDen = 100; // So says the APNG spec. + } + + // Need to cast delay_num to float to have a proper division and + // the result to int to avoid a compiler warning. + return static_cast(static_cast(delayNum) * 1000 / delayDen); +} + nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo) : mDispose(DisposalMethod::KEEP) , mBlend(BlendMethod::OVER) , mTimeout(0) { - png_uint_16 delay_num, delay_den; - // delay, in seconds is delay_num/delay_den - png_byte dispose_op; - png_byte blend_op; - delay_num = png_get_next_frame_delay_num(aPNG, aInfo); - delay_den = png_get_next_frame_delay_den(aPNG, aInfo); - dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo); - blend_op = png_get_next_frame_blend_op(aPNG, aInfo); - - if (delay_num == 0) { - mTimeout = 0; // SetFrameTimeout() will set to a minimum - } else { - if (delay_den == 0) { - delay_den = 100; // so says the APNG spec - } - - // Need to cast delay_num to float to have a proper division and - // the result to int to avoid compiler warning - mTimeout = static_cast(static_cast(delay_num) * - 1000 / delay_den); - } + png_byte dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo); + png_byte blend_op = png_get_next_frame_blend_op(aPNG, aInfo); if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) { mDispose = DisposalMethod::RESTORE_PREVIOUS; @@ -96,6 +97,8 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo) } else { mBlend = BlendMethod::OVER; } + + mTimeout = GetNextFrameDelay(aPNG, aInfo); } #endif @@ -595,18 +598,25 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) png_longjmp(decoder->mPNG, 1); // invalid number of channels } +#ifdef PNG_APNG_SUPPORTED + bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL); + if (isAnimated) { + decoder->PostIsAnimated(GetNextFrameDelay(png_ptr, info_ptr)); + } +#endif + if (decoder->IsMetadataDecode()) { decoder->CheckForTransparency(decoder->format, IntRect(0, 0, width, height)); - // We have the size and transparency information we're looking for, so we - // don't need to decode any further. + // We have the metadata we're looking for, so we don't need to decode any + // further. decoder->mSuccessfulEarlyFinish = true; png_longjmp(decoder->mPNG, 1); } #ifdef PNG_APNG_SUPPORTED - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) { + if (isAnimated) { png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, nullptr); } diff --git a/image/moz.build b/image/moz.build index 866e9801498d..1a3c93464ddc 100644 --- a/image/moz.build +++ b/image/moz.build @@ -59,7 +59,6 @@ UNIFIED_SOURCES += [ 'Image.cpp', 'ImageCacheKey.cpp', 'ImageFactory.cpp', - 'ImageMetadata.cpp', 'ImageOps.cpp', 'ImageWrapper.cpp', 'imgFrame.cpp', diff --git a/image/test/gtest/Common.cpp b/image/test/gtest/Common.cpp index 548dc11b135a..40946a981528 100644 --- a/image/test/gtest/Common.cpp +++ b/image/test/gtest/Common.cpp @@ -15,6 +15,7 @@ #include "nsIProperties.h" #include "nsNetUtil.h" #include "mozilla/nsRefPtr.h" +#include "nsStreamUtils.h" #include "nsString.h" namespace mozilla { @@ -71,6 +72,15 @@ LoadFile(const char* aRelativePath) rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file); ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr); + // Ensure the resulting input stream is buffered. + if (!NS_InputStreamIsBuffered(inputStream)) { + nsCOMPtr bufStream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), + inputStream, 1024); + ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr); + inputStream = bufStream; + } + return inputStream.forget(); } @@ -144,13 +154,14 @@ ImageTestCase GreenICOTestCase() ImageTestCase GreenFirstFrameAnimatedGIFTestCase() { - return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100)); + return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100), + TEST_CASE_IS_ANIMATED); } ImageTestCase GreenFirstFrameAnimatedPNGTestCase() { return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100), - TEST_CASE_IS_TRANSPARENT); + TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED); } ImageTestCase CorruptTestCase() @@ -198,4 +209,13 @@ ImageTestCase RLE8BMPTestCase() TEST_CASE_IS_TRANSPARENT); } +ImageTestCase NoFrameDelayGIFTestCase() +{ + // This is an invalid (or at least, questionably valid) GIF that's animated + // even though it specifies a frame delay of zero. It's animated, but it's not + // marked TEST_CASE_IS_ANIMATED because the metadata decoder can't detect that + // it's animated. + return ImageTestCase("no-frame-delay.gif", "image/gif", IntSize(100, 100)); +} + } // namespace mozilla diff --git a/image/test/gtest/Common.h b/image/test/gtest/Common.h index f88824bd614e..6864c09a2b32 100644 --- a/image/test/gtest/Common.h +++ b/image/test/gtest/Common.h @@ -21,8 +21,9 @@ enum TestCaseFlags { TEST_CASE_DEFAULT_FLAGS = 0, TEST_CASE_IS_FUZZY = 1 << 0, - TEST_CASE_IS_TRANSPARENT = 1 << 1, - TEST_CASE_HAS_ERROR = 1 << 2 + TEST_CASE_HAS_ERROR = 1 << 1, + TEST_CASE_IS_TRANSPARENT = 1 << 2, + TEST_CASE_IS_ANIMATED = 1 << 3, }; struct ImageTestCase @@ -97,6 +98,7 @@ ImageTestCase CorruptTestCase(); ImageTestCase TransparentPNGTestCase(); ImageTestCase TransparentGIFTestCase(); ImageTestCase FirstFramePaddingGIFTestCase(); +ImageTestCase NoFrameDelayGIFTestCase(); ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase(); ImageTestCase RLE4BMPTestCase(); diff --git a/image/test/gtest/TestMetadata.cpp b/image/test/gtest/TestMetadata.cpp index 9ebe6d0bf925..a01833104e53 100644 --- a/image/test/gtest/TestMetadata.cpp +++ b/image/test/gtest/TestMetadata.cpp @@ -10,6 +10,7 @@ #include "decoders/nsBMPDecoder.h" #include "imgIContainer.h" #include "imgITools.h" +#include "ImageFactory.h" #include "mozilla/gfx/2D.h" #include "nsComponentManagerUtils.h" #include "nsCOMPtr.h" @@ -37,26 +38,22 @@ TEST(ImageMetadata, ImageModuleAvailable) EXPECT_TRUE(imgTools != nullptr); } +enum class BMPAlpha +{ + DISABLED, + ENABLED +}; + static void -CheckMetadata(const ImageTestCase& aTestCase, bool aEnableBMPAlpha = false) +CheckMetadata(const ImageTestCase& aTestCase, + BMPAlpha aBMPAlpha = BMPAlpha::DISABLED) { nsCOMPtr inputStream = LoadFile(aTestCase.mPath); ASSERT_TRUE(inputStream != nullptr); - // Prepare the input stream. - nsresult rv; - if (!NS_InputStreamIsBuffered(inputStream)) { - nsCOMPtr bufStream; - rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), - inputStream, 1024); - if (NS_SUCCEEDED(rv)) { - inputStream = bufStream; - } - } - // Figure out how much data we have. uint64_t length; - rv = inputStream->Available(&length); + nsresult rv = inputStream->Available(&length); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Write the data into a SourceBuffer. @@ -73,7 +70,7 @@ CheckMetadata(const ImageTestCase& aTestCase, bool aEnableBMPAlpha = false) DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, sourceBuffer); ASSERT_TRUE(decoder != nullptr); - if (aEnableBMPAlpha) { + if (aBMPAlpha == BMPAlpha::ENABLED) { static_cast(decoder.get())->SetUseAlphaData(true); } @@ -84,7 +81,8 @@ CheckMetadata(const ImageTestCase& aTestCase, bool aEnableBMPAlpha = false) // (which would indicate that it decoded past the header of the image). Progress metadataProgress = decoder->TakeProgress(); EXPECT_TRUE(0 == (metadataProgress & ~(FLAG_SIZE_AVAILABLE | - FLAG_HAS_TRANSPARENCY))); + FLAG_HAS_TRANSPARENCY | + FLAG_IS_ANIMATED))); // If the test case is corrupt, assert what we can and return early. if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) { @@ -102,18 +100,21 @@ CheckMetadata(const ImageTestCase& aTestCase, bool aEnableBMPAlpha = false) EXPECT_EQ(aTestCase.mSize.width, metadataSize.width); EXPECT_EQ(aTestCase.mSize.height, metadataSize.height); - bool expectTransparency = aEnableBMPAlpha + bool expectTransparency = aBMPAlpha == BMPAlpha::ENABLED ? true : bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT); EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY)); + EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED), + bool(metadataProgress & FLAG_IS_ANIMATED)); + // Create a full decoder, so we can compare the result. decoder = DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, imgIContainer::DECODE_FLAGS_DEFAULT); ASSERT_TRUE(decoder != nullptr); - if (aEnableBMPAlpha) { + if (aBMPAlpha == BMPAlpha::ENABLED) { static_cast(decoder.get())->SetUseAlphaData(true); } @@ -164,17 +165,90 @@ TEST(ImageMetadata, FirstFramePaddingGIF) TEST(ImageMetadata, TransparentBMPWithBMPAlphaOff) { - CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(), - /* aEnableBMPAlpha = */ false); + CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(), BMPAlpha::ENABLED); } TEST(ImageMetadata, TransparentBMPWithBMPAlphaOn) { - CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(), - /* aEnableBMPAlpha = */ true); + CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(), BMPAlpha::ENABLED); } TEST(ImageMetadata, RLE4BMP) { CheckMetadata(RLE4BMPTestCase()); } TEST(ImageMetadata, RLE8BMP) { CheckMetadata(RLE8BMPTestCase()); } TEST(ImageMetadata, Corrupt) { CheckMetadata(CorruptTestCase()); } + +TEST(ImageMetadata, NoFrameDelayGIF) +{ + CheckMetadata(NoFrameDelayGIFTestCase()); +} + +TEST(ImageMetadata, NoFrameDelayGIFFullDecode) +{ + ImageTestCase testCase = NoFrameDelayGIFTestCase(); + + // The previous test (NoFrameDelayGIF) verifies that we *don't* detect that + // this test case is animated, because it has a zero frame delay for the first + // frame. This test verifies that when we do a full decode, we detect the + // animation at that point and successfully decode all the frames. + + // Create an image. + nsRefPtr image = + ImageFactory::CreateAnonymousImage(nsAutoCString(testCase.mMimeType)); + ASSERT_TRUE(!image->HasError()); + + nsCOMPtr inputStream = LoadFile(testCase.mPath); + ASSERT_TRUE(inputStream != nullptr); + + // Figure out how much data we have. + uint64_t length; + nsresult rv = inputStream->Available(&length); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Write the data into the image. + rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0, + static_cast(length)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + // Let the image know we've sent all the data. + rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + nsRefPtr tracker = image->GetProgressTracker(); + tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); + + // Use GetFrame() to force a sync decode of the image. + nsRefPtr surface = + image->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + + // Ensure that the image's metadata meets our expectations. + IntSize imageSize(0, 0); + rv = image->GetWidth(&imageSize.width); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + rv = image->GetHeight(&imageSize.height); + EXPECT_TRUE(NS_SUCCEEDED(rv)); + + EXPECT_EQ(testCase.mSize.width, imageSize.width); + EXPECT_EQ(testCase.mSize.height, imageSize.height); + + Progress imageProgress = tracker->GetProgress(); + + EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false); + EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true); + + // Ensure that we decoded both frames of the image. + LookupResult firstFrameLookupResult = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + imgIContainer::DECODE_FLAGS_DEFAULT, + /* aFrameNum = */ 0)); + EXPECT_EQ(MatchType::EXACT, firstFrameLookupResult.Type()); + + LookupResult secondFrameLookupResult = + SurfaceCache::Lookup(ImageKey(image.get()), + RasterSurfaceKey(imageSize, + imgIContainer::DECODE_FLAGS_DEFAULT, + /* aFrameNum = */ 1)); + EXPECT_EQ(MatchType::EXACT, secondFrameLookupResult.Type()); +} diff --git a/image/test/gtest/moz.build b/image/test/gtest/moz.build index edb595faf44c..45f9f96197fb 100644 --- a/image/test/gtest/moz.build +++ b/image/test/gtest/moz.build @@ -24,6 +24,7 @@ TEST_HARNESS_FILES.gtest += [ 'green.ico', 'green.jpg', 'green.png', + 'no-frame-delay.gif', 'rle4.bmp', 'rle8.bmp', 'transparent.bmp', diff --git a/image/test/gtest/no-frame-delay.gif b/image/test/gtest/no-frame-delay.gif new file mode 100644 index 000000000000..1c50b67431fb Binary files /dev/null and b/image/test/gtest/no-frame-delay.gif differ diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index 32d9280b17cd..5ccb45ac4ff0 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -361,23 +361,6 @@ js::obj_toString(JSContext* cx, unsigned argc, Value* vp) return true; } -/* ES5 15.2.4.3. */ -static bool -obj_toLocaleString(JSContext* cx, unsigned argc, Value* vp) -{ - JS_CHECK_RECURSION(cx, return false); - - CallArgs args = CallArgsFromVp(argc, vp); - - /* Step 1. */ - RootedObject obj(cx, ToObject(cx, args.thisv())); - if (!obj) - return false; - - /* Steps 2-4. */ - RootedId id(cx, NameToId(cx->names().toString)); - return obj->callMethod(cx, id, 0, nullptr, args.rval()); -} bool js::obj_valueOf(JSContext* cx, unsigned argc, Value* vp) @@ -999,7 +982,7 @@ static const JSFunctionSpec object_methods[] = { JS_FN(js_toSource_str, obj_toSource, 0,0), #endif JS_FN(js_toString_str, obj_toString, 0,0), - JS_FN(js_toLocaleString_str, obj_toLocaleString, 0,0), + JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0,JSPROP_DEFINE_LATE), JS_FN(js_valueOf_str, obj_valueOf, 0,0), #if JS_HAS_OBJ_WATCHPOINT JS_FN(js_watch_str, obj_watch, 2,0), diff --git a/js/src/builtin/Object.js b/js/src/builtin/Object.js index f2316b89c665..f4957ec46e9c 100644 --- a/js/src/builtin/Object.js +++ b/js/src/builtin/Object.js @@ -51,6 +51,15 @@ function ObjectIsExtensible(obj) { return IsObject(obj) && std_Reflect_isExtensible(obj); } +/* ES2015 19.1.3.5 Object.prototype.toLocaleString */ +function Object_toLocaleString() { + // Step 1. + var O = this; + + // Step 2. + return O.toString(); +} + function ObjectDefineSetter(name, setter) { var object; if (this === null || this === undefined) diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index 6584662ed3f6..c336b957f773 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -1922,7 +1922,7 @@ TypedObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, Handl uint32_t index; if (IdIsIndex(id, &index)) { if (!receiver.isObject() || obj != &receiver.toObject()) - return SetPropertyByDefining(cx, obj, id, v, receiver, false, result); + return SetPropertyByDefining(cx, obj, id, v, receiver, result); if (index >= uint32_t(typedObj->length())) { JS_ReportErrorNumber(cx, GetErrorMessage, @@ -1948,7 +1948,7 @@ TypedObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, Handl break; if (!receiver.isObject() || obj != &receiver.toObject()) - return SetPropertyByDefining(cx, obj, id, v, receiver, false, result); + return SetPropertyByDefining(cx, obj, id, v, receiver, result); size_t offset = descr->fieldOffset(fieldIndex); Rooted fieldType(cx, &descr->fieldDescr(fieldIndex)); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index bb049f44dd4b..86e917cad62e 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -463,90 +463,6 @@ FoldType(ExclusiveContext* cx, ParseNode* pn, ParseNodeKind kind) return true; } -/* - * Fold two numeric constants. Beware that pn1 and pn2 are recycled, unless - * one of them aliases pn, so you can't safely fetch pn2->pn_next, e.g., after - * a successful call to this function. - */ -static bool -FoldBinaryNumeric(ExclusiveContext* cx, JSOp op, ParseNode* pn1, ParseNode* pn2, - ParseNode* pn) -{ - double d, d2; - int32_t i, j; - - MOZ_ASSERT(pn1->isKind(PNK_NUMBER) && pn2->isKind(PNK_NUMBER)); - d = pn1->pn_dval; - d2 = pn2->pn_dval; - switch (op) { - case JSOP_LSH: - case JSOP_RSH: - i = ToInt32(d); - j = ToInt32(d2); - j &= 31; - d = int32_t((op == JSOP_LSH) ? uint32_t(i) << j : i >> j); - break; - - case JSOP_URSH: - j = ToInt32(d2); - j &= 31; - d = ToUint32(d) >> j; - break; - - case JSOP_ADD: - d += d2; - break; - - case JSOP_SUB: - d -= d2; - break; - - case JSOP_MUL: - d *= d2; - break; - - case JSOP_DIV: - if (d2 == 0) { -#if defined(XP_WIN) - /* XXX MSVC miscompiles such that (NaN == 0) */ - if (IsNaN(d2)) - d = GenericNaN(); - else -#endif - if (d == 0 || IsNaN(d)) - d = GenericNaN(); - else if (IsNegative(d) != IsNegative(d2)) - d = NegativeInfinity(); - else - d = PositiveInfinity(); - } else { - d /= d2; - } - break; - - case JSOP_MOD: - if (d2 == 0) { - d = GenericNaN(); - } else { - d = js_fmod(d, d2); - } - break; - - case JSOP_POW: - d = ecmaPow(d, d2); - break; - - default:; - } - - /* Take care to allow pn1 or pn2 to alias pn. */ - pn->setKind(PNK_NUMBER); - pn->setOp(JSOP_DOUBLE); - pn->setArity(PN_NULLARY); - pn->pn_dval = d; - return true; -} - // Remove a ParseNode, **pnp, from a parse tree, putting another ParseNode, // *pn, in its place. // @@ -561,6 +477,19 @@ ReplaceNode(ParseNode** pnp, ParseNode* pn) *pnp = pn; } +static bool +IsEffectless(ParseNode* node) +{ + return node->isKind(PNK_TRUE) || + node->isKind(PNK_FALSE) || + node->isKind(PNK_STRING) || + node->isKind(PNK_TEMPLATE_STRING) || + node->isKind(PNK_NUMBER) || + node->isKind(PNK_NULL) || + node->isKind(PNK_FUNCTION) || + node->isKind(PNK_GENEXP); +} + enum Truthiness { Truthy, Falsy, Unknown }; static Truthiness @@ -571,6 +500,7 @@ Boolish(ParseNode* pn) return (pn->pn_dval != 0 && !IsNaN(pn->pn_dval)) ? Truthy : Falsy; case PNK_STRING: + case PNK_TEMPLATE_STRING: return (pn->pn_atom->length() > 0) ? Truthy : Falsy; case PNK_TRUE: @@ -582,36 +512,58 @@ Boolish(ParseNode* pn) case PNK_NULL: return Falsy; + case PNK_VOID: { + // |void | evaluates to |undefined| which isn't truthy. But the + // sense of this method requires that the expression be literally + // replaceable with true/false: not the case if the nested expression + // is effectful, might throw, &c. Walk past the |void| (and nested + // |void| expressions, for good measure) and check that the nested + // expression doesn't break this requirement before indicating falsity. + do { + pn = pn->pn_kid; + } while (pn->isKind(PNK_VOID)); + + return IsEffectless(pn) ? Falsy : Unknown; + } + default: return Unknown; } } -// Expressions that appear in a few specific places are treated specially -// during constant folding. This enum tells where a parse node appears. -enum class SyntacticContext : int { - // pn is an expression, and it appears in a context where only its side - // effects and truthiness matter: the condition of an if statement, - // conditional expression, while loop, or for(;;) loop; or an operand of && - // or || in such a context. - Condition, - - // pn is the operand of the 'delete' keyword. - Delete, - - // Any other syntactic context. - Other -}; - -static SyntacticContext -condIf(const ParseNode* pn, ParseNodeKind kind) -{ - return pn->isKind(kind) ? SyntacticContext::Condition : SyntacticContext::Other; -} +static bool +Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bool inGenexpLambda); static bool -Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bool inGenexpLambda, - SyntacticContext sc); +FoldCondition(ExclusiveContext* cx, ParseNode** nodePtr, Parser& parser, + bool inGenexpLambda) +{ + // Conditions fold like any other expression... + if (!Fold(cx, nodePtr, parser, inGenexpLambda)) + return false; + + // ...but then they sometimes can be further folded to constants. + ParseNode* node = *nodePtr; + Truthiness t = Boolish(node); + if (t != Unknown) { + // We can turn function nodes into constant nodes here, but mutating + // function nodes is tricky --- in particular, mutating a function node + // that appears on a method list corrupts the method list. However, + // methods are M's in statements of the form 'this.foo = M;', which we + // never fold, so we're okay. + parser.prepareNodeForMutation(node); + if (t == Truthy) { + node->setKind(PNK_TRUE); + node->setOp(JSOP_TRUE); + } else { + node->setKind(PNK_FALSE); + node->setOp(JSOP_FALSE); + } + node->setArity(PN_NULLARY); + } + + return true; +} static bool FoldTypeOfExpr(ExclusiveContext* cx, ParseNode* node, Parser& parser, @@ -621,7 +573,7 @@ FoldTypeOfExpr(ExclusiveContext* cx, ParseNode* node, Parser& MOZ_ASSERT(node->isArity(PN_UNARY)); ParseNode*& expr = node->pn_kid; - if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &expr, parser, inGenexpLambda)) return false; // Constant-fold the entire |typeof| if given a constant with known type. @@ -649,36 +601,6 @@ FoldTypeOfExpr(ExclusiveContext* cx, ParseNode* node, Parser& return true; } -static bool -FoldVoid(ExclusiveContext* cx, ParseNode* node, Parser& parser, - bool inGenexpLambda, SyntacticContext sc) -{ - MOZ_ASSERT(node->isKind(PNK_VOID)); - MOZ_ASSERT(node->isArity(PN_UNARY)); - - ParseNode*& expr = node->pn_kid; - if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other)) - return false; - - if (sc == SyntacticContext::Condition) { - if (expr->isKind(PNK_TRUE) || - expr->isKind(PNK_FALSE) || - expr->isKind(PNK_STRING) || - expr->isKind(PNK_TEMPLATE_STRING) || - expr->isKind(PNK_NUMBER) || - expr->isKind(PNK_NULL) || - expr->isKind(PNK_FUNCTION)) - { - parser.prepareNodeForMutation(node); - node->setKind(PNK_FALSE); - node->setArity(PN_NULLARY); - node->setOp(JSOP_FALSE); - } - } - - return true; -} - static bool FoldDeleteExpr(ExclusiveContext* cx, ParseNode* node, Parser& parser, bool inGenexpLambda) @@ -687,19 +609,12 @@ FoldDeleteExpr(ExclusiveContext* cx, ParseNode* node, Parser& MOZ_ASSERT(node->isArity(PN_UNARY)); ParseNode*& expr = node->pn_kid; - if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &expr, parser, inGenexpLambda)) return false; - // Expression deletion evaluates the expression, then evaluates to - // true. For trivial expressions, eliminate the expression evaluation. - if (expr->isKind(PNK_TRUE) || - expr->isKind(PNK_FALSE) || - expr->isKind(PNK_STRING) || - expr->isKind(PNK_TEMPLATE_STRING) || - expr->isKind(PNK_NUMBER) || - expr->isKind(PNK_NULL) || - expr->isKind(PNK_FUNCTION)) - { + // Expression deletion evaluates the expression, then evaluates to true. + // For effectless expressions, eliminate the expression evaluation. + if (IsEffectless(expr)) { parser.prepareNodeForMutation(node); node->setKind(PNK_TRUE); node->setArity(PN_NULLARY); @@ -718,7 +633,7 @@ FoldDeleteElement(ExclusiveContext* cx, ParseNode* node, Parserpn_kid->isKind(PNK_ELEM) || node->pn_kid->isKind(PNK_SUPERELEM)); ParseNode*& expr = node->pn_kid; - if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &expr, parser, inGenexpLambda)) return false; // If we're deleting an element, but constant-folding converted our @@ -749,7 +664,7 @@ FoldDeleteProperty(ExclusiveContext* cx, ParseNode* node, ParsergetKind(); #endif - if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &expr, parser, inGenexpLambda)) return false; MOZ_ASSERT(expr->isKind(oldKind), @@ -766,7 +681,7 @@ FoldNot(ExclusiveContext* cx, ParseNode* node, Parser& parser, MOZ_ASSERT(node->isArity(PN_UNARY)); ParseNode*& expr = node->pn_kid; - if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Condition)) + if (!FoldCondition(cx, &expr, parser, inGenexpLambda)) return false; if (expr->isKind(PNK_NUMBER)) { @@ -802,7 +717,7 @@ FoldUnaryArithmetic(ExclusiveContext* cx, ParseNode* node, ParserisArity(PN_UNARY)); ParseNode*& expr = node->pn_kid; - if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &expr, parser, inGenexpLambda)) return false; if (expr->isKind(PNK_NUMBER) || expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE)) { @@ -840,7 +755,7 @@ FoldIncrementDecrement(ExclusiveContext* cx, ParseNode* node, Parserpn_kid; MOZ_ASSERT(parser.isValidSimpleAssignmentTarget(target, Parser::PermitAssignmentToFunctionCalls)); - if (!Fold(cx, &target, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &target, parser, inGenexpLambda)) return false; MOZ_ASSERT(parser.isValidSimpleAssignmentTarget(target, Parser::PermitAssignmentToFunctionCalls)); @@ -850,7 +765,7 @@ FoldIncrementDecrement(ExclusiveContext* cx, ParseNode* node, Parser& parser, - bool inGenexpLambda, SyntacticContext sc) + bool inGenexpLambda) { ParseNode* node = *nodePtr; @@ -860,8 +775,7 @@ FoldAndOr(ExclusiveContext* cx, ParseNode** nodePtr, Parser& p bool isOrNode = node->isKind(PNK_OR); ParseNode** elem = &node->pn_head; do { - // Pass |sc| through to propagate conditionality. - if (!Fold(cx, elem, parser, inGenexpLambda, sc)) + if (!Fold(cx, elem, parser, inGenexpLambda)) return false; Truthiness t = Boolish(*elem); @@ -950,11 +864,11 @@ FoldConditional(ExclusiveContext* cx, ParseNode** nodePtr, ParserisArity(PN_TERNARY)); ParseNode*& expr = node->pn_kid1; - if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Condition)) + if (!FoldCondition(cx, &expr, parser, inGenexpLambda)) return false; ParseNode*& ifTruthy = node->pn_kid2; - if (!Fold(cx, &ifTruthy, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &ifTruthy, parser, inGenexpLambda)) return false; ParseNode*& ifFalsy = node->pn_kid3; @@ -969,7 +883,7 @@ FoldConditional(ExclusiveContext* cx, ParseNode** nodePtr, ParserisKind(PNK_CONDITIONAL)) { nextNode = &ifFalsy; } else { - if (!Fold(cx, &ifFalsy, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &ifFalsy, parser, inGenexpLambda)) return false; } @@ -1041,11 +955,11 @@ FoldIf(ExclusiveContext* cx, ParseNode** nodePtr, Parser& pars MOZ_ASSERT(node->isArity(PN_TERNARY)); ParseNode*& expr = node->pn_kid1; - if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Condition)) + if (!FoldCondition(cx, &expr, parser, inGenexpLambda)) return false; ParseNode*& consequent = node->pn_kid2; - if (!Fold(cx, &consequent, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &consequent, parser, inGenexpLambda)) return false; ParseNode*& alternative = node->pn_kid3; @@ -1058,11 +972,8 @@ FoldIf(ExclusiveContext* cx, ParseNode** nodePtr, Parser& pars if (alternative->isKind(PNK_IF)) { nextNode = &alternative; } else { - if (!Fold(cx, &alternative, parser, inGenexpLambda, - SyntacticContext::Other)) - { + if (!Fold(cx, &alternative, parser, inGenexpLambda)) return false; - } } } @@ -1154,16 +1065,55 @@ FoldFunction(ExclusiveContext* cx, ParseNode* node, Parser& pa // Note: pn_body is null for lazily-parsed functions. if (ParseNode*& functionBody = node->pn_body) { - if (!Fold(cx, &functionBody, parser, node->pn_funbox->inGenexpLambda, - SyntacticContext::Other)) - { + if (!Fold(cx, &functionBody, parser, node->pn_funbox->inGenexpLambda)) return false; - } } return true; } +static double +ComputeBinary(ParseNodeKind kind, double left, double right) +{ + if (kind == PNK_ADD) + return left + right; + + if (kind == PNK_SUB) + return left - right; + + if (kind == PNK_STAR) + return left * right; + + if (kind == PNK_MOD) + return right == 0 ? GenericNaN() : js_fmod(left, right); + + if (kind == PNK_URSH) + return ToUint32(left) >> (ToUint32(right) & 31); + + if (kind == PNK_DIV) { + if (right == 0) { +#if defined(XP_WIN) + /* XXX MSVC miscompiles such that (NaN == 0) */ + if (IsNaN(right)) + return GenericNaN(); +#endif + if (left == 0 || IsNaN(left)) + return GenericNaN(); + if (IsNegative(left) != IsNegative(right)) + return NegativeInfinity(); + return PositiveInfinity(); + } + + return left / right; + } + + MOZ_ASSERT(kind == PNK_LSH || kind == PNK_RSH); + + int32_t i = ToInt32(left); + uint32_t j = ToUint32(right) & 31; + return int32_t((kind == PNK_LSH) ? uint32_t(i) << j : i >> j); +} + static bool FoldBinaryArithmetic(ExclusiveContext* cx, ParseNode* node, Parser& parser, bool inGenexpLambda) @@ -1181,7 +1131,7 @@ FoldBinaryArithmetic(ExclusiveContext* cx, ParseNode* node, Parserpn_head; for (; *listp; listp = &(*listp)->pn_next) { - if (!Fold(cx, listp, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, listp, parser, inGenexpLambda)) return false; if (!FoldType(cx, *listp, PNK_NUMBER)) @@ -1196,22 +1146,26 @@ FoldBinaryArithmetic(ExclusiveContext* cx, ParseNode* node, ParsergetOp(); ParseNode* elem = node->pn_head; ParseNode* next = elem->pn_next; if (elem->isKind(PNK_NUMBER)) { + ParseNodeKind kind = node->getKind(); while (true) { if (!next || !next->isKind(PNK_NUMBER)) break; - ParseNode* afterNext = next->pn_next; - if (!FoldBinaryNumeric(cx, op, elem, next, elem)) - return false; + double d = ComputeBinary(kind, elem->pn_dval, next->pn_dval); + ParseNode* afterNext = next->pn_next; parser.freeTree(next); next = afterNext; elem->pn_next = next; + elem->setKind(PNK_NUMBER); + elem->setOp(JSOP_DOUBLE); + elem->setArity(PN_NULLARY); + elem->pn_dval = d; + node->pn_count--; } @@ -1243,7 +1197,7 @@ FoldExponentiation(ExclusiveContext* cx, ParseNode* node, Parserpn_head; for (; *listp; listp = &(*listp)->pn_next) { - if (!Fold(cx, listp, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, listp, parser, inGenexpLambda)) return false; if (!FoldType(cx, *listp, PNK_NUMBER)) @@ -1284,7 +1238,7 @@ FoldList(ExclusiveContext* cx, ParseNode* list, Parser& parser ParseNode** elem = &list->pn_head; for (; *elem; elem = &(*elem)->pn_next) { - if (!Fold(cx, elem, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, elem, parser, inGenexpLambda)) return false; } @@ -1304,7 +1258,7 @@ FoldReturn(ExclusiveContext* cx, ParseNode* node, Parser& pars MOZ_ASSERT(node->isArity(PN_BINARY)); if (ParseNode*& expr = node->pn_left) { - if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &expr, parser, inGenexpLambda)) return false; } @@ -1327,16 +1281,16 @@ FoldTry(ExclusiveContext* cx, ParseNode* node, Parser& parser, MOZ_ASSERT(node->isArity(PN_TERNARY)); ParseNode*& statements = node->pn_kid1; - if (!Fold(cx, &statements, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &statements, parser, inGenexpLambda)) return false; if (ParseNode*& catchList = node->pn_kid2) { - if (!Fold(cx, &catchList, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &catchList, parser, inGenexpLambda)) return false; } if (ParseNode*& finally = node->pn_kid3) { - if (!Fold(cx, &finally, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &finally, parser, inGenexpLambda)) return false; } @@ -1351,16 +1305,16 @@ FoldCatch(ExclusiveContext* cx, ParseNode* node, Parser& parse MOZ_ASSERT(node->isArity(PN_TERNARY)); ParseNode*& declPattern = node->pn_kid1; - if (!Fold(cx, &declPattern, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &declPattern, parser, inGenexpLambda)) return false; if (ParseNode*& cond = node->pn_kid2) { - if (!Fold(cx, &cond, parser, inGenexpLambda, SyntacticContext::Condition)) + if (!FoldCondition(cx, &cond, parser, inGenexpLambda)) return false; } if (ParseNode*& statements = node->pn_kid3) { - if (!Fold(cx, &statements, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &statements, parser, inGenexpLambda)) return false; } @@ -1375,28 +1329,369 @@ FoldClass(ExclusiveContext* cx, ParseNode* node, Parser& parse MOZ_ASSERT(node->isArity(PN_TERNARY)); if (ParseNode*& classNames = node->pn_kid1) { - if (!Fold(cx, &classNames, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &classNames, parser, inGenexpLambda)) return false; } if (ParseNode*& heritage = node->pn_kid2) { - if (!Fold(cx, &heritage, parser, inGenexpLambda, SyntacticContext::Other)) + if (!Fold(cx, &heritage, parser, inGenexpLambda)) return false; } ParseNode*& body = node->pn_kid3; - return Fold(cx, &body, parser, inGenexpLambda, SyntacticContext::Other); + return Fold(cx, &body, parser, inGenexpLambda); +} + +static bool +FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser& parser, + bool inGenexpLambda) +{ + ParseNode* node = *nodePtr; + + MOZ_ASSERT(node->isKind(PNK_ELEM)); + MOZ_ASSERT(node->isArity(PN_BINARY)); + + ParseNode*& expr = node->pn_left; + if (!Fold(cx, &expr, parser, inGenexpLambda)) + return false; + + ParseNode*& key = node->pn_right; + if (!Fold(cx, &key, parser, inGenexpLambda)) + return false; + + PropertyName* name = nullptr; + if (key->isKind(PNK_STRING)) { + JSAtom* atom = key->pn_atom; + uint32_t index; + + if (atom->isIndex(&index)) { + // Optimization 1: We have something like expr["100"]. This is + // equivalent to expr[100] which is faster. + key->setKind(PNK_NUMBER); + key->setOp(JSOP_DOUBLE); + key->pn_dval = index; + } else { + name = atom->asPropertyName(); + } + } else if (key->isKind(PNK_NUMBER)) { + double number = key->pn_dval; + if (number != ToUint32(number)) { + // Optimization 2: We have something like expr[3.14]. The number + // isn't an array index, so it converts to a string ("3.14"), + // enabling optimization 3 below. + JSAtom* atom = ToAtom(cx, DoubleValue(number)); + if (!atom) + return false; + name = atom->asPropertyName(); + } + } + + // If we don't have a name, we can't optimize to getprop. + if (!name) + return true; + + // Also don't optimize if the name doesn't map directly to its id for TI's + // purposes. + if (NameToId(name) != IdToTypeId(NameToId(name))) + return true; + + // Optimization 3: We have expr["foo"] where foo is not an index. Convert + // to a property access (like expr.foo) that optimizes better downstream. + // Don't bother with this for names that TI considers to be indexes, to + // simplify downstream analysis. + ParseNode* dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end); + if (!dottedAccess) + return false; + dottedAccess->setInParens(node->isInParens()); + ReplaceNode(nodePtr, dottedAccess); + + // If we've replaced |expr["prop"]| with |expr.prop|, we can now free the + // |"prop"| and |expr["prop"]| nodes -- but not the |expr| node that we're + // now using as a sub-node of |dottedAccess|. Munge |expr["prop"]| into a + // node with |"prop"| as its only child, that'll pass AST sanity-checking + // assertions during freeing, then free it. + node->setKind(PNK_TYPEOFEXPR); + node->setArity(PN_UNARY); + node->pn_kid = key; + parser.freeTree(node); + + return true; +} + +static bool +FoldAdd(ExclusiveContext* cx, ParseNode** nodePtr, Parser& parser, + bool inGenexpLambda) +{ + ParseNode* node = *nodePtr; + + MOZ_ASSERT(node->isKind(PNK_ADD)); + MOZ_ASSERT(node->isArity(PN_LIST)); + MOZ_ASSERT(node->pn_count >= 2); + + // Generically fold all operands first. + if (!FoldList(cx, node, parser, inGenexpLambda)) + return false; + + // Fold leading numeric operands together: + // + // (1 + 2 + x) becomes (3 + x) + // + // Don't go past the leading operands: additions after a string are + // string concatenations, not additions: ("1" + 2 + 3 === "123"). + ParseNode* current = node->pn_head; + ParseNode* next = current->pn_next; + if (current->isKind(PNK_NUMBER)) { + do { + if (!next->isKind(PNK_NUMBER)) + break; + + current->pn_dval += next->pn_dval; + current->pn_next = next->pn_next; + parser.freeTree(next); + next = current->pn_next; + + MOZ_ASSERT(node->pn_count > 1); + node->pn_count--; + } while (next); + } + + // If any operands remain, attempt string concatenation folding. + do { + // If no operands remain, we're done. + if (!next) + break; + + // (number + string) is string concatenation *only* at the start of + // the list: (x + 1 + "2" !== x + "12") when x is a number. + if (current->isKind(PNK_NUMBER) && next->isKind(PNK_STRING)) { + if (!FoldType(cx, current, PNK_STRING)) + return false; + next = current->pn_next; + } + + // The first string forces all subsequent additions to be + // string concatenations. + do { + if (current->isKind(PNK_STRING)) + break; + + current = next; + next = next->pn_next; + } while (next); + + // If there's nothing left to fold, we're done. + if (!next) + break; + + RootedString combination(cx); + RootedString tmp(cx); + do { + // Create a rope of the current string and all succeeding + // constants that we can convert to strings, then atomize it + // and replace them all with that fresh string. + MOZ_ASSERT(current->isKind(PNK_STRING)); + + combination = current->pn_atom; + + do { + // Try folding the next operand to a string. + if (!FoldType(cx, next, PNK_STRING)) + return false; + + // Stop glomming once folding doesn't produce a string. + if (!next->isKind(PNK_STRING)) + break; + + // Add this string to the combination and remove the node. + tmp = next->pn_atom; + combination = ConcatStrings(cx, combination, tmp); + if (!combination) + return false; + + current->pn_next = next->pn_next; + parser.freeTree(next); + next = current->pn_next; + + MOZ_ASSERT(node->pn_count > 1); + node->pn_count--; + } while (next); + + // Replace |current|'s string with the entire combination. + MOZ_ASSERT(current->isKind(PNK_STRING)); + combination = AtomizeString(cx, combination); + if (!combination) + return false; + current->pn_atom = &combination->asAtom(); + + + // If we're out of nodes, we're done. + if (!next) + break; + + current = next; + next = current->pn_next; + + // If we're out of nodes *after* the non-foldable-to-string + // node, we're done. + if (!next) + break; + + // Otherwise find the next node foldable to a string, and loop. + do { + current = next; + next = current->pn_next; + + if (!FoldType(cx, current, PNK_STRING)) + return false; + next = current->pn_next; + } while (!current->isKind(PNK_STRING) && next); + } while (next); + } while (false); + + MOZ_ASSERT(!next, "must have considered all nodes here"); + MOZ_ASSERT(!current->pn_next, "current node must be the last node"); + + node->pn_tail = ¤t->pn_next; + node->checkListConsistency(); + + if (node->pn_count == 1) { + // We reduced the list to a constant. Replace the PNK_ADD node + // with that constant. + ReplaceNode(nodePtr, current); + + // Free the old node to aggressively verify nothing uses it. + node->setKind(PNK_TRUE); + node->setArity(PN_NULLARY); + node->setOp(JSOP_TRUE); + parser.freeTree(node); + } + + return true; +} + +static bool +FoldCall(ExclusiveContext* cx, ParseNode* node, Parser& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_TAGGED_TEMPLATE)); + MOZ_ASSERT(node->isArity(PN_LIST)); + + // Don't fold a parenthesized callable component in an invocation, as this + // might cause a different |this| value to be used, changing semantics: + // + // var prop = "global"; + // var obj = { prop: "obj", f: function() { return this.prop; } }; + // assertEq((true ? obj.f : null)(), "global"); + // assertEq(obj.f(), "obj"); + // assertEq((true ? obj.f : null)``, "global"); + // assertEq(obj.f``, "obj"); + // + // See bug 537673 and bug 1182373. + ParseNode** listp = &node->pn_head; + if ((*listp)->isInParens()) + listp = &(*listp)->pn_next; + + for (; *listp; listp = &(*listp)->pn_next) { + if (!Fold(cx, listp, parser, inGenexpLambda)) + return false; + } + + // If the last node in the list was replaced, pn_tail points into the wrong node. + node->pn_tail = listp; + + node->checkListConsistency(); + return true; +} + +static bool +FoldForInOrOf(ExclusiveContext* cx, ParseNode* node, Parser& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_FORIN) || node->isKind(PNK_FOROF)); + MOZ_ASSERT(node->isArity(PN_TERNARY)); + + if (ParseNode*& decl = node->pn_kid1) { + if (!Fold(cx, &decl, parser, inGenexpLambda)) + return false; + } + + return Fold(cx, &node->pn_kid2, parser, inGenexpLambda) && + Fold(cx, &node->pn_kid3, parser, inGenexpLambda); +} + +static bool +FoldForHead(ExclusiveContext* cx, ParseNode* node, Parser& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_FORHEAD)); + MOZ_ASSERT(node->isArity(PN_TERNARY)); + + if (ParseNode*& init = node->pn_kid1) { + if (!Fold(cx, &init, parser, inGenexpLambda)) + return false; + } + + if (ParseNode*& test = node->pn_kid2) { + if (!FoldCondition(cx, &test, parser, inGenexpLambda)) + return false; + + if (test->isKind(PNK_TRUE)) { + parser.freeTree(test); + test = nullptr; + } + } + + if (ParseNode*& update = node->pn_kid3) { + if (!Fold(cx, &update, parser, inGenexpLambda)) + return false; + } + + return true; +} + +static bool +FoldDottedProperty(ExclusiveContext* cx, ParseNode* node, Parser& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_DOT)); + MOZ_ASSERT(node->isArity(PN_NAME)); + + // Iterate through a long chain of dotted property accesses to find the + // most-nested non-dotted property node, then fold that. + ParseNode** nested = &node->pn_expr; + while ((*nested)->isKind(PNK_DOT)) { + MOZ_ASSERT((*nested)->isArity(PN_NAME)); + nested = &(*nested)->pn_expr; + } + + return Fold(cx, nested, parser, inGenexpLambda); +} + +static bool +FoldName(ExclusiveContext* cx, ParseNode* node, Parser& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_NAME)); + MOZ_ASSERT(node->isArity(PN_NAME)); + + // Name nodes that are used, are in use-definition lists. Such nodes store + // name analysis information and contain nothing foldable. + if (node->isUsed()) + return true; + + // Other names might have a foldable expression in pn_expr. + if (!node->pn_expr) + return true; + + return Fold(cx, &node->pn_expr, parser, inGenexpLambda); } bool -Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bool inGenexpLambda, - SyntacticContext sc) +Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bool inGenexpLambda) { JS_CHECK_RECURSION(cx, return false); ParseNode* pn = *pnp; - ParseNode* pn1 = nullptr; - ParseNode* pn2 = nullptr; switch (pn->getKind()) { case PNK_NEWTARGET: @@ -1419,7 +1714,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_SUPERPROP: case PNK_FRESHENBLOCK: MOZ_ASSERT(pn->isArity(PN_NULLARY)); - goto afterFolding; + return true; case PNK_TYPEOFNAME: MOZ_ASSERT(pn->isArity(PN_UNARY)); @@ -1430,9 +1725,6 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_TYPEOFEXPR: return FoldTypeOfExpr(cx, pn, parser, inGenexpLambda); - case PNK_VOID: - return FoldVoid(cx, pn, parser, inGenexpLambda, sc); - case PNK_DELETENAME: { MOZ_ASSERT(pn->isArity(PN_UNARY)); MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME)); @@ -1476,18 +1768,21 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_COMPUTED_NAME: case PNK_SPREAD: case PNK_SUPERELEM: + case PNK_EXPORT: + case PNK_EXPORT_DEFAULT: + case PNK_VOID: MOZ_ASSERT(pn->isArity(PN_UNARY)); - return Fold(cx, &pn->pn_kid, parser, inGenexpLambda, SyntacticContext::Other); + return Fold(cx, &pn->pn_kid, parser, inGenexpLambda); case PNK_SEMI: MOZ_ASSERT(pn->isArity(PN_UNARY)); if (ParseNode*& expr = pn->pn_kid) - return Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other); + return Fold(cx, &expr, parser, inGenexpLambda); return true; case PNK_AND: case PNK_OR: - return FoldAndOr(cx, pnp, parser, inGenexpLambda, sc); + return FoldAndOr(cx, pnp, parser, inGenexpLambda); case PNK_FUNCTION: return FoldFunction(cx, pn, parser, inGenexpLambda); @@ -1520,6 +1815,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_INSTANCEOF: case PNK_IN: case PNK_COMMA: + case PNK_NEW: case PNK_ARRAY: case PNK_OBJECT: case PNK_ARRAYCOMP: @@ -1535,13 +1831,14 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_CALLSITEOBJ: case PNK_EXPORT_SPEC_LIST: case PNK_IMPORT_SPEC_LIST: + case PNK_GENEXP: return FoldList(cx, pn, parser, inGenexpLambda); case PNK_YIELD_STAR: MOZ_ASSERT(pn->isArity(PN_BINARY)); MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME)); MOZ_ASSERT(!pn->pn_right->isAssigned()); - return Fold(cx, &pn->pn_left, parser, inGenexpLambda, SyntacticContext::Other); + return Fold(cx, &pn->pn_left, parser, inGenexpLambda); case PNK_YIELD: MOZ_ASSERT(pn->isArity(PN_BINARY)); @@ -1551,7 +1848,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo pn->pn_right->pn_right->isKind(PNK_GENERATOR))); if (!pn->pn_left) return true; - return Fold(cx, &pn->pn_left, parser, inGenexpLambda, SyntacticContext::Other); + return Fold(cx, &pn->pn_left, parser, inGenexpLambda); case PNK_RETURN: return FoldReturn(cx, pn, parser, inGenexpLambda); @@ -1565,369 +1862,102 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_CLASS: return FoldClass(cx, pn, parser, inGenexpLambda); - case PNK_EXPORT: + case PNK_ELEM: + return FoldElement(cx, pnp, parser, inGenexpLambda); + + case PNK_ADD: + return FoldAdd(cx, pnp, parser, inGenexpLambda); + + case PNK_CALL: + case PNK_TAGGED_TEMPLATE: + return FoldCall(cx, pn, parser, inGenexpLambda); + + case PNK_SWITCH: + case PNK_CASE: + case PNK_COLON: case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: case PNK_BITORASSIGN: - case PNK_BITXORASSIGN: case PNK_BITANDASSIGN: + case PNK_BITXORASSIGN: case PNK_LSHASSIGN: case PNK_RSHASSIGN: case PNK_URSHASSIGN: - case PNK_MULASSIGN: case PNK_DIVASSIGN: case PNK_MODASSIGN: + case PNK_MULASSIGN: case PNK_POWASSIGN: - case PNK_ELEM: - case PNK_COLON: - case PNK_CASE: + case PNK_IMPORT: + case PNK_EXPORT_FROM: case PNK_SHORTHAND: - case PNK_DOWHILE: - case PNK_WHILE: - case PNK_SWITCH: case PNK_LETBLOCK: case PNK_FOR: case PNK_CLASSMETHOD: - case PNK_WITH: + case PNK_IMPORT_SPEC: + case PNK_EXPORT_SPEC: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + return Fold(cx, &pn->pn_left, parser, inGenexpLambda) && + Fold(cx, &pn->pn_right, parser, inGenexpLambda); + case PNK_CLASSNAMES: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + if (ParseNode*& outerBinding = pn->pn_left) { + if (!Fold(cx, &outerBinding, parser, inGenexpLambda)) + return false; + } + return Fold(cx, &pn->pn_right, parser, inGenexpLambda); + + case PNK_DOWHILE: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + return Fold(cx, &pn->pn_left, parser, inGenexpLambda) && + FoldCondition(cx, &pn->pn_right, parser, inGenexpLambda); + + case PNK_WHILE: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + return FoldCondition(cx, &pn->pn_left, parser, inGenexpLambda) && + Fold(cx, &pn->pn_right, parser, inGenexpLambda); + case PNK_DEFAULT: - case PNK_IMPORT: - case PNK_EXPORT_FROM: - case PNK_EXPORT_DEFAULT: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + MOZ_ASSERT(!pn->pn_left); + MOZ_ASSERT(pn->pn_right->isKind(PNK_STATEMENTLIST)); + return Fold(cx, &pn->pn_right, parser, inGenexpLambda); + + case PNK_WITH: + MOZ_ASSERT(pn->isArity(PN_BINARY_OBJ)); + return Fold(cx, &pn->pn_left, parser, inGenexpLambda) && + Fold(cx, &pn->pn_right, parser, inGenexpLambda); + case PNK_FORIN: case PNK_FOROF: + return FoldForInOrOf(cx, pn, parser, inGenexpLambda); + case PNK_FORHEAD: - case PNK_ADD: - case PNK_NEW: - case PNK_CALL: - case PNK_GENEXP: - case PNK_TAGGED_TEMPLATE: + return FoldForHead(cx, pn, parser, inGenexpLambda); + case PNK_LABEL: + MOZ_ASSERT(pn->isArity(PN_NAME)); + return Fold(cx, &pn->pn_expr, parser, inGenexpLambda); + case PNK_DOT: + return FoldDottedProperty(cx, pn, parser, inGenexpLambda); + case PNK_LEXICALSCOPE: + MOZ_ASSERT(pn->isArity(PN_NAME)); + if (!pn->pn_expr) + return true; + return Fold(cx, &pn->pn_expr, parser, inGenexpLambda); + case PNK_NAME: - case PNK_EXPORT_SPEC: - case PNK_IMPORT_SPEC: - MOZ_ASSERT(!pn->isArity(PN_CODE), "only functions are code nodes"); - break; // for now + return FoldName(cx, pn, parser, inGenexpLambda); case PNK_LIMIT: // invalid sentinel value MOZ_CRASH("invalid node kind"); } - // First, recursively fold constants on the children of this node. - switch (pn->getArity()) { - case PN_CODE: - MOZ_ASSERT(pn->isKind(PNK_FUNCTION)); - MOZ_CRASH("should have been handled above"); - - case PN_LIST: - { - // Don't fold a parenthesized call expression. See bug 537673. - ParseNode** listp = &pn->pn_head; - if ((pn->isKind(PNK_CALL) || pn->isKind(PNK_TAGGED_TEMPLATE)) && (*listp)->isInParens()) - listp = &(*listp)->pn_next; - - for (; *listp; listp = &(*listp)->pn_next) { - if (!Fold(cx, listp, parser, inGenexpLambda, SyntacticContext::Other)) - return false; - } - - // If the last node in the list was replaced, pn_tail points into the wrong node. - pn->pn_tail = listp; - - // Save the list head in pn1 for later use. - pn1 = pn->pn_head; - pn2 = nullptr; - break; - } - - case PN_TERNARY: - MOZ_ASSERT(!pn->isKind(PNK_CONDITIONAL), - "should be skipping this above"); - MOZ_ASSERT(!pn->isKind(PNK_IF), - "should be skipping this above"); - /* Any kid may be null (e.g. for (;;)). */ - if (pn->pn_kid1) { - if (!Fold(cx, &pn->pn_kid1, parser, inGenexpLambda, SyntacticContext::Other)) - return false; - } - pn1 = pn->pn_kid1; - - if (pn->pn_kid2) { - if (!Fold(cx, &pn->pn_kid2, parser, inGenexpLambda, condIf(pn, PNK_FORHEAD))) - return false; - if (pn->isKind(PNK_FORHEAD) && pn->pn_kid2->isKind(PNK_TRUE)) { - parser.freeTree(pn->pn_kid2); - pn->pn_kid2 = nullptr; - } - } - pn2 = pn->pn_kid2; - - if (pn->pn_kid3) { - if (!Fold(cx, &pn->pn_kid3, parser, inGenexpLambda, SyntacticContext::Other)) - return false; - } - break; - - case PN_BINARY: - case PN_BINARY_OBJ: - /* First kid may be null (for default case in switch). */ - if (pn->pn_left) { - if (!Fold(cx, &pn->pn_left, parser, inGenexpLambda, condIf(pn, PNK_WHILE))) - return false; - } - /* Second kid may be null (for return in non-generator). */ - if (pn->pn_right) { - if (!Fold(cx, &pn->pn_right, parser, inGenexpLambda, condIf(pn, PNK_DOWHILE))) - return false; - } - pn1 = pn->pn_left; - pn2 = pn->pn_right; - break; - - case PN_UNARY: - MOZ_ASSERT(!IsDeleteKind(pn->getKind()), - "should have been handled above"); - if (pn->pn_kid) { - if (!Fold(cx, &pn->pn_kid, parser, inGenexpLambda, SyntacticContext::Other)) - return false; - } - pn1 = pn->pn_kid; - break; - - case PN_NAME: - /* - * Skip pn1 down along a chain of dotted member expressions to avoid - * excessive recursion. Our only goal here is to fold constants (if - * any) in the primary expression operand to the left of the first - * dot in the chain. - */ - if (!pn->isUsed()) { - ParseNode** lhsp = &pn->pn_expr; - while (*lhsp && (*lhsp)->isArity(PN_NAME) && !(*lhsp)->isUsed()) - lhsp = &(*lhsp)->pn_expr; - if (*lhsp && !Fold(cx, lhsp, parser, inGenexpLambda, SyntacticContext::Other)) - return false; - pn1 = *lhsp; - } - break; - - case PN_NULLARY: - break; - } - - // The immediate child of a PNK_DELETE* node should not be replaced - // with node indicating a different syntactic form; |delete x| is not - // the same as |delete (true && x)|. See bug 888002. - // - // pn is the immediate child in question. Its descendants were already - // constant-folded above, so we're done. - if (sc == SyntacticContext::Delete) - return true; - - switch (pn->getKind()) { - case PNK_ADD: { - MOZ_ASSERT(pn->isArity(PN_LIST)); - - bool folded = false; - - pn2 = pn1->pn_next; - if (pn1->isKind(PNK_NUMBER)) { - // Fold addition of numeric literals: (1 + 2 + x === 3 + x). - // Note that we can only do this the front of the list: - // (x + 1 + 2 !== x + 3) when x is a string. - while (pn2 && pn2->isKind(PNK_NUMBER)) { - pn1->pn_dval += pn2->pn_dval; - pn1->pn_next = pn2->pn_next; - parser.freeTree(pn2); - pn2 = pn1->pn_next; - pn->pn_count--; - folded = true; - } - } - - // Now search for adjacent pairs of literals to fold for string - // concatenation. - // - // isStringConcat is true if we know the operation we're looking at - // will be string concatenation at runtime. As soon as we see a - // string, we know that every addition to the right of it will be - // string concatenation, even if both operands are numbers: - // ("s" + x + 1 + 2 === "s" + x + "12"). - // - bool isStringConcat = false; - RootedString foldedStr(cx); - - // (number + string) is definitely concatenation, but only at the - // front of the list: (x + 1 + "2" !== x + "12") when x is a - // number. - if (pn1->isKind(PNK_NUMBER) && pn2 && pn2->isKind(PNK_STRING)) - isStringConcat = true; - - while (pn2) { - isStringConcat = isStringConcat || pn1->isKind(PNK_STRING); - - if (isStringConcat && - (pn1->isKind(PNK_STRING) || pn1->isKind(PNK_NUMBER)) && - (pn2->isKind(PNK_STRING) || pn2->isKind(PNK_NUMBER))) - { - // Fold string concatenation of literals. - if (pn1->isKind(PNK_NUMBER) && !FoldType(cx, pn1, PNK_STRING)) - return false; - if (pn2->isKind(PNK_NUMBER) && !FoldType(cx, pn2, PNK_STRING)) - return false; - if (!foldedStr) - foldedStr = pn1->pn_atom; - RootedString right(cx, pn2->pn_atom); - foldedStr = ConcatStrings(cx, foldedStr, right); - if (!foldedStr) - return false; - pn1->pn_next = pn2->pn_next; - parser.freeTree(pn2); - pn2 = pn1->pn_next; - pn->pn_count--; - folded = true; - } else { - if (foldedStr) { - // Convert the rope of folded strings into an Atom. - pn1->pn_atom = AtomizeString(cx, foldedStr); - if (!pn1->pn_atom) - return false; - foldedStr = nullptr; - } - pn1 = pn2; - pn2 = pn2->pn_next; - } - } - - if (foldedStr) { - // Convert the rope of folded strings into an Atom. - pn1->pn_atom = AtomizeString(cx, foldedStr); - if (!pn1->pn_atom) - return false; - } - - if (folded) { - if (pn->pn_count == 1) { - // We reduced the list to one constant. There is no - // addition anymore. Replace the PNK_ADD node with the - // single PNK_STRING or PNK_NUMBER node. - ReplaceNode(pnp, pn1); - pn = pn1; - } else if (!pn2) { - pn->pn_tail = &pn1->pn_next; - } - } - - break; - } - - case PNK_TYPEOFNAME: - case PNK_TYPEOFEXPR: - case PNK_VOID: - case PNK_NOT: - case PNK_BITNOT: - case PNK_POS: - case PNK_NEG: - case PNK_CONDITIONAL: - case PNK_IF: - case PNK_AND: - case PNK_OR: - case PNK_SUB: - case PNK_STAR: - case PNK_LSH: - case PNK_RSH: - case PNK_URSH: - case PNK_DIV: - case PNK_MOD: - case PNK_POW: - MOZ_CRASH("should have been fully handled above"); - - case PNK_ELEM: { - // An indexed expression, pn1[pn2]. A few cases can be improved. - PropertyName* name = nullptr; - if (pn2->isKind(PNK_STRING)) { - JSAtom* atom = pn2->pn_atom; - uint32_t index; - - if (atom->isIndex(&index)) { - // Optimization 1: We have something like pn1["100"]. This is - // equivalent to pn1[100] which is faster. - pn2->setKind(PNK_NUMBER); - pn2->setOp(JSOP_DOUBLE); - pn2->pn_dval = index; - } else { - name = atom->asPropertyName(); - } - } else if (pn2->isKind(PNK_NUMBER)) { - double number = pn2->pn_dval; - if (number != ToUint32(number)) { - // Optimization 2: We have something like pn1[3.14]. The number - // is not an array index. This is equivalent to pn1["3.14"] - // which enables optimization 3 below. - JSAtom* atom = ToAtom(cx, DoubleValue(number)); - if (!atom) - return false; - name = atom->asPropertyName(); - } - } - - if (name && NameToId(name) == IdToTypeId(NameToId(name))) { - // Optimization 3: We have pn1["foo"] where foo is not an index. - // Convert to a property access (like pn1.foo) which we optimize - // better downstream. Don't bother with this for names which TI - // considers to be indexes, to simplify downstream analysis. - ParseNode* expr = parser.handler.newPropertyAccess(pn->pn_left, name, pn->pn_pos.end); - if (!expr) - return false; - expr->setInParens(pn->isInParens()); - ReplaceNode(pnp, expr); - - // Supposing we're replacing |obj["prop"]| with |obj.prop|, we now - // can free the |"prop"| node and |obj["prop"]| nodes -- but not - // the |obj| node now a sub-node of |expr|. Mutate |pn| into a - // node with |"prop"| as its child so that our |pn| doesn't have a - // necessarily-weird structure (say, by nulling out |pn->pn_left| - // only) that would fail AST sanity assertions performed by - // |parser.freeTree(pn)|. - pn->setKind(PNK_TYPEOFEXPR); - pn->setArity(PN_UNARY); - pn->pn_kid = pn2; - parser.freeTree(pn); - - pn = expr; - } - break; - } - - default:; - } - - afterFolding: - if (sc == SyntacticContext::Condition) { - Truthiness t = Boolish(pn); - if (t != Unknown) { - /* - * We can turn function nodes into constant nodes here, but mutating function - * nodes is tricky --- in particular, mutating a function node that appears on - * a method list corrupts the method list. However, methods are M's in - * statements of the form 'this.foo = M;', which we never fold, so we're okay. - */ - parser.prepareNodeForMutation(pn); - if (t == Truthy) { - pn->setKind(PNK_TRUE); - pn->setOp(JSOP_TRUE); - } else { - pn->setKind(PNK_FALSE); - pn->setOp(JSOP_FALSE); - } - pn->setArity(PN_NULLARY); - } - } - - return true; + MOZ_CRASH("shouldn't reach here"); + return false; } bool @@ -1938,5 +1968,5 @@ frontend::FoldConstants(ExclusiveContext* cx, ParseNode** pnp, Parserpc->useAsmOrInsideUseAsm()) return true; - return Fold(cx, pnp, *parser, false, SyntacticContext::Other); + return Fold(cx, pnp, *parser, false); } diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 19bbd98bafc8..b346e01df48c 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -5705,7 +5705,9 @@ Parser::yieldExpression(InHandling inHandling) case TOK_COMMA: // No value. exprNode = null(); - tokenStream.addModifierException(TokenStream::NoneIsOperand); + tokenStream.addModifierException((tt == TOK_EOL || tt == TOK_EOF) + ? TokenStream::NoneIsOperandYieldEOL + : TokenStream::NoneIsOperand); break; case TOK_MUL: kind = PNK_YIELD_STAR; @@ -5768,7 +5770,9 @@ Parser::yieldExpression(InHandling inHandling) case TOK_COMMA: // No value. exprNode = null(); - tokenStream.addModifierException(TokenStream::NoneIsOperand); + tokenStream.addModifierException((tt == TOK_EOL || tt == TOK_EOF) + ? TokenStream::NoneIsOperandYieldEOL + : TokenStream::NoneIsOperand); break; default: exprNode = assignExpr(inHandling, YieldIsKeyword); diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index ae6e97a5e5da..0c21ba6f7fa0 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1650,7 +1650,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) // occurs and then the token is re-gotten (or peeked, etc.), we can assert // that both gets have used the same modifiers. tp->modifier = modifier; - tp->modifierExceptions = NoException; + tp->modifierException = NoException; #endif MOZ_ASSERT(IsTokenSane(tp)); *ttp = tp->type; diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index c9bf570b1e5a..75a62c560fa6 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -117,6 +117,29 @@ struct Token // ^ TemplateTail context TemplateTail, }; + enum ModifierException + { + NoException, + + // If an yield expression operand is omitted and yield expression is + // followed by non-EOL, the next token is already gotten with Operand, + // but we expect operator (None). + NoneIsOperand, + + // If an yield expression operand is omitted and yield expression is + // followed by EOL, the next token is already gotten with Operand, and + // we expect Operand in next statement, but MatchOrInsertSemicolon + // after expression statement expects operator (None). + NoneIsOperandYieldEOL, + + // If a semicolon is inserted automatically, the next token is already + // gotten with None, but we expect Operand. + OperandIsNone, + + // If name of method definition is `get` or `set`, the next token is + // already gotten with KeywordIsName, but we expect None. + NoneIsKeywordIsName, + }; friend class TokenStream; public: @@ -136,7 +159,7 @@ struct Token } u; #ifdef DEBUG Modifier modifier; // Modifier used to get this token - uint8_t modifierExceptions; // Bitwise OR of modifier exceptions + ModifierException modifierException; // Exception for this modifier #endif // This constructor is necessary only for MSVC 2013 and how it compiles the @@ -411,37 +434,47 @@ class MOZ_STACK_CLASS TokenStream static MOZ_CONSTEXPR_VAR Modifier KeywordIsName = Token::KeywordIsName; static MOZ_CONSTEXPR_VAR Modifier TemplateTail = Token::TemplateTail; - enum ModifierException - { - NoException = 0x00, - - // If a semicolon is inserted automatically, the next token is already - // gotten with None, but we expect Operand. - NoneIsOperand = 0x01, - - // If an yield expression operand is omitted, the next token is already - // gotten with Operand, but we expect operator (None). - OperandIsNone = 0x02, - - // If name of method definition is `get` or `set`, the next token is - // already gotten with KeywordIsName, but we expect None. - NoneIsKeywordIsName = 0x04, - }; + typedef Token::ModifierException ModifierException; + static MOZ_CONSTEXPR_VAR ModifierException NoException = Token::NoException; + static MOZ_CONSTEXPR_VAR ModifierException NoneIsOperand = Token::NoneIsOperand; + static MOZ_CONSTEXPR_VAR ModifierException NoneIsOperandYieldEOL = Token::NoneIsOperandYieldEOL; + static MOZ_CONSTEXPR_VAR ModifierException OperandIsNone = Token::OperandIsNone; + static MOZ_CONSTEXPR_VAR ModifierException NoneIsKeywordIsName = Token::NoneIsKeywordIsName; void addModifierException(ModifierException modifierException) { #ifdef DEBUG const Token& next = nextToken(); + if (next.modifierException == NoneIsOperand || + next.modifierException == NoneIsOperandYieldEOL) + { + // Token after yield expression without operand already has + // NoneIsOperand or NoneIsOperandYieldEOL exception. + MOZ_ASSERT(modifierException == OperandIsNone); + if (next.modifierException == NoneIsOperand) + MOZ_ASSERT(next.type != TOK_DIV && next.type != TOK_REGEXP, + "next token requires contextual specifier to be parsed unambiguously"); + else + MOZ_ASSERT(next.type != TOK_DIV, + "next token requires contextual specifier to be parsed unambiguously"); + + // Do not update modifierException. + return; + } + + MOZ_ASSERT(next.modifierException == NoException); switch (modifierException) { case NoneIsOperand: MOZ_ASSERT(next.modifier == Operand); MOZ_ASSERT(next.type != TOK_DIV && next.type != TOK_REGEXP, "next token requires contextual specifier to be parsed unambiguously"); break; + case NoneIsOperandYieldEOL: + MOZ_ASSERT(next.modifier == Operand); + MOZ_ASSERT(next.type != TOK_DIV, + "next token requires contextual specifier to be parsed unambiguously"); + break; case OperandIsNone: - // Non-Operand token after yield/continue/break already has - // NoneIsOperand exception. - MOZ_ASSERT(next.modifier == None || - ((next.modifierExceptions & NoneIsOperand) && next.modifier == Operand)); + MOZ_ASSERT(next.modifier == None); MOZ_ASSERT(next.type != TOK_DIV && next.type != TOK_REGEXP, "next token requires contextual specifier to be parsed unambiguously"); break; @@ -452,7 +485,7 @@ class MOZ_STACK_CLASS TokenStream default: MOZ_CRASH("unexpected modifier exception"); } - tokens[(cursor + 1) & ntokensMask].modifierExceptions |= modifierException; + tokens[(cursor + 1) & ntokensMask].modifierException = modifierException; #endif } @@ -473,19 +506,21 @@ class MOZ_STACK_CLASS TokenStream if (modifier == lookaheadToken.modifier) return; - if (lookaheadToken.modifierExceptions & OperandIsNone) { + if (lookaheadToken.modifierException == OperandIsNone) { // getToken(Operand) permissibly following getToken(). if (modifier == Operand && lookaheadToken.modifier == None) return; } - if (lookaheadToken.modifierExceptions & NoneIsOperand) { + if (lookaheadToken.modifierException == NoneIsOperand || + lookaheadToken.modifierException == NoneIsOperandYieldEOL) + { // getToken() permissibly following getToken(Operand). if (modifier == None && lookaheadToken.modifier == Operand) return; } - if (lookaheadToken.modifierExceptions & NoneIsKeywordIsName) { + if (lookaheadToken.modifierException == NoneIsKeywordIsName) { // getToken() permissibly following getToken(KeywordIsName). if (modifier == None && lookaheadToken.modifier == KeywordIsName) return; diff --git a/js/src/jit-test/tests/basic/bug1172503-2.js b/js/src/jit-test/tests/basic/bug1172503-2.js new file mode 100644 index 000000000000..7f1ded8dc357 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1172503-2.js @@ -0,0 +1,10 @@ +var n = 0; +this.__proto__ = new Proxy({}, { + has: function () { + if (++n === 2) + return false; + a = 0; + } +}); +a = 0; +assertEq(a, 0); diff --git a/js/src/jit-test/tests/basic/bug1172503.js b/js/src/jit-test/tests/basic/bug1172503.js new file mode 100644 index 000000000000..5fda66ffe332 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1172503.js @@ -0,0 +1,9 @@ +// |jit-test| error: m is not defined +this.__proto__ = Proxy.create({ + has:function(){ + try { + aa0 = Function(undefined); + } catch (aa) {} + } +}); +m(); diff --git a/js/src/jit-test/tests/basic/bug1189744.js b/js/src/jit-test/tests/basic/bug1189744.js new file mode 100644 index 000000000000..6d202e702ca1 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1189744.js @@ -0,0 +1,11 @@ +var obj; +for (var i = 0; i < 100; i++) + obj = {a: 7, b: 13, c: 42, d: 0}; + +Object.defineProperty(obj, "x", { + get: function () { return 3; } +}); +obj.__ob__ = 17; + +Object.defineProperty(obj, "c", {value: 8, writable: true}); +assertEq(obj.__ob__, 17); diff --git a/js/src/jit-test/tests/gc/oomInWeakMap.js b/js/src/jit-test/tests/gc/oomInWeakMap.js new file mode 100644 index 000000000000..df9867fc647e --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInWeakMap.js @@ -0,0 +1,7 @@ +// |jit-test| --no-ggc; allow-unhandlable-oom; --no-threads + +load(libdir + 'oomTest.js'); +oomTest(function () { + eval(`var wm = new WeakMap(); + wm.set({}, 'FOO').get(false);`); +}); diff --git a/js/src/jit-test/tests/ion/dce-with-rinstructions.js b/js/src/jit-test/tests/ion/dce-with-rinstructions.js index 3226fb641ce6..126b2dabdfad 100644 --- a/js/src/jit-test/tests/ion/dce-with-rinstructions.js +++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js @@ -475,7 +475,8 @@ function rpow_number(i) { var x = Math.pow(i, 3.14159); if (uceFault_pow_number(i) || uceFault_pow_number(i)) assertEq(x, Math.pow(99, 3.14159)); - assertRecoveredOnBailout(x, true); + // POW recovery temporarily disabled. See bug 1188586. + assertRecoveredOnBailout(x, false); return i; } diff --git a/js/src/jit-test/tests/parser/yield-without-operand.js b/js/src/jit-test/tests/parser/yield-without-operand.js index 64a3c23e5a3f..56f041a7306e 100644 --- a/js/src/jit-test/tests/parser/yield-without-operand.js +++ b/js/src/jit-test/tests/parser/yield-without-operand.js @@ -6,8 +6,14 @@ assertNoWarning(() => Function("yield"), SyntaxError, "yield followed by EOF is fine"); assertNoWarning(() => Function("yield;"), SyntaxError, "yield followed by semicolon is fine"); -assertNoWarning(() => Function("yield\n print('ok');"), SyntaxError, +assertNoWarning(() => Function("yield\n"), SyntaxError, "yield followed by newline is fine"); +assertNoWarning(() => Function("yield\n print('ok');"), SyntaxError, + "yield followed by newline and statement is fine"); +assertNoWarning(() => Function("yield\n /x/;"), SyntaxError, + "yield followed by newline and regexp is fine"); +assertThrowsInstanceOf(() => Function("yield\n /"), SyntaxError, + "yield followed by newline and slash is fine"); assertNoWarning(() => eval("(function () { yield; })"), SyntaxError, "yield followed by semicolon in eval code is fine"); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 8024c94044f5..bb0eab23b19b 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -5949,7 +5949,8 @@ class MPow } bool writeRecoverData(CompactBufferWriter& writer) const override; bool canRecoverOnBailout() const override { - return true; + // Temporarily disable recovery to relieve fuzzer pressure. See bug 1188586. + return false; } ALLOW_CLONE(MPow) diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 1312c4a132bc..f0cc12a21d75 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -1021,11 +1021,10 @@ ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t len return false; if (!hole && !v.isNullOrUndefined()) { if (Locale) { - JSObject* robj = ToObject(cx, v); - if (!robj) + RootedValue fun(cx); + if (!GetProperty(cx, v, cx->names().toLocaleString, &fun)) return false; - RootedId id(cx, NameToId(cx->names().toLocaleString)); - if (!robj->callMethod(cx, id, 0, nullptr, &v)) + if (!Invoke(cx, v, fun, 0, nullptr, &v)) return false; } if (!ValueToStringBuffer(cx, v, sb)) diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 53f903ad3f78..85c6691101d5 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2586,8 +2586,10 @@ js::GetOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, } else { // This is either a straight-up data property or (rarely) a // property with a JSGetterOp/JSSetterOp. The latter must be - // reported to the caller as a plain data property, so don't - // populate desc.getter/setter, and mask away the SHARED bit. + // reported to the caller as a plain data property, so clear + // desc.getter/setter, and mask away the SHARED bit. + desc.setGetter(nullptr); + desc.setSetter(nullptr); desc.attributesRef() &= ~JSPROP_SHARED; if (IsImplicitDenseOrTypedArrayElement(shape)) { diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 2f9f794f58f9..acffeca0b38d 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1004,7 +1004,8 @@ GetObjectClassName(JSContext* cx, HandleObject obj); */ /* - * If obj a WindowProxy, return its current inner Window. Otherwise return obj. + * If obj is a WindowProxy, return its current inner Window. Otherwise return + * obj. This function can't fail and never returns nullptr. * * GetInnerObject is called when we need a scope chain; you never want a * WindowProxy on a scope chain. @@ -1027,6 +1028,7 @@ GetInnerObject(JSObject* obj) /* * If obj is a Window object, return the WindowProxy. Otherwise return obj. + * This function can't fail; it never sets an exception or returns nullptr. * * This must be called before passing an object to script, if the object might * be a Window. (But usually those cases involve scope objects, and for those, diff --git a/js/src/jsweakmap.cpp b/js/src/jsweakmap.cpp index ddd8c4e5a22c..a90ae99fa2df 100644 --- a/js/src/jsweakmap.cpp +++ b/js/src/jsweakmap.cpp @@ -355,14 +355,15 @@ SetWeakMapEntryInternal(JSContext* cx, Handle mapObj, { ObjectValueMap* map = mapObj->getMap(); if (!map) { - map = cx->new_(cx, mapObj.get()); - if (!map) + AutoInitGCManagedObject newMap( + cx->make_unique(cx, mapObj.get())); + if (!newMap) return false; - if (!map->init()) { - js_delete(map); + if (!newMap->init()) { JS_ReportOutOfMemory(cx); return false; } + map = newMap.release(); mapObj->setPrivate(map); } diff --git a/js/src/proxy/Proxy.cpp b/js/src/proxy/Proxy.cpp index 33f7a4b36d93..9cbac49a296b 100644 --- a/js/src/proxy/Proxy.cpp +++ b/js/src/proxy/Proxy.cpp @@ -88,17 +88,6 @@ js::assertEnteredPolicy(JSContext* cx, JSObject* proxy, jsid id, } #endif -#define INVOKE_ON_PROTOTYPE(cx, handler, proxy, protoCall) \ - JS_BEGIN_MACRO \ - RootedObject proto(cx); \ - if (!GetPrototype(cx, proxy, &proto)) \ - return false; \ - if (!proto) \ - return true; \ - assertSameCompartment(cx, proxy, proto); \ - return protoCall; \ - JS_END_MACRO \ - bool Proxy::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle desc) @@ -109,13 +98,12 @@ Proxy::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET_PROPERTY_DESCRIPTOR, true); if (!policy.allowed()) return policy.returnValue(); - if (!handler->hasPrototype()) - return handler->getPropertyDescriptor(cx, proxy, id, desc); - if (!handler->getOwnPropertyDescriptor(cx, proxy, id, desc)) - return false; - if (desc.object()) - return true; - INVOKE_ON_PROTOTYPE(cx, handler, proxy, GetPropertyDescriptor(cx, proto, id, desc)); + + // Special case. See the comment on BaseProxyHandler::mHasPrototype. + if (handler->hasPrototype()) + return handler->BaseProxyHandler::getPropertyDescriptor(cx, proxy, id, desc); + + return handler->getPropertyDescriptor(cx, proxy, id, desc); } bool @@ -241,16 +229,23 @@ Proxy::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); if (!policy.allowed()) return policy.returnValue(); - if (!handler->hasPrototype()) - return handler->has(cx, proxy, id, bp); - if (!handler->hasOwn(cx, proxy, id, bp)) - return false; - if (*bp) - return true; - bool Bp; - INVOKE_ON_PROTOTYPE(cx, handler, proxy, - JS_HasPropertyById(cx, proto, id, &Bp) && - ((*bp = Bp) || true)); + + if (handler->hasPrototype()) { + if (!handler->hasOwn(cx, proxy, id, bp)) + return false; + if (*bp) + return true; + + RootedObject proto(cx); + if (!GetPrototype(cx, proxy, &proto)) + return false; + if (!proto) + return true; + + return HasProperty(cx, proto, id, bp); + } + + return handler->has(cx, proxy, id, bp); } bool @@ -265,8 +260,18 @@ Proxy::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) return handler->hasOwn(cx, proxy, id, bp); } +static Value +OuterizeValue(JSContext* cx, HandleValue v) +{ + if (v.isObject()) { + RootedObject obj(cx, &v.toObject()); + return ObjectValue(*GetOuterObject(cx, obj)); + } + return v; +} + bool -Proxy::get(JSContext* cx, HandleObject proxy, HandleObject receiver, HandleId id, +Proxy::get(JSContext* cx, HandleObject proxy, HandleObject receiver_, HandleId id, MutableHandleValue vp) { JS_CHECK_RECURSION(cx, return false); @@ -275,16 +280,26 @@ Proxy::get(JSContext* cx, HandleObject proxy, HandleObject receiver, HandleId id AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); if (!policy.allowed()) return policy.returnValue(); - bool own; - if (!handler->hasPrototype()) { - own = true; - } else { + + // Outerize the receiver. Proxy handlers shouldn't have to know about + // the Window/WindowProxy distinction. + RootedObject receiver(cx, GetOuterObject(cx, receiver_)); + + if (handler->hasPrototype()) { + bool own; if (!handler->hasOwn(cx, proxy, id, &own)) return false; + if (!own) { + RootedObject proto(cx); + if (!GetPrototype(cx, proxy, &proto)) + return false; + if (!proto) + return true; + return GetProperty(cx, proto, receiver, id, vp); + } } - if (own) - return handler->get(cx, proxy, receiver, id, vp); - INVOKE_ON_PROTOTYPE(cx, handler, proxy, GetProperty(cx, proto, receiver, id, vp)); + + return handler->get(cx, proxy, receiver, id, vp); } bool @@ -307,7 +322,7 @@ Proxy::callProp(JSContext* cx, HandleObject proxy, HandleObject receiver, Handle } bool -Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver, +Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver_, ObjectOpResult& result) { JS_CHECK_RECURSION(cx, return false); @@ -319,6 +334,10 @@ Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, Handle return result.succeed(); } + // Outerize the receiver. Proxy handlers shouldn't have to know about + // the Window/WindowProxy distinction. + RootedValue receiver(cx, OuterizeValue(cx, receiver_)); + // Special case. See the comment on BaseProxyHandler::mHasPrototype. if (handler->hasPrototype()) return handler->BaseProxyHandler::set(cx, proxy, id, v, receiver, result); @@ -343,33 +362,35 @@ Proxy::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) JS_CHECK_RECURSION(cx, return false); const BaseProxyHandler* handler = proxy->as().handler(); objp.set(nullptr); // default result if we refuse to perform this action - if (!handler->hasPrototype()) { - AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, - BaseProxyHandler::ENUMERATE, true); - // If the policy denies access but wants us to return true, we need - // to hand a valid (empty) iterator object to the caller. - if (!policy.allowed()) { - return policy.returnValue() && - NewEmptyPropertyIterator(cx, 0, objp); - } - return handler->enumerate(cx, proxy, objp); + + if (handler->hasPrototype()) { + AutoIdVector props(cx); + if (!Proxy::getOwnEnumerablePropertyKeys(cx, proxy, props)) + return false; + + RootedObject proto(cx); + if (!GetPrototype(cx, proxy, &proto)) + return false; + if (!proto) + return EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp); + assertSameCompartment(cx, proxy, proto); + + AutoIdVector protoProps(cx); + return GetPropertyKeys(cx, proto, 0, &protoProps) && + AppendUnique(cx, props, protoProps) && + EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp); } - AutoIdVector props(cx); - if (!Proxy::getOwnEnumerablePropertyKeys(cx, proxy, props)) - return false; + AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, + BaseProxyHandler::ENUMERATE, true); - RootedObject proto(cx); - if (!GetPrototype(cx, proxy, &proto)) - return false; - if (!proto) - return EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp); - assertSameCompartment(cx, proxy, proto); - - AutoIdVector protoProps(cx); - return GetPropertyKeys(cx, proto, 0, &protoProps) && - AppendUnique(cx, props, protoProps) && - EnumeratedIdVectorToIterator(cx, proxy, 0, props, objp); + // If the policy denies access but wants us to return true, we need + // to hand a valid (empty) iterator object to the caller. + if (!policy.allowed()) { + return policy.returnValue() && + NewEmptyPropertyIterator(cx, 0, objp); + } + return handler->enumerate(cx, proxy, objp); } bool diff --git a/js/src/tests/ecma_6/Array/toLocaleString.js b/js/src/tests/ecma_6/Array/toLocaleString.js new file mode 100644 index 000000000000..5bc19a839f22 --- /dev/null +++ b/js/src/tests/ecma_6/Array/toLocaleString.js @@ -0,0 +1,16 @@ +"use strict"; + +Object.defineProperty(String.prototype, "toLocaleString", { + get() { + // Congratulations! You probably fixed primitive-this getters. + // Change "object" to "string". + assertEq(typeof this, "object"); + + return function() { return typeof this; }; + } +}) + +assertEq(["test"].toLocaleString(), "string"); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Object/toLocaleString.js b/js/src/tests/ecma_6/Object/toLocaleString.js new file mode 100644 index 000000000000..bc5a96ea0ab0 --- /dev/null +++ b/js/src/tests/ecma_6/Object/toLocaleString.js @@ -0,0 +1,15 @@ +"use strict"; + +Object.defineProperty(String.prototype, "toString", { + get() { + // Congratulations! You probably fixed primitive-this getters. + // Change "object" to "string". + assertEq(typeof this, "object"); + + return function() { return typeof this; }; + } +}) +assertEq(Object.prototype.toLocaleString.call("test"), "string"); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Proxy/global-receiver.js b/js/src/tests/ecma_6/Proxy/global-receiver.js new file mode 100644 index 000000000000..48db39b23710 --- /dev/null +++ b/js/src/tests/ecma_6/Proxy/global-receiver.js @@ -0,0 +1,29 @@ +// The global object can be the receiver passed to the get and set traps of a Proxy. + +var global = this; +var proto = Object.getPrototypeOf(global); +var gets = 0, sets = 0; +Object.setPrototypeOf(global, new Proxy(proto, { + has(t, id) { + return id === "bareword" || Reflect.has(t, id); + }, + get(t, id, r) { + gets++; + assertEq(r, global); + return Reflect.get(t, id, r); + }, + set(t, id, v, r) { + sets++; + assertEq(r, global); + return Reflect.set(t, id, v, r); + } +})); + +assertEq(bareword, undefined); +assertEq(gets, 1); + +bareword = 12; +assertEq(sets, 1); +assertEq(global.bareword, 12); + +reportCompare(0, 0); diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index bcf691cad204..b143ac843fef 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -2055,7 +2055,7 @@ NativeSetExistingDataProperty(JSContext* cx, HandleNativeObject obj, HandleShape */ bool js::SetPropertyByDefining(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, - HandleValue receiverValue, bool objHasOwn, ObjectOpResult& result) + HandleValue receiverValue, ObjectOpResult& result) { // Step 5.b. if (!receiverValue.isObject()) @@ -2063,25 +2063,7 @@ js::SetPropertyByDefining(JSContext* cx, HandleObject obj, HandleId id, HandleVa RootedObject receiver(cx, &receiverValue.toObject()); bool existing; - if (receiver == obj) { - // Steps 5.c-e.ii. - // The common case. The caller has necessarily done a property lookup - // on obj and passed us the answer as objHasOwn. - // We also know that the property is a data property and writable - // if it exists. -#ifdef DEBUG - // Check that objHasOwn is correct. This could fail if receiver or a - // native object on its prototype chain has a nondeterministic resolve - // hook. We shouldn't have any that are quite that badly behaved. - Rooted desc(cx); - if (!GetOwnPropertyDescriptor(cx, receiver, id, &desc)) - return false; - MOZ_ASSERT(!!desc.object() == objHasOwn); - MOZ_ASSERT_IF(desc.object(), desc.isDataDescriptor()); - MOZ_ASSERT_IF(desc.object(), desc.writable()); -#endif - existing = objHasOwn; - } else { + { // Steps 5.c-d. Rooted desc(cx); if (!GetOwnPropertyDescriptor(cx, receiver, id, &desc)) @@ -2156,7 +2138,7 @@ js::SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, HandleValue RootedObject proto(cx, obj->getProto()); if (proto) return SetProperty(cx, proto, id, v, receiver, result); - return SetPropertyByDefining(cx, obj, id, v, receiver, false, result); + return SetPropertyByDefining(cx, obj, id, v, receiver, result); } /* @@ -2178,7 +2160,7 @@ SetNonexistentProperty(JSContext* cx, HandleNativeObject obj, HandleId id, Handl return false; } - return SetPropertyByDefining(cx, obj, id, v, receiver, false, result); + return SetPropertyByDefining(cx, obj, id, v, receiver, result); } /* @@ -2239,7 +2221,7 @@ SetExistingProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleVa return SetDenseOrTypedArrayElement(cx, pobj, JSID_TO_INT(id), v, result); // Steps 5.b-f. - return SetPropertyByDefining(cx, obj, id, v, receiver, obj == pobj, result); + return SetPropertyByDefining(cx, obj, id, v, receiver, result); } // Step 5 for all other properties. @@ -2277,7 +2259,7 @@ SetExistingProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleVa // Shadow pobj[id] by defining a new data property receiver[id]. // Delegate everything to SetPropertyByDefining. - return SetPropertyByDefining(cx, obj, id, v, receiver, obj == pobj, result); + return SetPropertyByDefining(cx, obj, id, v, receiver, result); } // Steps 6-11. diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index 25d065add92d..7c69472fb2b1 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -1312,7 +1312,7 @@ NativeGetElement(JSContext* cx, HandleNativeObject obj, uint32_t index, MutableH bool SetPropertyByDefining(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, - HandleValue receiver, bool objHasOwn, ObjectOpResult& result); + HandleValue receiver, ObjectOpResult& result); bool SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp index 13cb4443be36..6460e3556993 100644 --- a/js/src/vm/UnboxedObject.cpp +++ b/js/src/vm/UnboxedObject.cpp @@ -818,7 +818,7 @@ UnboxedPlainObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id return SetProperty(cx, obj, id, v, receiver, result); } - return SetPropertyByDefining(cx, obj, id, v, receiver, false, result); + return SetPropertyByDefining(cx, obj, id, v, receiver, result); } if (UnboxedExpandoObject* expando = obj->as().maybeExpando()) { @@ -1485,7 +1485,7 @@ UnboxedArrayObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id return SetProperty(cx, obj, id, v, receiver, result); } - return SetPropertyByDefining(cx, obj, id, v, receiver, false, result); + return SetPropertyByDefining(cx, obj, id, v, receiver, result); } return SetPropertyOnProto(cx, obj, id, v, receiver, result); diff --git a/layout/style/crashtests/1146101-1.html b/layout/style/crashtests/1146101-1.html new file mode 100644 index 000000000000..e3f8f2aa3f59 --- /dev/null +++ b/layout/style/crashtests/1146101-1.html @@ -0,0 +1,10 @@ + + + +
diff --git a/layout/style/crashtests/crashtests.list b/layout/style/crashtests/crashtests.list index 3028fd765886..444071f15209 100644 --- a/layout/style/crashtests/crashtests.list +++ b/layout/style/crashtests/crashtests.list @@ -112,6 +112,7 @@ load 1066089-1.html load 1074651-1.html pref(dom.webcomponents.enabled,true) load 1089463-1.html pref(layout.css.expensive-style-struct-assertions.enabled,true) load 1136010-1.html +pref(layout.css.expensive-style-struct-assertions.enabled,true) load 1146101-1.html load 1153693-1.html load 1161320-1.html pref(dom.animations-api.core.enabled,true) load 1161320-2.html diff --git a/modules/libjar/nsJARChannel.cpp b/modules/libjar/nsJARChannel.cpp index eeffff82eb6e..3a027ca3a6d2 100644 --- a/modules/libjar/nsJARChannel.cpp +++ b/modules/libjar/nsJARChannel.cpp @@ -933,6 +933,10 @@ nsJARChannel::OverrideWithSynthesizedResponse(nsIInputStream* aSynthesizedInput, NS_IMETHODIMP nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx) { + MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetEnforceSecurity(), + "security flags in loadInfo but asyncOpen2() not called"); + LOG(("nsJARChannel::AsyncOpen [this=%x]\n", this)); NS_ENSURE_ARG_POINTER(listener); diff --git a/netwerk/base/nsBaseChannel.cpp b/netwerk/base/nsBaseChannel.cpp index cde055f4be0b..8568b5f968a2 100644 --- a/netwerk/base/nsBaseChannel.cpp +++ b/netwerk/base/nsBaseChannel.cpp @@ -626,6 +626,10 @@ nsBaseChannel::Open2(nsIInputStream** aStream) NS_IMETHODIMP nsBaseChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) { + MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED); NS_ENSURE_TRUE(!mPump, NS_ERROR_IN_PROGRESS); NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); diff --git a/netwerk/base/nsBaseChannel.h b/netwerk/base/nsBaseChannel.h index 717ed60b6337..56b56bc4c1ad 100644 --- a/netwerk/base/nsBaseChannel.h +++ b/netwerk/base/nsBaseChannel.h @@ -269,7 +269,6 @@ private: nsCOMPtr mProgressSink; nsCOMPtr mOriginalURI; nsCOMPtr mOwner; - nsCOMPtr mLoadInfo; nsCOMPtr mSecurityInfo; nsCOMPtr mRedirectChannel; nsCString mContentType; @@ -285,6 +284,7 @@ private: protected: nsCOMPtr mURI; nsCOMPtr mLoadGroup; + nsCOMPtr mLoadInfo; nsCOMPtr mCallbacks; nsCOMPtr mListener; nsCOMPtr mListenerContext; diff --git a/netwerk/protocol/app/AppProtocolHandler.cpp b/netwerk/protocol/app/AppProtocolHandler.cpp index 94b41ae5d86c..48f892b1a0e3 100644 --- a/netwerk/protocol/app/AppProtocolHandler.cpp +++ b/netwerk/protocol/app/AppProtocolHandler.cpp @@ -111,6 +111,10 @@ DummyChannel::Open2(nsIInputStream** aStream) NS_IMETHODIMP DummyChannel::AsyncOpen(nsIStreamListener* aListener, nsISupports* aContext) { + MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + mListener = aListener; mListenerContext = aContext; mPending = true; diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index 4ab6c4a42dad..17a239650147 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -1488,6 +1488,10 @@ HttpChannelChild::GetSecurityInfo(nsISupports **aSecurityInfo) NS_IMETHODIMP HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext) { + MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + LOG(("HttpChannelChild::AsyncOpen [this=%p uri=%s]\n", this, mSpec.get())); if (mCanceled) diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 79ceb97d9c84..0f011e49c640 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -4953,6 +4953,10 @@ nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo) NS_IMETHODIMP nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context) { + MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this)); NS_ENSURE_ARG_POINTER(listener); diff --git a/netwerk/protocol/rtsp/RtspChannelChild.cpp b/netwerk/protocol/rtsp/RtspChannelChild.cpp index 08e7d58975cf..f6c8f7904efa 100644 --- a/netwerk/protocol/rtsp/RtspChannelChild.cpp +++ b/netwerk/protocol/rtsp/RtspChannelChild.cpp @@ -107,6 +107,10 @@ private: NS_IMETHODIMP RtspChannelChild::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) { + MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + // Precondition checks. MOZ_ASSERT(aListener); nsCOMPtr uri = nsBaseChannel::URI(); diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp index 1edb63c54e2d..8824e9b1215f 100644 --- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp +++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp @@ -258,6 +258,15 @@ nsViewSourceChannel::Open2(nsIInputStream** aStream) NS_IMETHODIMP nsViewSourceChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *ctxt) { +#ifdef DEBUG + { + nsCOMPtr loadInfo = mChannel->GetLoadInfo(); + MOZ_ASSERT(!loadInfo || loadInfo->GetSecurityMode() == 0 || + loadInfo->GetEnforceSecurity(), + "security flags in loadInfo but asyncOpen2() not called"); + } +#endif + NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE); mListener = aListener; diff --git a/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp index f041c11dbcd7..ca16a2779fac 100644 --- a/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp +++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp @@ -626,6 +626,10 @@ GetTabChild(nsIChannel* aChannel) NS_IMETHODIMP WyciwygChannelChild::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) { + MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + LOG(("WyciwygChannelChild::AsyncOpen [this=%p]\n", this)); // The only places creating wyciwyg: channels should be diff --git a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp index 0e27a1397e68..51d838d870f7 100644 --- a/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp +++ b/netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp @@ -431,6 +431,10 @@ nsWyciwygChannel::Open2(nsIInputStream** aStream) NS_IMETHODIMP nsWyciwygChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx) { + MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + LOG(("nsWyciwygChannel::AsyncOpen [this=%p]\n", this)); MOZ_ASSERT(mMode == NONE, "nsWyciwygChannel already open"); diff --git a/security/manager/ssl/nsSecureBrowserUIImpl.cpp b/security/manager/ssl/nsSecureBrowserUIImpl.cpp index 3db3e709a6ff..a9db36ccdb6c 100644 --- a/security/manager/ssl/nsSecureBrowserUIImpl.cpp +++ b/security/manager/ssl/nsSecureBrowserUIImpl.cpp @@ -263,18 +263,24 @@ nsSecureBrowserUIImpl::MapInternalToExternalState(uint32_t* aState, lockIconStat *aState |= nsIWebProgressListener::STATE_IDENTITY_EV_TOPLEVEL; } } - // * If so, the state should be broken; overriding the previous state - // set by the lock parameter. + // * If so, the state should be broken or insecure; overriding the previous + // state set by the lock parameter. + uint32_t tempState = STATE_IS_BROKEN; + if (lock == lis_no_security) { + // this is to ensure that http: pages with mixed content in nested + // iframes don't get marked as broken instead of insecure + tempState = STATE_IS_INSECURE; + } if (docShell->GetHasMixedActiveContentLoaded() && docShell->GetHasMixedDisplayContentLoaded()) { - *aState = STATE_IS_BROKEN | + *aState = tempState | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT; } else if (docShell->GetHasMixedActiveContentLoaded()) { - *aState = STATE_IS_BROKEN | + *aState = tempState | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT; } else if (docShell->GetHasMixedDisplayContentLoaded()) { - *aState = STATE_IS_BROKEN | + *aState = tempState | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT; } diff --git a/testing/config/mozharness/b2g_emulator_config.py b/testing/config/mozharness/b2g_emulator_config.py index a3ec67493d4b..cd98e74aecb3 100644 --- a/testing/config/mozharness/b2g_emulator_config.py +++ b/testing/config/mozharness/b2g_emulator_config.py @@ -12,7 +12,6 @@ config = { "--addEnv", "LD_LIBRARY_PATH=/vendor/lib:/system/lib:/system/b2g", "--with-b2g-emulator=%(b2gpath)s", - "--skip-manifest=b2g_cppunittest_manifest.txt", "." ], "run_filename": "remotecppunittests.py", diff --git a/testing/mach_commands.py b/testing/mach_commands.py index 9502ff95d2bf..85ecc7678964 100644 --- a/testing/mach_commands.py +++ b/testing/mach_commands.py @@ -299,9 +299,10 @@ class MachCommands(MachCommandBase): if len(params['test_files']) == 0: testdir = os.path.join(self.distdir, 'cppunittests') - tests = cppunittests.extract_unittests_from_args([testdir], mozinfo.info) + manifest = os.path.join(self.topsrcdir, 'testing', 'cppunittest.ini') + tests = cppunittests.extract_unittests_from_args([testdir], mozinfo.info, manifest) else: - tests = cppunittests.extract_unittests_from_args(params['test_files'], mozinfo.info) + tests = cppunittests.extract_unittests_from_args(params['test_files'], mozinfo.info, None) # See if we have crash symbols symbols_path = os.path.join(self.distdir, 'crashreporter-symbols') diff --git a/testing/mozharness/scripts/spidermonkey_build.py b/testing/mozharness/scripts/spidermonkey_build.py index b05f45aea18b..99bf5b55bb07 100755 --- a/testing/mozharness/scripts/spidermonkey_build.py +++ b/testing/mozharness/scripts/spidermonkey_build.py @@ -349,6 +349,14 @@ class SpidermonkeyBuild(MockMixin, def checkout_tools(self): dirs = self.query_abs_dirs() + + # If running from within a directory also passed as the --source dir, + # this has the danger of clobbering /tools/ + if self.config['source']: + srcdir = self.config['source'] + if os.path.samefile(srcdir, os.path.dirname(dirs['abs_tools_dir'])): + raise Exception("Cannot run from source checkout to avoid overwriting subdirs") + rev = self.vcs_checkout( vcs='hg', # Don't have hgtool.py yet repo=self.config['tools_repo'], diff --git a/testing/remotecppunittests.py b/testing/remotecppunittests.py index ce7d9c20a3c8..1ffebd506b74 100644 --- a/testing/remotecppunittests.py +++ b/testing/remotecppunittests.py @@ -114,7 +114,8 @@ class RemoteCPPUnitTests(cppunittests.CPPUnitTests): return env - def run_one_test(self, prog, env, symbols_path=None, interactive=False): + def run_one_test(self, prog, env, symbols_path=None, interactive=False, + timeout_factor=1): """ Run a single C++ unit test program remotely. @@ -123,6 +124,7 @@ class RemoteCPPUnitTests(cppunittests.CPPUnitTests): * env: The environment to use for running the program. * symbols_path: A path to a directory containing Breakpad-formatted symbol files for producing stack traces on crash. + * timeout_factor: An optional test-specific timeout multiplier. Return True if the program exits with a zero status, False otherwise. """ @@ -130,8 +132,9 @@ class RemoteCPPUnitTests(cppunittests.CPPUnitTests): remote_bin = posixpath.join(self.remote_bin_dir, basename) self.log.test_start(basename) buf = StringIO.StringIO() + test_timeout = cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT * timeout_factor returncode = self.device.shell([remote_bin], buf, env=env, cwd=self.remote_home_dir, - timeout=cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT) + timeout=test_timeout) self.log.process_output(basename, "\n%s" % buf.getvalue(), command=[remote_bin]) with mozfile.TemporaryDirectory() as tempdir: @@ -254,8 +257,10 @@ def main(): options.xre_path = os.path.abspath(options.xre_path) cppunittests.update_mozinfo() - progs = cppunittests.extract_unittests_from_args(args, mozinfo.info) - tester = RemoteCPPUnitTests(dm, options, progs) + progs = cppunittests.extract_unittests_from_args(args, + mozinfo.info, + options.manifest_path) + tester = RemoteCPPUnitTests(dm, options, [item[0] for item in progs]) try: result = tester.run_tests(progs, options.xre_path, options.symbols_path) except Exception, e: diff --git a/testing/runcppunittests.py b/testing/runcppunittests.py index c182ea204cc8..d22a58524a76 100644 --- a/testing/runcppunittests.py +++ b/testing/runcppunittests.py @@ -24,7 +24,8 @@ class CPPUnitTests(object): # Time (seconds) in which process will be killed if it produces no output. TEST_PROC_NO_OUTPUT_TIMEOUT = 300 - def run_one_test(self, prog, env, symbols_path=None, interactive=False): + def run_one_test(self, prog, env, symbols_path=None, interactive=False, + timeout_factor=1): """ Run a single C++ unit test program. @@ -33,6 +34,7 @@ class CPPUnitTests(object): * env: The environment to use for running the program. * symbols_path: A path to a directory containing Breakpad-formatted symbol files for producing stack traces on crash. + * timeout_factor: An optional test-specific timeout multiplier. Return True if the program exits with a zero status, False otherwise. """ @@ -53,7 +55,8 @@ class CPPUnitTests(object): processOutputLine=lambda _: None) #TODO: After bug 811320 is fixed, don't let .run() kill the process, # instead use a timeout in .wait() and then kill to get a stack. - proc.run(timeout=CPPUnitTests.TEST_PROC_TIMEOUT, + test_timeout = CPPUnitTests.TEST_PROC_TIMEOUT * timeout_factor + proc.run(timeout=test_timeout, outputTimeout=CPPUnitTests.TEST_PROC_NO_OUTPUT_TIMEOUT) proc.wait() if proc.output: @@ -133,7 +136,7 @@ class CPPUnitTests(object): Run a set of C++ unit test programs. Arguments: - * programs: An iterable containing paths to test programs. + * programs: An iterable containing (test path, test timeout factor) tuples * xre_path: A path to a directory containing a XUL Runtime Environment. * symbols_path: A path to a directory containing Breakpad-formatted symbol files for producing stack traces on crash. @@ -148,7 +151,10 @@ class CPPUnitTests(object): pass_count = 0 fail_count = 0 for prog in programs: - single_result = self.run_one_test(prog, env, symbols_path, interactive) + test_path = prog[0] + timeout_factor = prog[1] + single_result = self.run_one_test(test_path, env, symbols_path, + interactive, timeout_factor) if single_result: pass_count += 1 else: @@ -172,33 +178,41 @@ class CPPUnittestOptions(OptionParser): action = "store", type = "string", dest = "symbols_path", default = None, help = "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols") - self.add_option("--skip-manifest", - action = "store", type = "string", dest = "manifest_file", + self.add_option("--manifest-path", + action = "store", type = "string", dest = "manifest_path", default = None, - help = "absolute path to a manifest file") + help = "path to test manifest, if different from the path to test binaries") -def extract_unittests_from_args(args, environ): +def extract_unittests_from_args(args, environ, manifest_path): """Extract unittests from args, expanding directories as needed""" mp = manifestparser.TestManifest(strict=True) tests = [] - for p in args: - if os.path.isdir(p): - try: - mp.read(os.path.join(p, 'cppunittest.ini')) - except IOError: - tests.extend([os.path.abspath(os.path.join(p, x)) for x in os.listdir(p)]) - else: - tests.append(os.path.abspath(p)) + binary_path = None + + if manifest_path: + mp.read(manifest_path) + binary_path = os.path.abspath(args[0]) + else: + for p in args: + if os.path.isdir(p): + try: + mp.read(os.path.join(p, 'cppunittest.ini')) + except IOError: + tests.extend([(os.path.abspath(os.path.join(p, x)), 1) for x in os.listdir(p)]) + else: + tests.append((os.path.abspath(p), 1)) # we skip the existence check here because not all tests are built # for all platforms (and it will fail on Windows anyway) - if mozinfo.isWin: - tests.extend([test['path'] + '.exe' for test in mp.active_tests(exists=False, disabled=False, **environ)]) + active_tests = mp.active_tests(exists=False, disabled=False, **environ) + suffix = '.exe' if mozinfo.isWin else '' + if binary_path: + tests.extend([(os.path.join(binary_path, test['relpath'] + suffix), int(test.get('requesttimeoutfactor', 1))) for test in active_tests]) else: - tests.extend([test['path'] for test in mp.active_tests(exists=False, disabled=False, **environ)]) + tests.extend([(test['path'] + suffix, int(test.get('requesttimeoutfactor', 1))) for test in active_tests]) # skip non-existing tests - tests = [test for test in tests if os.path.isfile(test)] + tests = [test for test in tests if os.path.isfile(test[0])] return tests @@ -223,12 +237,15 @@ def main(): if not options.xre_path: print >>sys.stderr, """Error: --xre-path is required""" sys.exit(1) + if options.manifest_path and len(args) > 1: + print >>sys.stderr, "Error: multiple arguments not supported with --test-manifest" + sys.exit(1) log = mozlog.commandline.setup_logging("cppunittests", options, {"tbpl": sys.stdout}) update_mozinfo() - progs = extract_unittests_from_args(args, mozinfo.info) + progs = extract_unittests_from_args(args, mozinfo.info, options.manifest_path) options.xre_path = os.path.abspath(options.xre_path) if mozinfo.isMac: options.xre_path = os.path.join(os.path.dirname(options.xre_path), 'Resources') diff --git a/toolkit/components/gfx/SanityTest.js b/toolkit/components/gfx/SanityTest.js index 9603b54acf7e..256eb0c0d70e 100644 --- a/toolkit/components/gfx/SanityTest.js +++ b/toolkit/components/gfx/SanityTest.js @@ -74,6 +74,16 @@ function reportTestReason(val) { histogram.add(val); } +function reportSnapshotContents(canvas) { + try { + var data = canvas.toDataURL(); + Cc['@mozilla.org/observer-service;1']. + getService(Ci.nsIObserverService). + notifyObservers(null, "graphics-sanity-test-failed", data); + } catch (e) { + } +} + function annotateCrashReport(value) { try { // "1" if we're annotating the crash report, "" to remove the annotation. @@ -167,7 +177,9 @@ let listener = { // Perform the compositor backbuffer test, which currently we use for // actually deciding whether to enable hardware media decoding. - testCompositor(this.win, this.ctx); + if (!testCompositor(this.win, this.ctx)) { + reportSnapshotContents(this.canvas); + } this.endTest(); }, diff --git a/toolkit/components/telemetry/TelemetryEnvironment.jsm b/toolkit/components/telemetry/TelemetryEnvironment.jsm index a744092a9f3a..e327d70bf23d 100644 --- a/toolkit/components/telemetry/TelemetryEnvironment.jsm +++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm @@ -161,6 +161,7 @@ const EXPERIMENTS_CHANGED_TOPIC = "experiments-changed"; const SEARCH_ENGINE_MODIFIED_TOPIC = "browser-search-engine-modified"; const SEARCH_SERVICE_TOPIC = "browser-search-service"; const COMPOSITOR_CREATED_TOPIC = "compositor:created"; +const SANITY_TEST_FAILED_TOPIC = "graphics-sanity-test-failed"; /** * Get the current browser. @@ -814,6 +815,7 @@ EnvironmentCache.prototype = { Services.obs.addObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC, false); Services.obs.addObserver(this, SEARCH_SERVICE_TOPIC, false); Services.obs.addObserver(this, COMPOSITOR_CREATED_TOPIC, false); + Services.obs.addObserver(this, SANITY_TEST_FAILED_TOPIC, false); }, _removeObservers: function () { @@ -821,6 +823,7 @@ EnvironmentCache.prototype = { Services.obs.removeObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC); Services.obs.removeObserver(this, SEARCH_SERVICE_TOPIC); Services.obs.removeObserver(this, COMPOSITOR_CREATED_TOPIC); + Services.obs.removeObserver(this, SANITY_TEST_FAILED_TOPIC); }, observe: function (aSubject, aTopic, aData) { @@ -846,6 +849,9 @@ EnvironmentCache.prototype = { // first compositor to be created and then query nsIGfxInfo again. this._onCompositorCreated(); break; + case SANITY_TEST_FAILED_TOPIC: + this._onGraphicsSanityTestFailed(aData); + break; } }, @@ -925,6 +931,11 @@ EnvironmentCache.prototype = { } }, + _onGraphicsSanityTestFailed: function (aData) { + let gfxData = this._currentEnvironment.system.gfx; + gfxData.sanityTestSnapshot = aData; + }, + /** * Get the build data in object form. * @return Object containing the build data. diff --git a/toolkit/content/contentAreaUtils.js b/toolkit/content/contentAreaUtils.js index 80ba11140380..117b65ffb991 100644 --- a/toolkit/content/contentAreaUtils.js +++ b/toolkit/content/contentAreaUtils.js @@ -129,7 +129,7 @@ function saveImageURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache, // This is like saveDocument, but takes any browser/frame-like element // (nsIFrameLoaderOwner) and saves the current document inside it, // whether in-process or out-of-process. -function saveBrowser(aBrowser, aSkipPrompt) +function saveBrowser(aBrowser, aSkipPrompt, aOuterWindowID=0) { if (!aBrowser) { throw "Must have a browser when calling saveBrowser"; @@ -137,13 +137,15 @@ function saveBrowser(aBrowser, aSkipPrompt) let persistable = aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner) .frameLoader .QueryInterface(Ci.nsIWebBrowserPersistable); - persistable.startPersistence({ + let stack = Components.stack.caller; + persistable.startPersistence(aOuterWindowID, { onDocumentReady: function (document) { saveDocument(document, aSkipPrompt); + }, + onError: function (status) { + throw new Components.Exception("saveBrowser failed asynchronously in startPersistence", + status, stack); } - // This interface also has an |onError| method which takes an - // nsresult, but in case of asynchronous failure there isn't - // really anything useful that can be done here. }); } @@ -153,8 +155,7 @@ function saveBrowser(aBrowser, aSkipPrompt) // aDocument can also be a CPOW for a remote nsIDOMDocument, in which // case "save as" modes that serialize the document's DOM are // unavailable. This is a temporary measure for the "Save Frame As" -// command (bug 1141337), and it's possible that there could be -// add-ons doing something similar. +// command (bug 1141337) and pre-e10s add-ons. function saveDocument(aDocument, aSkipPrompt) { const Ci = Components.interfaces; diff --git a/toolkit/devtools/server/tests/unit/xpcshell.ini b/toolkit/devtools/server/tests/unit/xpcshell.ini index a644efa6d667..1eb4730237d9 100644 --- a/toolkit/devtools/server/tests/unit/xpcshell.ini +++ b/toolkit/devtools/server/tests/unit/xpcshell.ini @@ -44,6 +44,7 @@ support-files = [test_SaveHeapSnapshot.js] [test_ReadHeapSnapshot.js] [test_ReadHeapSnapshot_worker.js] +skip-if = os == 'linux' # Bug 1176173 [test_dbgactor.js] [test_dbgglobal.js] [test_dbgclient_debuggerstatement.js] diff --git a/uriloader/exthandler/nsExternalProtocolHandler.cpp b/uriloader/exthandler/nsExternalProtocolHandler.cpp index 08ef7bf47a8d..0143c176e89b 100644 --- a/uriloader/exthandler/nsExternalProtocolHandler.cpp +++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp @@ -186,6 +186,10 @@ NS_IMETHODIMP nsExtProtocolChannel::Open2(nsIInputStream** aStream) NS_IMETHODIMP nsExtProtocolChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) { + MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone(), + "security flags in loadInfo but asyncOpen2() not called"); + NS_ENSURE_ARG_POINTER(listener); NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); diff --git a/webapprt/content/webapp.js b/webapprt/content/webapp.js index 88310c4618dc..0a1a5d6a9d5c 100644 --- a/webapprt/content/webapp.js +++ b/webapprt/content/webapp.js @@ -20,11 +20,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter", "nsICrashReporter"); #endif -function isSameOrigin(url) { - let origin = Services.io.newURI(url, null, null).prePath; - return (origin == WebappRT.config.app.origin); -} - let progressListener = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]), @@ -51,15 +46,30 @@ let progressListener = { } } + let isSameOrigin = (location.prePath === WebappRT.config.app.origin); + // Set the title of the window to the name of the webapp, adding the origin // of the page being loaded if it's from a different origin than the app // (per security bug 741955, which specifies that other-origin pages loaded // in runtime windows must be identified in chrome). let title = WebappRT.localeManifest.name; - if (!isSameOrigin(location.spec)) { + if (!isSameOrigin) { title = location.prePath + " - " + title; } document.documentElement.setAttribute("title", title); + +#ifndef XP_WIN +#ifndef XP_MACOSX + if (isSameOrigin) { + // On non-Windows platforms, we open new windows in fullscreen mode + // if the opener window is in fullscreen mode, so we hide the menubar; + // but on Mac we don't need to hide the menubar. + if (document.mozFullScreenElement) { + document.getElementById("main-menubar").style.display = "none"; + } + } +#endif +#endif }, onStateChange: function onStateChange(aProgress, aRequest, aFlags, aStatus) { @@ -72,42 +82,38 @@ let progressListener = { }; function onOpenWindow(event) { - let name = event.detail.name; - - if (name == "_blank") { + if (event.detail.name === "_blank") { let uri = Services.io.newURI(event.detail.url, null, null); + // Prevent the default handler so nsContentTreeOwner.ProvideWindow + // doesn't create the window itself. + event.preventDefault(); + // Direct the URL to the browser. Cc["@mozilla.org/uriloader/external-protocol-service;1"]. getService(Ci.nsIExternalProtocolService). getProtocolHandlerInfo(uri.scheme). launchWithURI(uri); - } else { - let win = window.openDialog("chrome://webapprt/content/webapp.xul", - name, - "chrome,dialog=no,resizable," + event.detail.features); + } - win.addEventListener("load", function onLoad() { - win.removeEventListener("load", onLoad, false); + // Otherwise, don't do anything to make nsContentTreeOwner.ProvideWindow + // create the window itself and return it to the window.open caller. +} -#ifndef XP_WIN -#ifndef XP_MACOSX - if (isSameOrigin(event.detail.url)) { - // On non-Windows platforms, we open new windows in fullscreen mode - // if the opener window is in fullscreen mode, so we hide the menubar; - // but on Mac we don't need to hide the menubar. - if (document.mozFullScreenElement) { - win.document.getElementById("main-menubar").style.display = "none"; - } - } -#endif -#endif - - win.document.getElementById("content").docShell.setIsApp(WebappRT.appID); - win.document.getElementById("content").setAttribute("src", event.detail.url); - }, false); +function onDOMContentLoaded() { + window.removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); + // The initial window's app ID is set by Startup.jsm before the app + // is loaded, so this code only handles subsequent windows that are opened + // by the app via window.open calls. We do this on DOMContentLoaded + // in order to ensure it gets set before the window's content is loaded. + if (gAppBrowser.docShell.appId === Ci.nsIScriptSecurityManager.NO_APP_ID) { + // Set the principal to the correct app ID. Since this is a subsequent + // window, we know that WebappRT.configPromise has been resolved, so we + // don't have to yield to it before accessing WebappRT.appID. + gAppBrowser.docShell.setIsApp(WebappRT.appID); } } +window.addEventListener("DOMContentLoaded", onDOMContentLoaded, false); function onLoad() { window.removeEventListener("load", onLoad, false); diff --git a/webapprt/test/chrome/browser_window-open.js b/webapprt/test/chrome/browser_window-open.js index 46405223d00e..4027b8cd0249 100644 --- a/webapprt/test/chrome/browser_window-open.js +++ b/webapprt/test/chrome/browser_window-open.js @@ -27,7 +27,12 @@ function test() { winAppBrowser.addEventListener("load", function onLoadBrowser() { winAppBrowser.removeEventListener("load", onLoadBrowser, true); - is(winAppBrowser.getAttribute("src"), + let contentWindow = Cu.waiveXrays(gAppBrowser.contentDocument.defaultView); + is(contentWindow.openedWindow.location.href, + "http://test/webapprtChrome/webapprt/test/chrome/sample.html", + "window.open returns window with correct URL"); + + is(winAppBrowser.documentURI.spec, "http://test/webapprtChrome/webapprt/test/chrome/sample.html", "New window browser has correct src"); diff --git a/webapprt/test/chrome/window-open.html b/webapprt/test/chrome/window-open.html index 5673512e4280..437b92c7f632 100644 --- a/webapprt/test/chrome/window-open.html +++ b/webapprt/test/chrome/window-open.html @@ -4,8 +4,9 @@ Window Open Test App diff --git a/widget/android/GeneratedJNIWrappers.h b/widget/android/GeneratedJNIWrappers.h index 1e6ec00451c4..22dfd9e19adb 100644 --- a/widget/android/GeneratedJNIWrappers.h +++ b/widget/android/GeneratedJNIWrappers.h @@ -24,7 +24,7 @@ public: "org/mozilla/gecko/ANRReporter"; protected: - using Class::Class; + ANRReporter(jobject instance) : Class(instance) {} public: struct GetNativeStack_t { @@ -88,7 +88,7 @@ public: "org/mozilla/gecko/DownloadsIntegration"; protected: - using Class::Class; + DownloadsIntegration(jobject instance) : Class(instance) {} public: struct ScanMedia_t { @@ -123,7 +123,7 @@ public: "org/mozilla/gecko/GeckoAppShell"; protected: - using Class::Class; + GeckoAppShell(jobject instance) : Class(instance) {} public: struct AcknowledgeEvent_t { @@ -1655,7 +1655,7 @@ public: "org/mozilla/gecko/GeckoJavaSampler"; protected: - using Class::Class; + GeckoJavaSampler(jobject instance) : Class(instance) {} public: struct GetFrameNameJavaProfilingWrapper_t { @@ -1798,7 +1798,7 @@ public: "org/mozilla/gecko/GeckoThread"; protected: - using Class::Class; + GeckoThread(jobject instance) : Class(instance) {} public: class State; @@ -1889,7 +1889,7 @@ public: "org/mozilla/gecko/GeckoThread$State"; protected: - using Class::Class; + State(jobject instance) : Class(instance) {} public: struct New_t { @@ -2168,7 +2168,7 @@ public: "org/mozilla/gecko/RestrictedProfiles"; protected: - using Class::Class; + RestrictedProfiles(jobject instance) : Class(instance) {} public: struct IsAllowed_t { @@ -2220,7 +2220,7 @@ public: "org/mozilla/gecko/SurfaceBits"; protected: - using Class::Class; + SurfaceBits(jobject instance) : Class(instance) {} public: struct New_t { @@ -2329,7 +2329,7 @@ public: "org/mozilla/gecko/ThumbnailHelper"; protected: - using Class::Class; + ThumbnailHelper(jobject instance) : Class(instance) {} public: struct SendThumbnail_t { @@ -2366,7 +2366,7 @@ public: "org/mozilla/gecko/gfx/DisplayPortMetrics"; protected: - using Class::Class; + DisplayPortMetrics(jobject instance) : Class(instance) {} public: struct New_t { @@ -2438,7 +2438,7 @@ public: "org/mozilla/gecko/gfx/GLController"; protected: - using Class::Class; + GLController(jobject instance) : Class(instance) {} public: struct CreateEGLSurfaceForCompositorWrapper_t { @@ -2471,7 +2471,7 @@ public: "org/mozilla/gecko/gfx/GeckoLayerClient"; protected: - using Class::Class; + GeckoLayerClient(jobject instance) : Class(instance) {} public: struct ActivateProgram_t { @@ -2721,7 +2721,7 @@ public: "org/mozilla/gecko/gfx/ImmutableViewportMetrics"; protected: - using Class::Class; + ImmutableViewportMetrics(jobject instance) : Class(instance) {} public: struct New_t { @@ -2767,7 +2767,7 @@ public: "org/mozilla/gecko/gfx/LayerView"; protected: - using Class::Class; + LayerView(jobject instance) : Class(instance) {} public: struct RegisterCompositorWrapper_t { @@ -2818,7 +2818,7 @@ public: "org/mozilla/gecko/gfx/NativePanZoomController"; protected: - using Class::Class; + NativePanZoomController(jobject instance) : Class(instance) {} public: struct RequestContentRepaintWrapper_t { @@ -2856,7 +2856,7 @@ public: "org/mozilla/gecko/gfx/ProgressiveUpdateData"; protected: - using Class::Class; + ProgressiveUpdateData(jobject instance) : Class(instance) {} public: struct New_t { @@ -2983,7 +2983,7 @@ public: "org/mozilla/gecko/gfx/ViewTransform"; protected: - using Class::Class; + ViewTransform(jobject instance) : Class(instance) {} public: struct New_t { @@ -3190,7 +3190,7 @@ public: "org/mozilla/gecko/sqlite/MatrixBlobCursor"; protected: - using Class::Class; + MatrixBlobCursor(jobject instance) : Class(instance) {} public: struct New_t { @@ -3298,7 +3298,7 @@ public: "org/mozilla/gecko/sqlite/SQLiteBridgeException"; protected: - using Class::Class; + SQLiteBridgeException(jobject instance) : Class(instance) {} public: struct New_t { @@ -3366,7 +3366,7 @@ public: "org/mozilla/gecko/util/Clipboard"; protected: - using Class::Class; + Clipboard(jobject instance) : Class(instance) {} public: struct ClearText_t { @@ -3451,7 +3451,7 @@ public: "org/mozilla/gecko/util/NativeJSContainer"; protected: - using Class::Class; + NativeJSContainer(jobject instance) : Class(instance) {} public: struct New_t { @@ -3516,7 +3516,7 @@ public: "org/mozilla/gecko/util/NativeJSObject"; protected: - using Class::Class; + NativeJSObject(jobject instance) : Class(instance) {} public: struct New_t { diff --git a/widget/android/nsAppShell.cpp b/widget/android/nsAppShell.cpp index 5ac14203aafb..e713991138f4 100644 --- a/widget/android/nsAppShell.cpp +++ b/widget/android/nsAppShell.cpp @@ -140,6 +140,14 @@ nsAppShell::nsAppShell() return; } + if (jni::IsAvailable()) { + // Initialize JNI and Set the corresponding state in GeckoThread. + AndroidBridge::ConstructBridge(); + mozilla::ANRReporter::Init(); + + widget::GeckoThread::SetState(widget::GeckoThread::State::JNI_READY()); + } + sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); if (sPowerManagerService) { @@ -147,16 +155,6 @@ nsAppShell::nsAppShell() } else { NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!"); } - - // Initialize JNI and Set the corresponding state in GeckoThread. - - if (!jni::IsAvailable()) { - return; - } - AndroidBridge::ConstructBridge(); - mozilla::ANRReporter::Init(); - - widget::GeckoThread::SetState(widget::GeckoThread::State::JNI_READY()); } nsAppShell::~nsAppShell() @@ -170,7 +168,9 @@ nsAppShell::~nsAppShell() sWakeLockListener = nullptr; } - AndroidBridge::DeconstructBridge(); + if (jni::IsAvailable()) { + AndroidBridge::DeconstructBridge(); + } } void diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index 70f77173777b..f000556cdba2 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -2798,6 +2798,7 @@ RectTextureImage::Draw(GLManager* aManager, ShaderProgramOGL* program = aManager->GetProgram(LOCAL_GL_TEXTURE_RECTANGLE_ARB, gfx::SurfaceFormat::R8G8B8A8); + aManager->gl()->fActiveTexture(LOCAL_GL_TEXTURE0); aManager->gl()->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, mTexture); aManager->ActivateProgram(program); diff --git a/widget/windows/nsDataObj.cpp b/widget/windows/nsDataObj.cpp index f62da18eb059..873faa733bb1 100644 --- a/widget/windows/nsDataObj.cpp +++ b/widget/windows/nsDataObj.cpp @@ -74,14 +74,14 @@ nsresult nsDataObj::CStream::Init(nsIURI *pSourceURI, rv = NS_NewChannel(getter_AddRefs(mChannel), pSourceURI, aRequestingNode, - nsILoadInfo::SEC_NORMAL, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS, nsIContentPolicy::TYPE_OTHER, nullptr, // loadGroup nullptr, // aCallbacks nsIRequest::LOAD_FROM_CACHE); NS_ENSURE_SUCCESS(rv, rv); - rv = mChannel->AsyncOpen(this, nullptr); + rv = mChannel->AsyncOpen2(this); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } diff --git a/xpcom/ds/nsSupportsArray.cpp b/xpcom/ds/nsSupportsArray.cpp index 0d1696e2276f..f20dd614e9a8 100644 --- a/xpcom/ds/nsSupportsArray.cpp +++ b/xpcom/ds/nsSupportsArray.cpp @@ -189,6 +189,9 @@ nsSupportsArray::Read(nsIObjectInputStream* aStream) uint32_t newArraySize; rv = aStream->Read32(&newArraySize); + if (NS_FAILED(rv)) { + return rv; + } if (newArraySize <= kAutoArraySize) { if (mArray != mAutoArray) {