From 0d9b8ad89ac34be1643765e09cdf23fdc24b8c55 Mon Sep 17 00:00:00 2001 From: Randell Jesup Date: Tue, 7 Oct 2014 14:14:01 -0400 Subject: [PATCH 01/77] Bug 1033335: Don't send IDRs to change bitrates or periodic ones to fix encoder errors r=pkerr --- .../signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp | 6 ++++++ .../signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h | 3 +++ 2 files changed, 9 insertions(+) diff --git a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp b/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp index 884cf64fcd40..2451f4641a36 100644 --- a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp +++ b/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp @@ -788,7 +788,9 @@ WebrtcOMXH264VideoEncoder::WebrtcOMXH264VideoEncoder() , mHeight(0) , mFrameRate(0) , mBitRateKbps(0) +#ifdef OMX_IDR_NEEDED_FOR_BITRATE , mBitRateAtLastIDR(0) +#endif , mOMXConfigured(false) , mOMXReconfigure(false) { @@ -903,13 +905,16 @@ WebrtcOMXH264VideoEncoder::Encode(const webrtc::I420VideoFrame& aInputImage, return WEBRTC_VIDEO_CODEC_ERROR; } mOMXConfigured = true; +#ifdef OMX_IDR_NEEDED_FOR_BITRATE mLastIDRTime = TimeStamp::Now(); mBitRateAtLastIDR = mBitRateKbps; +#endif } if (aFrameTypes && aFrameTypes->size() && ((*aFrameTypes)[0] == webrtc::kKeyFrame)) { mOMX->RequestIDRFrame(); +#ifdef OMX_IDR_NEEDED_FOR_BITRATE mLastIDRTime = TimeStamp::Now(); mBitRateAtLastIDR = mBitRateKbps; } else if (mBitRateKbps != mBitRateAtLastIDR) { @@ -942,6 +947,7 @@ WebrtcOMXH264VideoEncoder::Encode(const webrtc::I420VideoFrame& aInputImage, mLastIDRTime = now; mBitRateAtLastIDR = mBitRateKbps; } +#endif } // Wrap I420VideoFrame input with PlanarYCbCrImage for OMXVideoEncoder. diff --git a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h b/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h index 6c5d08eb7449..a4faeab71c80 100644 --- a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h +++ b/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h @@ -26,6 +26,7 @@ class OMXOutputDrain; // XXX see if we can reduce this #define WEBRTC_OMX_H264_MIN_DECODE_BUFFERS 10 +#define OMX_IDR_NEEDED_FOR_BITRATE 0 class WebrtcOMXH264VideoEncoder : public WebrtcVideoEncoder { @@ -64,8 +65,10 @@ private: uint32_t mHeight; uint32_t mFrameRate; uint32_t mBitRateKbps; +#ifdef OMX_IDR_NEEDED_FOR_BITRATE uint32_t mBitRateAtLastIDR; TimeStamp mLastIDRTime; +#endif bool mOMXConfigured; bool mOMXReconfigure; webrtc::EncodedImage mEncodedImage; From bda824b9502b01f951a263082d623bbec9198140 Mon Sep 17 00:00:00 2001 From: Sotaro Ikeda Date: Tue, 7 Oct 2014 11:37:15 -0700 Subject: [PATCH 02/77] Bug 1076868 - Fix RemoveTextureFromCompositableAsync() call handling r=nical --- gfx/layers/client/ClientLayerManager.cpp | 2 +- gfx/layers/client/CompositableClient.cpp | 4 +++ gfx/layers/ipc/CompositableForwarder.h | 2 ++ gfx/layers/ipc/ImageBridgeChild.cpp | 4 +-- gfx/layers/ipc/ImageBridgeChild.h | 2 +- gfx/layers/ipc/LayerTransactionParent.cpp | 26 ++++++++++++++ gfx/layers/ipc/LayersMessages.ipdlh | 1 + gfx/layers/ipc/ShadowLayers.cpp | 43 +++++++++++++++++------ gfx/layers/ipc/ShadowLayers.h | 3 +- 9 files changed, 71 insertions(+), 16 deletions(-) diff --git a/gfx/layers/client/ClientLayerManager.cpp b/gfx/layers/client/ClientLayerManager.cpp index 9c7354892827..52979abdb97e 100644 --- a/gfx/layers/client/ClientLayerManager.cpp +++ b/gfx/layers/client/ClientLayerManager.cpp @@ -607,7 +607,7 @@ ClientLayerManager::ForwardTransaction(bool aScheduleComposite) } mForwarder->RemoveTexturesIfNecessary(); - mForwarder->SendPendingAsyncMessge(); + mForwarder->SendPendingAsyncMessges(); mPhase = PHASE_NONE; // this may result in Layers being deleted, which results in diff --git a/gfx/layers/client/CompositableClient.cpp b/gfx/layers/client/CompositableClient.cpp index 3de250250678..fca3280d280f 100644 --- a/gfx/layers/client/CompositableClient.cpp +++ b/gfx/layers/client/CompositableClient.cpp @@ -171,6 +171,10 @@ CompositableClient::Destroy() if (!mCompositableChild) { return; } + // Send pending AsyncMessages before deleting CompositableChild. + // They might have dependency to the mCompositableChild. + mForwarder->SendPendingAsyncMessges(); + // Delete CompositableChild. mCompositableChild->mCompositableClient = nullptr; PCompositableChild::Send__delete__(mCompositableChild); mCompositableChild = nullptr; diff --git a/gfx/layers/ipc/CompositableForwarder.h b/gfx/layers/ipc/CompositableForwarder.h index 040c7902fef4..6bac768cf63d 100644 --- a/gfx/layers/ipc/CompositableForwarder.h +++ b/gfx/layers/ipc/CompositableForwarder.h @@ -194,6 +194,8 @@ public: PTextureChild* aTexture, const FenceHandle& aFence) = 0; + virtual void SendPendingAsyncMessges() = 0; + void IdentifyTextureHost(const TextureFactoryIdentifier& aIdentifier); virtual int32_t GetMaxTextureSize() const MOZ_OVERRIDE diff --git a/gfx/layers/ipc/ImageBridgeChild.cpp b/gfx/layers/ipc/ImageBridgeChild.cpp index c8162382abfb..b58c5c0cf3b2 100644 --- a/gfx/layers/ipc/ImageBridgeChild.cpp +++ b/gfx/layers/ipc/ImageBridgeChild.cpp @@ -555,7 +555,7 @@ ImageBridgeChild::EndTransaction() NS_RUNTIMEABORT("not reached"); } } - SendPendingAsyncMessge(); + SendPendingAsyncMessges(); } @@ -964,7 +964,7 @@ bool ImageBridgeChild::IsSameProcess() const return OtherProcess() == ipc::kInvalidProcessHandle; } -void ImageBridgeChild::SendPendingAsyncMessge() +void ImageBridgeChild::SendPendingAsyncMessges() { if (!IsCreated() || mTransactionsToRespond.empty()) { diff --git a/gfx/layers/ipc/ImageBridgeChild.h b/gfx/layers/ipc/ImageBridgeChild.h index facf86d8321a..6e60c10879b7 100644 --- a/gfx/layers/ipc/ImageBridgeChild.h +++ b/gfx/layers/ipc/ImageBridgeChild.h @@ -310,7 +310,7 @@ public: virtual bool IsSameProcess() const MOZ_OVERRIDE; - void SendPendingAsyncMessge(); + virtual void SendPendingAsyncMessges(); void MarkShutDown(); protected: diff --git a/gfx/layers/ipc/LayerTransactionParent.cpp b/gfx/layers/ipc/LayerTransactionParent.cpp index 059752f6cd6d..86de8d9a8bdd 100644 --- a/gfx/layers/ipc/LayerTransactionParent.cpp +++ b/gfx/layers/ipc/LayerTransactionParent.cpp @@ -858,6 +858,8 @@ LayerTransactionParent::DeallocPTextureParent(PTextureParent* actor) bool LayerTransactionParent::RecvChildAsyncMessages(const InfallibleTArray& aMessages) { + AutoLayerTransactionParentAsyncMessageSender autoAsyncMessageSender(this); + for (AsyncChildMessageArray::index_type i = 0; i < aMessages.Length(); ++i) { const AsyncChildMessageData& message = aMessages[i]; @@ -888,6 +890,30 @@ LayerTransactionParent::RecvChildAsyncMessages(const InfallibleTArray tex = TextureHost::AsTextureHost(op.textureParent()); + + MOZ_ASSERT(tex.get()); + compositable->RemoveTextureHost(tex); + + // send FenceHandle if present via ImageBridge. + ImageBridgeParent::SendFenceHandleToTrackerIfPresent( + GetChildProcessId(), + op.holderId(), + op.transactionId(), + op.textureParent(), + compositable); + + // Send message back via PImageBridge. + ImageBridgeParent::ReplyRemoveTexture( + GetChildProcessId(), + OpReplyRemoveTexture(true, // isMain + op.holderId(), + op.transactionId())); + break; + } default: NS_ERROR("unknown AsyncChildMessageData type"); return false; diff --git a/gfx/layers/ipc/LayersMessages.ipdlh b/gfx/layers/ipc/LayersMessages.ipdlh index 521935573b07..837227919763 100644 --- a/gfx/layers/ipc/LayersMessages.ipdlh +++ b/gfx/layers/ipc/LayersMessages.ipdlh @@ -492,6 +492,7 @@ union AsyncParentMessageData { union AsyncChildMessageData { OpDeliverFenceFromChild; OpReplyDeliverFence; + OpRemoveTextureAsync; }; } // namespace diff --git a/gfx/layers/ipc/ShadowLayers.cpp b/gfx/layers/ipc/ShadowLayers.cpp index 15135f273857..116b8fc15672 100644 --- a/gfx/layers/ipc/ShadowLayers.cpp +++ b/gfx/layers/ipc/ShadowLayers.cpp @@ -135,6 +135,8 @@ public: } bool Finished() const { return !mOpen && Empty(); } + bool Opened() const { return mOpen; } + EditVector mCset; EditVector mPaints; ShadowableLayerSet mMutants; @@ -472,11 +474,19 @@ ShadowLayerForwarder::RemoveTextureFromCompositableAsync(AsyncTransactionTracker CompositableClient* aCompositable, TextureClient* aTexture) { - mTxn->AddEdit(OpRemoveTextureAsync(CompositableClient::GetTrackersHolderId(aCompositable->GetIPDLActor()), - aAsyncTransactionTracker->GetId(), - nullptr, aCompositable->GetIPDLActor(), - nullptr, aTexture->GetIPDLActor())); - // Hold AsyncTransactionTracker until receving reply + if (mTxn->Opened()) { + mTxn->AddEdit(OpRemoveTextureAsync(CompositableClient::GetTrackersHolderId(aCompositable->GetIPDLActor()), + aAsyncTransactionTracker->GetId(), + nullptr, aCompositable->GetIPDLActor(), + nullptr, aTexture->GetIPDLActor())); + } else { + // If the function is called outside of transaction, + // OpRemoveTextureAsync message is stored as pending message. + mPendingAsyncMessages.push_back(OpRemoveTextureAsync(CompositableClient::GetTrackersHolderId(aCompositable->GetIPDLActor()), + aAsyncTransactionTracker->GetId(), + nullptr, aCompositable->GetIPDLActor(), + nullptr, aTexture->GetIPDLActor())); + } CompositableClient::HoldUntilComplete(aCompositable->GetIPDLActor(), aAsyncTransactionTracker); } @@ -821,7 +831,7 @@ void ShadowLayerForwarder::StopReceiveAsyncParentMessge() !mShadowManager->IPCOpen()) { return; } - SendPendingAsyncMessge(); + SendPendingAsyncMessges(); mShadowManager->SetForwarder(nullptr); } @@ -831,7 +841,7 @@ void ShadowLayerForwarder::ClearCachedResources() !mShadowManager->IPCOpen()) { return; } - SendPendingAsyncMessge(); + SendPendingAsyncMessges(); mShadowManager->SendClearCachedResources(); } @@ -844,20 +854,31 @@ void ShadowLayerForwarder::Composite() mShadowManager->SendForceComposite(); } -void ShadowLayerForwarder::SendPendingAsyncMessge() +void ShadowLayerForwarder::SendPendingAsyncMessges() { if (!HasShadowManager() || - !mShadowManager->IPCOpen() || - mTransactionsToRespond.empty()) { + !mShadowManager->IPCOpen()) { + mTransactionsToRespond.clear(); + mPendingAsyncMessages.clear(); return; } - // Send OpReplyDeliverFence messages + + if (mTransactionsToRespond.empty() && mPendingAsyncMessages.empty()) { + return; + } + InfallibleTArray replies; replies.SetCapacity(mTransactionsToRespond.size()); + // Prepare OpReplyDeliverFence messages. for (size_t i = 0; i < mTransactionsToRespond.size(); i++) { replies.AppendElement(OpReplyDeliverFence(mTransactionsToRespond[i])); } mTransactionsToRespond.clear(); + // Prepare pending messages. + for (size_t i = 0; i < mPendingAsyncMessages.size(); i++) { + replies.AppendElement(mPendingAsyncMessages[i]); + } + mPendingAsyncMessages.clear(); mShadowManager->SendChildAsyncMessages(replies); } diff --git a/gfx/layers/ipc/ShadowLayers.h b/gfx/layers/ipc/ShadowLayers.h index c01c46f17373..694c7eb4c0b1 100644 --- a/gfx/layers/ipc/ShadowLayers.h +++ b/gfx/layers/ipc/ShadowLayers.h @@ -319,7 +319,7 @@ public: void Composite(); - void SendPendingAsyncMessge(); + virtual void SendPendingAsyncMessges(); /** * True if this is forwarding to a LayerManagerComposite. @@ -403,6 +403,7 @@ protected: private: Transaction* mTxn; + std::vector mPendingAsyncMessages; DiagnosticTypes mDiagnosticTypes; bool mIsFirstPaint; bool mWindowOverlayChanged; From 4ae95106e2786dea1de0f0e0193c8893f3b34e41 Mon Sep 17 00:00:00 2001 From: David Keeler Date: Mon, 6 Oct 2014 11:28:15 -0700 Subject: [PATCH 03/77] bug 1077891 - update getHSTSPreloadList.js to reflect changes to nsISiteSecurityService r=mmc DONTBUILD NPOTB --- security/manager/tools/getHSTSPreloadList.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/security/manager/tools/getHSTSPreloadList.js b/security/manager/tools/getHSTSPreloadList.js index c17624202a84..7da24a47ca2a 100644 --- a/security/manager/tools/getHSTSPreloadList.js +++ b/security/manager/tools/getHSTSPreloadList.js @@ -114,15 +114,18 @@ function getHosts(rawdata) { var gSSService = Cc["@mozilla.org/ssservice;1"] .getService(Ci.nsISiteSecurityService); -function processStsHeader(host, header, status) { +function processStsHeader(host, header, status, securityInfo) { var maxAge = { value: 0 }; var includeSubdomains = { value: false }; var error = ERROR_NONE; - if (header != null) { + if (header != null && securityInfo != null) { try { var uri = Services.io.newURI("https://" + host.name, null, null); + var sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider) + .SSLStatus; gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, - uri, header, 0, maxAge, includeSubdomains); + uri, header, sslStatus, 0, maxAge, + includeSubdomains); } catch (e) { dump("ERROR: could not process header '" + header + "' from " + @@ -180,7 +183,8 @@ function getHSTSStatus(host, resultList) { if (!inResultList && req.readyState == 4) { inResultList = true; var header = req.getResponseHeader("strict-transport-security"); - resultList.push(processStsHeader(host, header, req.status)); + resultList.push(processStsHeader(host, header, req.status, + req.channel.securityInfo)); } }; From 8045050eddeb4580f57724f5de9c46dbae98513c Mon Sep 17 00:00:00 2001 From: Bill McCloskey Date: Tue, 7 Oct 2014 11:46:24 -0700 Subject: [PATCH 04/77] Bug 1072467 - Add tests for e10s add-on shims (r=mconley,ted) --- toolkit/components/addoncompat/moz.build | 2 + .../components/addoncompat/tests/Makefile.in | 15 + .../addoncompat/tests/addon/bootstrap.js | 266 ++++++++++++++++++ .../addoncompat/tests/addon/chrome.manifest | 1 + .../addoncompat/tests/addon/install.rdf | 29 ++ .../addoncompat/tests/browser/browser.ini | 9 + .../tests/browser/browser_addonShims.js | 54 ++++ .../browser/browser_addonShims_testpage.html | 17 ++ .../browser/browser_addonShims_testpage2.html | 16 ++ .../components/addoncompat/tests/moz.build | 7 + 10 files changed, 416 insertions(+) create mode 100644 toolkit/components/addoncompat/tests/Makefile.in create mode 100644 toolkit/components/addoncompat/tests/addon/bootstrap.js create mode 100644 toolkit/components/addoncompat/tests/addon/chrome.manifest create mode 100644 toolkit/components/addoncompat/tests/addon/install.rdf create mode 100644 toolkit/components/addoncompat/tests/browser/browser.ini create mode 100644 toolkit/components/addoncompat/tests/browser/browser_addonShims.js create mode 100644 toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage.html create mode 100644 toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage2.html create mode 100644 toolkit/components/addoncompat/tests/moz.build diff --git a/toolkit/components/addoncompat/moz.build b/toolkit/components/addoncompat/moz.build index 8ffd59af115a..f915e7370e4c 100644 --- a/toolkit/components/addoncompat/moz.build +++ b/toolkit/components/addoncompat/moz.build @@ -4,6 +4,8 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +TEST_DIRS += ['tests'] + EXTRA_COMPONENTS += [ 'addoncompat.manifest', 'multiprocessShims.js', diff --git a/toolkit/components/addoncompat/tests/Makefile.in b/toolkit/components/addoncompat/tests/Makefile.in new file mode 100644 index 000000000000..86805290a3c4 --- /dev/null +++ b/toolkit/components/addoncompat/tests/Makefile.in @@ -0,0 +1,15 @@ +# 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 $(topsrcdir)/config/rules.mk + +# This is so hacky. Waiting on bug 988938. +addondir = $(srcdir)/addon +TESTROOT = $(CURDIR)/$(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) +testdir = $(TESTROOT)/browser + +libs:: + $(EXIT_ON_ERROR) \ + $(NSINSTALL) -D $(testdir); \ + (cd $(addondir) && zip -qr $(testdir)/addon.xpi *) diff --git a/toolkit/components/addoncompat/tests/addon/bootstrap.js b/toolkit/components/addoncompat/tests/addon/bootstrap.js new file mode 100644 index 000000000000..9211d69175bd --- /dev/null +++ b/toolkit/components/addoncompat/tests/addon/bootstrap.js @@ -0,0 +1,266 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); + +const baseURL = "http://mochi.test:8888/browser/" + + "toolkit/components/addoncompat/tests/browser/"; + +function forEachWindow(f) +{ + let wins = Services.ww.getWindowEnumerator("navigator:browser"); + while (wins.hasMoreElements()) { + let win = wins.getNext(); + if (win.gBrowser) { + f(win); + } + } +} + +function addLoadListener(target, listener) +{ + target.addEventListener("load", function handler(event) { + target.removeEventListener("load", handler, true); + return listener(event); + }, true); +} + +let gWin; +let gBrowser; +let ok, is, info; + +// Make sure that the shims for window.content, browser.contentWindow, +// and browser.contentDocument are working. +function testContentWindow() +{ + return new Promise(function(resolve, reject) { + const url = baseURL + "browser_addonShims_testpage.html"; + let tab = gBrowser.addTab(url); + gBrowser.selectedTab = tab; + let browser = tab.linkedBrowser; + addLoadListener(browser, function handler() { + ok(gWin.content, "content is defined on chrome window"); + ok(browser.contentWindow, "contentWindow is defined"); + ok(browser.contentDocument, "contentWindow is defined"); + is(gWin.content, browser.contentWindow, "content === contentWindow"); + + ok(browser.contentDocument.getElementById("link"), "link present in document"); + + // FIXME: Waiting on bug 1073631. + //is(browser.contentWindow.wrappedJSObject.global, 3, "global available on document"); + + gBrowser.removeTab(tab); + resolve(); + }); + }); +} + +// Test for bug 1060046 and bug 1072607. We want to make sure that +// adding and removing listeners works as expected. +function testListeners() +{ + return new Promise(function(resolve, reject) { + const url1 = baseURL + "browser_addonShims_testpage.html"; + const url2 = baseURL + "browser_addonShims_testpage2.html"; + + let tab = gBrowser.addTab(url2); + let browser = tab.linkedBrowser; + addLoadListener(browser, function handler() { + function dummyHandler() {} + + // Test that a removed listener stays removed (bug + // 1072607). We're looking to make sure that adding and removing + // a listener here doesn't cause later listeners to fire more + // than once. + for (let i = 0; i < 5; i++) { + gBrowser.addEventListener("load", dummyHandler, true); + gBrowser.removeEventListener("load", dummyHandler, true); + } + + // We also want to make sure that this listener doesn't fire + // after it's removed. + let loadWithRemoveCount = 0; + addLoadListener(browser, function handler1() { + loadWithRemoveCount++; + is(event.target.documentURI, url1, "only fire for first url"); + }); + + // Load url1 and then url2. We want to check that: + // 1. handler1 only fires for url1. + // 2. handler2 only fires once for url1 (so the second time it + // fires should be for url2). + let loadCount = 0; + browser.addEventListener("load", function handler2(event) { + loadCount++; + if (loadCount == 1) { + is(event.target.documentURI, url1, "first load is for first page loaded"); + browser.loadURI(url2); + } else { + gBrowser.removeEventListener("load", handler2, true); + + is(event.target.documentURI, url2, "second load is for second page loaded"); + is(loadWithRemoveCount, 1, "load handler is only called once"); + + gBrowser.removeTab(tab); + resolve(); + } + }, true); + + browser.loadURI(url1); + }); + }); +} + +// Test for bug 1059207. We want to make sure that adding a capturing +// listener and a non-capturing listener to the same element works as +// expected. +function testCapturing() +{ + return new Promise(function(resolve, reject) { + let capturingCount = 0; + let nonCapturingCount = 0; + + function capturingHandler(event) { + is(capturingCount, 0, "capturing handler called once"); + is(nonCapturingCount, 0, "capturing handler called before bubbling handler"); + capturingCount++; + } + + function nonCapturingHandler(event) { + is(capturingCount, 1, "bubbling handler called after capturing handler"); + is(nonCapturingCount, 0, "bubbling handler called once"); + nonCapturingCount++; + } + + gBrowser.addEventListener("mousedown", capturingHandler, true); + gBrowser.addEventListener("mousedown", nonCapturingHandler, false); + + const url = baseURL + "browser_addonShims_testpage.html"; + let tab = gBrowser.addTab(url); + let browser = tab.linkedBrowser; + addLoadListener(browser, function handler() { + let win = browser.contentWindow; + let event = win.document.createEvent("MouseEvents"); + event.initMouseEvent("mousedown", true, false, win, 1, + 1, 0, 0, 0, // screenX, screenY, clientX, clientY + false, false, false, false, // ctrlKey, altKey, shiftKey, metaKey + 0, null); // buttonCode, relatedTarget + + let element = win.document.getElementById("output"); + element.dispatchEvent(event); + + is(capturingCount, 1, "capturing handler fired"); + is(nonCapturingCount, 1, "bubbling handler fired"); + + gBrowser.removeEventListener("mousedown", capturingHandler, true); + gBrowser.removeEventListener("mousedown", nonCapturingHandler, false); + + gBrowser.removeTab(tab); + resolve(); + }); + }); +} + +// Make sure we get observer notifications that normally fire in the +// child. +function testObserver() +{ + return new Promise(function(resolve, reject) { + let observerFired = 0; + + function observer(subject, topic, data) { + Services.obs.removeObserver(observer, "document-element-inserted"); + observerFired++; + } + Services.obs.addObserver(observer, "document-element-inserted", false); + + let count = 0; + const url = baseURL + "browser_addonShims_testpage.html"; + let tab = gBrowser.addTab(url); + let browser = tab.linkedBrowser; + browser.addEventListener("load", function handler() { + count++; + if (count == 1) { + browser.reload(); + } else { + browser.removeEventListener("load", handler); + + is(observerFired, 1, "got observer notification"); + + gBrowser.removeTab(tab); + resolve(); + } + }, true); + }); +} + +// Test for bug 1072472. Make sure that creating a sandbox to run code +// in the content window works. This is essentially a test for +// Greasemonkey. +function testSandbox() +{ + return new Promise(function(resolve, reject) { + const url = baseURL + "browser_addonShims_testpage.html"; + let tab = gBrowser.addTab(url); + let browser = tab.linkedBrowser; + browser.addEventListener("load", function handler() { + browser.removeEventListener("load", handler); + + let sandbox = Cu.Sandbox(browser.contentWindow, + {sandboxPrototype: browser.contentWindow, + wantXrays: false}); + Cu.evalInSandbox("const unsafeWindow = window;", sandbox); + Cu.evalInSandbox("document.getElementById('output').innerHTML = 'hello';", sandbox); + + is(browser.contentDocument.getElementById("output").innerHTML, "hello", + "sandbox code ran successfully"); + + gBrowser.removeTab(tab); + resolve(); + }, true); + }); +} + +function runTests(win, funcs) +{ + ok = funcs.ok; + is = funcs.is; + info = funcs.info; + + gWin = win; + gBrowser = win.gBrowser; + + return testContentWindow(). + then(testListeners). + then(testCapturing). + then(testObserver). + then(testSandbox); +} + +/* + bootstrap.js API +*/ + +function startup(aData, aReason) +{ + forEachWindow(win => { + win.runAddonShimTests = (funcs) => runTests(win, funcs); + }); +} + +function shutdown(aData, aReason) +{ + forEachWindow(win => { + delete win.runAddonShimTests; + }); +} + +function install(aData, aReason) +{ +} + +function uninstall(aData, aReason) +{ +} + diff --git a/toolkit/components/addoncompat/tests/addon/chrome.manifest b/toolkit/components/addoncompat/tests/addon/chrome.manifest new file mode 100644 index 000000000000..443666893e47 --- /dev/null +++ b/toolkit/components/addoncompat/tests/addon/chrome.manifest @@ -0,0 +1 @@ +content addonshim1 content/ \ No newline at end of file diff --git a/toolkit/components/addoncompat/tests/addon/install.rdf b/toolkit/components/addoncompat/tests/addon/install.rdf new file mode 100644 index 000000000000..d16868ef89a6 --- /dev/null +++ b/toolkit/components/addoncompat/tests/addon/install.rdf @@ -0,0 +1,29 @@ + + + + + + test-addon-shim-1@tests.mozilla.org + 1 + 2 + true + + + Test addon shim 1 + Test an add-on that needs multiprocess shims. + false + + chrome://foo/skin/icon.png + chrome://foo/content/about.xul + chrome://foo/content/options.xul + + + + toolkit@mozilla.org + 10.0 + * + + + + diff --git a/toolkit/components/addoncompat/tests/browser/browser.ini b/toolkit/components/addoncompat/tests/browser/browser.ini new file mode 100644 index 000000000000..1c1b026c61de --- /dev/null +++ b/toolkit/components/addoncompat/tests/browser/browser.ini @@ -0,0 +1,9 @@ +[DEFAULT] +support-files = + addon.xpi + browser_addonShims_testpage.html + browser_addonShims_testpage2.html +generated-files = + addon.xpi + +[browser_addonShims.js] diff --git a/toolkit/components/addoncompat/tests/browser/browser_addonShims.js b/toolkit/components/addoncompat/tests/browser/browser_addonShims.js new file mode 100644 index 000000000000..c7e02d4da4c0 --- /dev/null +++ b/toolkit/components/addoncompat/tests/browser/browser_addonShims.js @@ -0,0 +1,54 @@ +let {AddonManager} = Cu.import("resource://gre/modules/AddonManager.jsm", {}); + +const ADDON_URL = "http://example.com/browser/toolkit/components/addoncompat/tests/browser/addon.xpi"; + +// Install a test add-on that will exercise e10s shims. +// url: Location of the add-on. +function addAddon(url) +{ + info("Installing add-on: " + url); + + return new Promise(function(resolve, reject) { + AddonManager.getInstallForURL(url, installer => { + installer.install(); + let listener = { + onInstallEnded: function(addon, addonInstall) { + installer.removeListener(listener); + + // Wait for add-on's startup scripts to execute. See bug 997408 + executeSoon(function() { + resolve(addonInstall); + }); + } + }; + installer.addListener(listener); + }, "application/x-xpinstall"); + }); +} + +// Uninstall a test add-on. +// addon: The addon reference returned from addAddon. +function removeAddon(addon) +{ + info("Removing addon."); + + return new Promise(function(resolve, reject) { + let listener = { + onUninstalled: function(uninstalledAddon) { + if (uninstalledAddon != addon) { + return; + } + AddonManager.removeAddonListener(listener); + resolve(); + } + }; + AddonManager.addAddonListener(listener); + addon.uninstall(); + }); +} + +add_task(function* test_addon_shims() { + let addon = yield addAddon(ADDON_URL); + yield window.runAddonShimTests({ok: ok, is: is, info: info}); + yield removeAddon(addon); +}); diff --git a/toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage.html b/toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage.html new file mode 100644 index 000000000000..5a8b34e8880c --- /dev/null +++ b/toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage.html @@ -0,0 +1,17 @@ + + + + shim test + + + +Hello! + +Link +
+ + + + diff --git a/toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage2.html b/toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage2.html new file mode 100644 index 000000000000..f644b1129c9d --- /dev/null +++ b/toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage2.html @@ -0,0 +1,16 @@ + + + + shim test + + + +Hello! + +Link + + + + diff --git a/toolkit/components/addoncompat/tests/moz.build b/toolkit/components/addoncompat/tests/moz.build new file mode 100644 index 000000000000..20f8e46466a0 --- /dev/null +++ b/toolkit/components/addoncompat/tests/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +BROWSER_CHROME_MANIFESTS += ['browser/browser.ini'] From 008e00e231eec9f517893f24064542185b426592 Mon Sep 17 00:00:00 2001 From: Bill McCloskey Date: Tue, 7 Oct 2014 11:46:24 -0700 Subject: [PATCH 05/77] Bug 930243 - Add a processType flag to chrome manifest directives (r=froydnj) --- python/mozbuild/mozpack/chrome/flags.py | 1 + python/mozbuild/mozpack/chrome/manifest.py | 1 + xpcom/components/ManifestParser.cpp | 16 +++++++++++- .../data/child_process_directive_service.js | 21 ++++++++++++++++ .../data/main_process_directive_service.js | 21 ++++++++++++++++ .../unit/data/process_directive.manifest | 5 ++++ xpcom/tests/unit/test_process_directives.js | 25 +++++++++++++++++++ .../unit/test_process_directives_child.js | 3 +++ xpcom/tests/unit/xpcshell.ini | 2 ++ 9 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 xpcom/tests/unit/data/child_process_directive_service.js create mode 100644 xpcom/tests/unit/data/main_process_directive_service.js create mode 100644 xpcom/tests/unit/data/process_directive.manifest create mode 100644 xpcom/tests/unit/test_process_directives.js create mode 100644 xpcom/tests/unit/test_process_directives_child.js diff --git a/python/mozbuild/mozpack/chrome/flags.py b/python/mozbuild/mozpack/chrome/flags.py index 1d0131986766..c54018f11b64 100644 --- a/python/mozbuild/mozpack/chrome/flags.py +++ b/python/mozbuild/mozpack/chrome/flags.py @@ -213,6 +213,7 @@ class Flags(OrderedDict): 'platform': Flag, 'xpcnativewrappers': Flag, 'tablet': Flag, + 'process': StringFlag, } RE = re.compile(r'([!<>=]+)') diff --git a/python/mozbuild/mozpack/chrome/manifest.py b/python/mozbuild/mozpack/chrome/manifest.py index 39ab0029b699..e4dd6773de3f 100644 --- a/python/mozbuild/mozpack/chrome/manifest.py +++ b/python/mozbuild/mozpack/chrome/manifest.py @@ -35,6 +35,7 @@ class ManifestEntry(object): 'abi', 'xpcnativewrappers', 'tablet', + 'process', ] def __init__(self, base, *flags): diff --git a/xpcom/components/ManifestParser.cpp b/xpcom/components/ManifestParser.cpp index 11637069d49b..04d53a5f1ce7 100644 --- a/xpcom/components/ManifestParser.cpp +++ b/xpcom/components/ManifestParser.cpp @@ -493,10 +493,14 @@ ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, NS_NAMED_LITERAL_STRING(kOs, "os"); NS_NAMED_LITERAL_STRING(kOsVersion, "osversion"); NS_NAMED_LITERAL_STRING(kABI, "abi"); + NS_NAMED_LITERAL_STRING(kProcess, "process"); #if defined(MOZ_WIDGET_ANDROID) NS_NAMED_LITERAL_STRING(kTablet, "tablet"); #endif + NS_NAMED_LITERAL_STRING(kMain, "main"); + NS_NAMED_LITERAL_STRING(kContent, "content"); + // Obsolete NS_NAMED_LITERAL_STRING(kXPCNativeWrappers, "xpcnativewrappers"); @@ -505,6 +509,7 @@ ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, nsAutoString geckoVersion; nsAutoString osTarget; nsAutoString abi; + nsAutoString process; nsCOMPtr xapp; if (!aXPTOnly) { @@ -578,6 +583,12 @@ ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, } #endif + if (XRE_GetProcessType() == GeckoProcessType_Content) { + process = kContent; + } else { + process = kMain; + } + // Because contracts must be registered after CIDs, we save and process them // at the end. nsTArray contracts; @@ -669,6 +680,7 @@ ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, TriState stOsVersion = eUnspecified; TriState stOs = eUnspecified; TriState stABI = eUnspecified; + TriState stProcess = eUnspecified; #if defined(MOZ_WIDGET_ANDROID) TriState stTablet = eUnspecified; #endif @@ -683,6 +695,7 @@ ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, if (CheckStringFlag(kApplication, wtoken, appID, stApp) || CheckStringFlag(kOs, wtoken, osTarget, stOs) || CheckStringFlag(kABI, wtoken, abi, stABI) || + CheckStringFlag(kProcess, wtoken, process, stProcess) || CheckVersionFlag(kOsVersion, wtoken, osVersion, stOsVersion) || CheckVersionFlag(kAppVersion, wtoken, appVersion, stAppVersion) || CheckVersionFlag(kGeckoVersion, wtoken, geckoVersion, stGeckoVersion)) { @@ -726,7 +739,8 @@ ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, #ifdef MOZ_WIDGET_ANDROID stTablet == eBad || #endif - stABI == eBad) { + stABI == eBad || + stProcess == eBad) { continue; } diff --git a/xpcom/tests/unit/data/child_process_directive_service.js b/xpcom/tests/unit/data/child_process_directive_service.js new file mode 100644 index 000000000000..9bc1c3206fb4 --- /dev/null +++ b/xpcom/tests/unit/data/child_process_directive_service.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +Components.utils.import("resource:///modules/XPCOMUtils.jsm"); + +function TestProcessDirective() {} +TestProcessDirective.prototype = { + + /* Boilerplate */ + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupportsString]), + contractID: "@mozilla.org/xpcom/tests/ChildProcessDirectiveTest;1", + classID: Components.ID("{4bd1ba60-45c4-11e4-916c-0800200c9a66}"), + + type: Components.interfaces.nsISupportsString.TYPE_STRING, + data: "child process", + toString: function() { + return this.data; + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestProcessDirective]); diff --git a/xpcom/tests/unit/data/main_process_directive_service.js b/xpcom/tests/unit/data/main_process_directive_service.js new file mode 100644 index 000000000000..f4eed11c9ca5 --- /dev/null +++ b/xpcom/tests/unit/data/main_process_directive_service.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +Components.utils.import("resource:///modules/XPCOMUtils.jsm"); + +function TestProcessDirective() {} +TestProcessDirective.prototype = { + + /* Boilerplate */ + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupportsString]), + contractID: "@mozilla.org/xpcom/tests/MainProcessDirectiveTest;1", + classID: Components.ID("{9b6f4160-45be-11e4-916c-0800200c9a66}"), + + type: Components.interfaces.nsISupportsString.TYPE_STRING, + data: "main process", + toString: function() { + return this.data; + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestProcessDirective]); diff --git a/xpcom/tests/unit/data/process_directive.manifest b/xpcom/tests/unit/data/process_directive.manifest new file mode 100644 index 000000000000..9cbf7f241a63 --- /dev/null +++ b/xpcom/tests/unit/data/process_directive.manifest @@ -0,0 +1,5 @@ +component {9b6f4160-45be-11e4-916c-0800200c9a66} main_process_directive_service.js process=main +contract @mozilla.org/xpcom/tests/MainProcessDirectiveTest;1 {9b6f4160-45be-11e4-916c-0800200c9a66} process=main + +component {4bd1ba60-45c4-11e4-916c-0800200c9a66} child_process_directive_service.js process=content +contract @mozilla.org/xpcom/tests/ChildProcessDirectiveTest;1 {4bd1ba60-45c4-11e4-916c-0800200c9a66} process=content diff --git a/xpcom/tests/unit/test_process_directives.js b/xpcom/tests/unit/test_process_directives.js new file mode 100644 index 000000000000..9d1113d3fa18 --- /dev/null +++ b/xpcom/tests/unit/test_process_directives.js @@ -0,0 +1,25 @@ +const Ci = Components.interfaces; +const Cc = Components.classes; + +Components.utils.import("resource:///modules/Services.jsm"); + +function run_test() +{ + Components.manager.autoRegister(do_get_file("data/process_directive.manifest")); + + let isChild = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT; + + if (isChild) { + do_check_false("@mozilla.org/xpcom/tests/MainProcessDirectiveTest;1" in Cc); + } else { + let svc = Cc["@mozilla.org/xpcom/tests/MainProcessDirectiveTest;1"].createInstance(Ci.nsISupportsString); + do_check_eq(svc.data, "main process"); + } + + if (!isChild) { + do_check_false("@mozilla.org/xpcom/tests/ChildProcessDirectiveTest;1" in Cc); + } else { + let svc = Cc["@mozilla.org/xpcom/tests/ChildProcessDirectiveTest;1"].createInstance(Ci.nsISupportsString); + do_check_eq(svc.data, "child process"); + } +} diff --git a/xpcom/tests/unit/test_process_directives_child.js b/xpcom/tests/unit/test_process_directives_child.js new file mode 100644 index 000000000000..dca63b3563b9 --- /dev/null +++ b/xpcom/tests/unit/test_process_directives_child.js @@ -0,0 +1,3 @@ +function run_test() { + run_test_in_child("test_process_directives.js"); +} diff --git a/xpcom/tests/unit/xpcshell.ini b/xpcom/tests/unit/xpcshell.ini index 18b2963f5f8e..ff7a9786dd27 100644 --- a/xpcom/tests/unit/xpcshell.ini +++ b/xpcom/tests/unit/xpcshell.ini @@ -49,6 +49,8 @@ fail-if = os == "android" # bug 704368: test causes harness to be killed on debug Linux64 skip-if = os == "win" || (os == "linux" && debug && bits == 64) [test_pipe.js] +[test_process_directives.js] +[test_process_directives_child.js] [test_storagestream.js] [test_streams.js] [test_seek_multiplex.js] From 42e95291a121133cacaa84e2f4e351a71f5e1cc7 Mon Sep 17 00:00:00 2001 From: Bill McCloskey Date: Tue, 7 Oct 2014 11:46:25 -0700 Subject: [PATCH 06/77] Bug 1067576 - Make console.log work in frame scripts (r=Mossop) --- browser/installer/package-manifest.in | 3 + toolkit/components/moz.build | 1 + .../ContentProcessSingleton.js | 64 +++++++++++++++++++ .../processsingleton/MainProcessSingleton.js | 53 +++++++++++++++ .../ProcessSingleton.manifest | 7 ++ toolkit/components/processsingleton/moz.build | 11 ++++ toolkit/content/browser-content.js | 6 +- toolkit/content/widgets/browser.xml | 1 - 8 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 toolkit/components/processsingleton/ContentProcessSingleton.js create mode 100644 toolkit/components/processsingleton/MainProcessSingleton.js create mode 100644 toolkit/components/processsingleton/ProcessSingleton.manifest create mode 100644 toolkit/components/processsingleton/moz.build diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 5cefadc1c54c..79948493b5c5 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -444,6 +444,9 @@ @BINPATH@/components/addoncompat.manifest @BINPATH@/components/multiprocessShims.js @BINPATH@/components/pluginGlue.manifest +@BINPATH@/components/ProcessSingleton.manifest +@BINPATH@/components/MainProcessSingleton.js +@BINPATH@/components/ContentProcessSingleton.js @BINPATH@/browser/components/nsSessionStore.manifest @BINPATH@/browser/components/nsSessionStartup.js @BINPATH@/browser/components/nsSessionStore.js diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build index 926adc5e69c3..f7e608b22a38 100644 --- a/toolkit/components/moz.build +++ b/toolkit/components/moz.build @@ -36,6 +36,7 @@ DIRS += [ 'passwordmgr', 'perf', 'places', + 'processsingleton', 'promiseworker', 'prompts', 'protobuf', diff --git a/toolkit/components/processsingleton/ContentProcessSingleton.js b/toolkit/components/processsingleton/ContentProcessSingleton.js new file mode 100644 index 000000000000..f9697e7dd09c --- /dev/null +++ b/toolkit/components/processsingleton/ContentProcessSingleton.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Cu = Components.utils; +const Ci = Components.interfaces; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "cpmm", + "@mozilla.org/childprocessmessagemanager;1", + "nsIMessageSender"); + +function ContentProcessSingleton() {} +ContentProcessSingleton.prototype = { + classID: Components.ID("{ca2a8470-45c7-11e4-916c-0800200c9a66}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsISupportsWeakReference]), + + observe: function(subject, topic, data) { + switch (topic) { + case "app-startup": { + Services.obs.addObserver(this, "console-api-log-event", false); + Services.obs.addObserver(this, "xpcom-shutdown", false); + break; + } + case "console-api-log-event": { + let consoleMsg = subject.wrappedJSObject; + + let msgData = { + level: consoleMsg.level, + filename: consoleMsg.filename, + lineNumber: consoleMsg.lineNumber, + functionName: consoleMsg.functionName, + timeStamp: consoleMsg.timeStamp, + arguments: [], + }; + + // We can't send objects over the message manager, so we sanitize + // them out. + for (let arg of consoleMsg.arguments) { + if ((typeof arg == "object" || typeof arg == "function") && arg !== null) { + msgData.arguments.push(""); + } else { + msgData.arguments.push(arg); + } + } + + cpmm.sendAsyncMessage("Console:Log", msgData); + break; + } + + case "xpcom-shutdown": + Services.obs.removeObserver(this, "console-api-log-event"); + Services.obs.removeObserver(this, "xpcom-shutdown"); + break; + } + }, +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentProcessSingleton]); diff --git a/toolkit/components/processsingleton/MainProcessSingleton.js b/toolkit/components/processsingleton/MainProcessSingleton.js new file mode 100644 index 000000000000..689edd559724 --- /dev/null +++ b/toolkit/components/processsingleton/MainProcessSingleton.js @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Cu = Components.utils; +const Ci = Components.interfaces; +const Cc = Components.classes; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "ppmm", + "@mozilla.org/parentprocessmessagemanager;1", + "nsIMessageListenerManager"); + +XPCOMUtils.defineLazyServiceGetter(this, "globalmm", + "@mozilla.org/globalmessagemanager;1", + "nsIMessageBroadcaster"); + +function MainProcessSingleton() {} +MainProcessSingleton.prototype = { + classID: Components.ID("{0636a680-45cb-11e4-916c-0800200c9a66}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsISupportsWeakReference]), + + receiveMessage: function(message) { + let logMsg = message.data; + logMsg.wrappedJSObject = logMsg; + Services.obs.notifyObservers(logMsg, "console-api-log-event", null); + }, + + observe: function(subject, topic, data) { + switch (topic) { + case "app-startup": { + Services.obs.addObserver(this, "xpcom-shutdown", false); + + // Load this script early so that console.* is initialized + // before other frame scripts. + globalmm.loadFrameScript("chrome://global/content/browser-content.js", true); + ppmm.addMessageListener("Console:Log", this); + break; + } + + case "xpcom-shutdown": + ppmm.removeMessageListener("Console:Log", this); + break; + } + }, +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MainProcessSingleton]); diff --git a/toolkit/components/processsingleton/ProcessSingleton.manifest b/toolkit/components/processsingleton/ProcessSingleton.manifest new file mode 100644 index 000000000000..7a882ed7b6fb --- /dev/null +++ b/toolkit/components/processsingleton/ProcessSingleton.manifest @@ -0,0 +1,7 @@ +component {0636a680-45cb-11e4-916c-0800200c9a66} MainProcessSingleton.js process=main +contract @mozilla.org/main-process-singleton;1 {0636a680-45cb-11e4-916c-0800200c9a66} process=main +category app-startup MainProcessSingleton service,@mozilla.org/main-process-singleton;1 process=main + +component {ca2a8470-45c7-11e4-916c-0800200c9a66} ContentProcessSingleton.js process=content +contract @mozilla.org/content-process-singleton;1 {ca2a8470-45c7-11e4-916c-0800200c9a66} process=content +category app-startup ContentProcessSingleton service,@mozilla.org/content-process-singleton;1 process=content diff --git a/toolkit/components/processsingleton/moz.build b/toolkit/components/processsingleton/moz.build new file mode 100644 index 000000000000..6ffaac0f8729 --- /dev/null +++ b/toolkit/components/processsingleton/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXTRA_COMPONENTS += [ + 'ContentProcessSingleton.js', + 'MainProcessSingleton.js', + 'ProcessSingleton.manifest', +] diff --git a/toolkit/content/browser-content.js b/toolkit/content/browser-content.js index 1244f468651e..7a3399738a23 100644 --- a/toolkit/content/browser-content.js +++ b/toolkit/content/browser-content.js @@ -348,4 +348,8 @@ let PopupBlocking = { {blockedPopups: this.popupData, freshPopup: freshPopup}); }, }; -PopupBlocking.init(); \ No newline at end of file +PopupBlocking.init(); + +// Set up console.* for frame scripts. +let Console = Components.utils.import("resource://gre/modules/devtools/Console.jsm", {}); +this.console = new Console.ConsoleAPI(); diff --git a/toolkit/content/widgets/browser.xml b/toolkit/content/widgets/browser.xml index d2488738032c..5488adc45c9b 100644 --- a/toolkit/content/widgets/browser.xml +++ b/toolkit/content/widgets/browser.xml @@ -790,7 +790,6 @@ this.messageManager.addMessageListener("PopupBlocking:UpdateBlockedPopups", this); this.messageManager.addMessageListener("Autoscroll:Start", this); this.messageManager.addMessageListener("Autoscroll:Cancel", this); - this.messageManager.loadFrameScript("chrome://global/content/browser-content.js", true); } ]]> From 728e34063fdd197b723c23113030835be0bc41dc Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Tue, 7 Oct 2014 14:07:49 -0500 Subject: [PATCH 07/77] Bug 965880 - OdinMonkey: allow typed array constructors to be imported and used (r=bbouvier) --- js/src/asmjs/AsmJSLink.cpp | 11 +- js/src/asmjs/AsmJSModule.h | 23 ++-- js/src/asmjs/AsmJSValidate.cpp | 118 ++++++++++++++------- js/src/jit-test/tests/asm.js/testResize.js | 32 ++++++ 4 files changed, 136 insertions(+), 48 deletions(-) create mode 100644 js/src/jit-test/tests/asm.js/testResize.js diff --git a/js/src/asmjs/AsmJSLink.cpp b/js/src/asmjs/AsmJSLink.cpp index f99f8703e8e2..d0b6516a5806 100644 --- a/js/src/asmjs/AsmJSLink.cpp +++ b/js/src/asmjs/AsmJSLink.cpp @@ -219,10 +219,12 @@ ValidateFFI(JSContext *cx, AsmJSModule::Global &global, HandleValue importVal, } static bool -ValidateArrayView(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal, - HandleValue bufferVal) +ValidateArrayView(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal) { - RootedPropertyName field(cx, global.viewName()); + RootedPropertyName field(cx, global.maybeViewName()); + if (!field) + return true; + RootedValue v(cx); if (!GetDataProperty(cx, globalVal, field, &v)) return false; @@ -508,7 +510,8 @@ DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) return false; break; case AsmJSModule::Global::ArrayView: - if (!ValidateArrayView(cx, global, globalVal, bufferVal)) + case AsmJSModule::Global::ArrayViewCtor: + if (!ValidateArrayView(cx, global, globalVal)) return false; break; case AsmJSModule::Global::MathBuiltinFunction: diff --git a/js/src/asmjs/AsmJSModule.h b/js/src/asmjs/AsmJSModule.h index 4ea4c91c4664..bf7ba366c75d 100644 --- a/js/src/asmjs/AsmJSModule.h +++ b/js/src/asmjs/AsmJSModule.h @@ -200,7 +200,7 @@ class AsmJSModule class Global { public: - enum Which { Variable, FFI, ArrayView, MathBuiltinFunction, Constant, + enum Which { Variable, FFI, ArrayView, ArrayViewCtor, MathBuiltinFunction, Constant, SimdCtor, SimdOperation}; enum VarInitKind { InitConstant, InitImport }; enum ConstantKind { GlobalConstant, MathConstant }; @@ -284,12 +284,16 @@ class AsmJSModule MOZ_ASSERT(pod.which_ == FFI); return pod.u.ffiIndex_; } - PropertyName *viewName() const { - MOZ_ASSERT(pod.which_ == ArrayView); + // When a view is created from an imported constructor: + // var I32 = stdlib.Int32Array; + // var i32 = new I32(buffer); + // the second import has nothing to validate and thus has a null field. + PropertyName *maybeViewName() const { + MOZ_ASSERT(pod.which_ == ArrayView || pod.which_ == ArrayViewCtor); return name_; } Scalar::Type viewType() const { - MOZ_ASSERT(pod.which_ == ArrayView); + MOZ_ASSERT(pod.which_ == ArrayView || pod.which_ == ArrayViewCtor); return pod.u.viewType_; } PropertyName *mathName() const { @@ -943,10 +947,17 @@ class AsmJSModule g.pod.u.ffiIndex_ = *ffiIndex = pod.numFFIs_++; return globals_.append(g); } - bool addArrayView(Scalar::Type vt, PropertyName *field) { + bool addArrayView(Scalar::Type vt, PropertyName *maybeField) { MOZ_ASSERT(!isFinishedWithModulePrologue()); pod.hasArrayView_ = true; - Global g(Global::ArrayView, field); + Global g(Global::ArrayView, maybeField); + g.pod.u.viewType_ = vt; + return globals_.append(g); + } + bool addArrayViewCtor(Scalar::Type vt, PropertyName *field) { + MOZ_ASSERT(!isFinishedWithModulePrologue()); + MOZ_ASSERT(field); + Global g(Global::ArrayViewCtor, field); g.pod.u.viewType_ = vt; return globals_.append(g); } diff --git a/js/src/asmjs/AsmJSValidate.cpp b/js/src/asmjs/AsmJSValidate.cpp index f096791194c4..ba40df14e454 100644 --- a/js/src/asmjs/AsmJSValidate.cpp +++ b/js/src/asmjs/AsmJSValidate.cpp @@ -1086,6 +1086,7 @@ class MOZ_STACK_CLASS ModuleCompiler FuncPtrTable, FFI, ArrayView, + ArrayViewCtor, MathBuiltinFunction, SimdCtor, SimdOperation @@ -1148,7 +1149,7 @@ class MOZ_STACK_CLASS ModuleCompiler return u.ffiIndex_; } Scalar::Type viewType() const { - MOZ_ASSERT(which_ == ArrayView); + MOZ_ASSERT(which_ == ArrayView || which_ == ArrayViewCtor); return u.viewType_; } bool isMathFunction() const { @@ -1623,11 +1624,20 @@ class MOZ_STACK_CLASS ModuleCompiler global->u.ffiIndex_ = index; return globals_.putNew(varName, global); } - bool addArrayView(PropertyName *varName, Scalar::Type vt, PropertyName *fieldName) { + bool addArrayView(PropertyName *varName, Scalar::Type vt, PropertyName *maybeField) { Global *global = moduleLifo_.new_(Global::ArrayView); if (!global) return false; - if (!module_->addArrayView(vt, fieldName)) + if (!module_->addArrayView(vt, maybeField)) + return false; + global->u.viewType_ = vt; + return globals_.putNew(varName, global); + } + bool addArrayViewCtor(PropertyName *varName, Scalar::Type vt, PropertyName *fieldName) { + Global *global = moduleLifo_.new_(Global::ArrayViewCtor); + if (!global) + return false; + if (!module_->addArrayViewCtor(vt, fieldName)) return false; global->u.viewType_ = vt; return globals_.putNew(varName, global); @@ -3499,53 +3509,78 @@ CheckGlobalVariableInitImport(ModuleCompiler &m, PropertyName *varName, ParseNod return CheckGlobalVariableImportExpr(m, varName, coercion, coercedExpr, isConst); } +static bool +IsArrayViewCtorName(ModuleCompiler &m, PropertyName *name, Scalar::Type *type) +{ + JSAtomState &names = m.cx()->names(); + if (name == names.Int8Array || name == names.SharedInt8Array) + *type = Scalar::Int8; + else if (name == names.Uint8Array || name == names.SharedUint8Array) + *type = Scalar::Uint8; + else if (name == names.Int16Array || name == names.SharedInt16Array) + *type = Scalar::Int16; + else if (name == names.Uint16Array || name == names.SharedUint16Array) + *type = Scalar::Uint16; + else if (name == names.Int32Array || name == names.SharedInt32Array) + *type = Scalar::Int32; + else if (name == names.Uint32Array || name == names.SharedUint32Array) + *type = Scalar::Uint32; + else if (name == names.Float32Array || name == names.SharedFloat32Array) + *type = Scalar::Float32; + else if (name == names.Float64Array || name == names.SharedFloat64Array) + *type = Scalar::Float64; + else + return false; + return true; +} + static bool CheckNewArrayView(ModuleCompiler &m, PropertyName *varName, ParseNode *newExpr) { - ParseNode *ctorExpr = ListHead(newExpr); - if (!ctorExpr->isKind(PNK_DOT)) - return m.fail(ctorExpr, "only valid 'new' import is 'new global.*Array(buf)'"); - - ParseNode *base = DotBase(ctorExpr); - PropertyName *field = DotMember(ctorExpr); - PropertyName *globalName = m.module().globalArgumentName(); if (!globalName) - return m.fail(base, "cannot create array view without an asm.js global parameter"); - if (!IsUseOfName(base, globalName)) - return m.failName(base, "expecting '%s.*Array", globalName); + return m.fail(newExpr, "cannot create array view without an asm.js global parameter"); + + PropertyName *bufferName = m.module().bufferArgumentName(); + if (!bufferName) + return m.fail(newExpr, "cannot create array view without an asm.js heap parameter"); + + ParseNode *ctorExpr = ListHead(newExpr); + + PropertyName *field; + Scalar::Type type; + if (ctorExpr->isKind(PNK_DOT)) { + ParseNode *base = DotBase(ctorExpr); + + if (!IsUseOfName(base, globalName)) + return m.failName(base, "expecting '%s.*Array", globalName); + + field = DotMember(ctorExpr); + if (!IsArrayViewCtorName(m, field, &type)) + return m.fail(ctorExpr, "could not match typed array name"); + } else { + if (!ctorExpr->isKind(PNK_NAME)) + return m.fail(ctorExpr, "expecting name of imported array view constructor"); + + PropertyName *globalName = ctorExpr->name(); + const ModuleCompiler::Global *global = m.lookupGlobal(globalName); + if (!global) + return m.failName(ctorExpr, "%s not found in module global scope", globalName); + + if (global->which() != ModuleCompiler::Global::ArrayViewCtor) + return m.failName(ctorExpr, "%s must be an imported array view constructor", globalName); + + field = nullptr; + type = global->viewType(); + } ParseNode *bufArg = NextNode(ctorExpr); if (!bufArg || NextNode(bufArg) != nullptr) return m.fail(ctorExpr, "array view constructor takes exactly one argument"); - PropertyName *bufferName = m.module().bufferArgumentName(); - if (!bufferName) - return m.fail(bufArg, "cannot create array view without an asm.js heap parameter"); if (!IsUseOfName(bufArg, bufferName)) return m.failName(bufArg, "argument to array view constructor must be '%s'", bufferName); - JSAtomState &names = m.cx()->names(); - Scalar::Type type; - if (field == names.Int8Array || field == names.SharedInt8Array) - type = Scalar::Int8; - else if (field == names.Uint8Array || field == names.SharedUint8Array) - type = Scalar::Uint8; - else if (field == names.Int16Array || field == names.SharedInt16Array) - type = Scalar::Int16; - else if (field == names.Uint16Array || field == names.SharedUint16Array) - type = Scalar::Uint16; - else if (field == names.Int32Array || field == names.SharedInt32Array) - type = Scalar::Int32; - else if (field == names.Uint32Array || field == names.SharedUint32Array) - type = Scalar::Uint32; - else if (field == names.Float32Array || field == names.SharedFloat32Array) - type = Scalar::Float32; - else if (field == names.Float64Array || field == names.SharedFloat64Array) - type = Scalar::Float64; - else - return m.fail(ctorExpr, "could not match typed array name"); - return m.addArrayView(varName, type, field); } @@ -3664,7 +3699,12 @@ CheckGlobalDotImport(ModuleCompiler &m, PropertyName *varName, ParseNode *initNo return m.addGlobalConstant(varName, GenericNaN(), field); if (field == m.cx()->names().Infinity) return m.addGlobalConstant(varName, PositiveInfinity(), field); - return m.failName(initNode, "'%s' is not a standard global constant", field); + + Scalar::Type type; + if (IsArrayViewCtorName(m, field, &type)) + return m.addArrayViewCtor(varName, type, field); + + return m.failName(initNode, "'%s' is not a standard constant or typed array name", field); } if (base->name() == m.module().importArgumentName()) @@ -3930,6 +3970,7 @@ CheckVarRef(FunctionCompiler &f, ParseNode *varRef, MDefinition **def, Type *typ case ModuleCompiler::Global::MathBuiltinFunction: case ModuleCompiler::Global::FuncPtrTable: case ModuleCompiler::Global::ArrayView: + case ModuleCompiler::Global::ArrayViewCtor: case ModuleCompiler::Global::SimdCtor: case ModuleCompiler::Global::SimdOperation: return f.failName(varRef, "'%s' may not be accessed by ordinary expressions", name); @@ -5186,6 +5227,7 @@ CheckCoercedCall(FunctionCompiler &f, ParseNode *call, RetType retType, MDefinit case ModuleCompiler::Global::Variable: case ModuleCompiler::Global::FuncPtrTable: case ModuleCompiler::Global::ArrayView: + case ModuleCompiler::Global::ArrayViewCtor: return f.failName(callee, "'%s' is not callable function", callee->name()); case ModuleCompiler::Global::SimdCtor: case ModuleCompiler::Global::SimdOperation: diff --git a/js/src/jit-test/tests/asm.js/testResize.js b/js/src/jit-test/tests/asm.js/testResize.js new file mode 100644 index 000000000000..52115fd33a78 --- /dev/null +++ b/js/src/jit-test/tests/asm.js/testResize.js @@ -0,0 +1,32 @@ +load(libdir + "asm.js"); + +assertAsmTypeFail('glob', USE_ASM + "var I32=glob.Int32Arra; function f() {} return f"); +var m = asmCompile('glob', USE_ASM + "var I32=glob.Int32Array; function f() {} return f"); +assertAsmLinkFail(m, {}); +assertAsmLinkFail(m, {Int32Array:null}); +assertAsmLinkFail(m, {Int32Array:{}}); +assertAsmLinkFail(m, {Int32Array:Uint32Array}); +assertEq(asmLink(m, {Int32Array:Int32Array})(), undefined); +var m = asmCompile('glob', 'ffis', 'buf', USE_ASM + "var I32=glob.Int32Array; function f() {} return f"); +assertEq(asmLink(m, this)(), undefined); +assertEq(asmLink(m, this, null, BUF_64KB)(), undefined); + +assertAsmTypeFail('glob', 'ffis', 'buf', USE_ASM + 'var I32=glob.Int32Array; var i32=new I3(buf); function f() {} return f'); +assertAsmTypeFail('glob', 'ffis', 'buf', USE_ASM + 'var I32=0; var i32=new I32(buf); function f() {} return f'); +var m = asmCompile('glob', 'ffis', 'buf', USE_ASM + 'var I32=glob.Int32Array; var i32=new I32(buf); function f() {} return f'); +assertAsmLinkFail(m, this, null, {}); +assertAsmLinkAlwaysFail(m, this, null, null); +assertAsmLinkFail(m, this, null, new ArrayBuffer(100)); +assertEq(asmLink(m, this, null, BUF_64KB)(), undefined); + +var m = asmCompile('glob', 'ffis', 'buf', USE_ASM + 'var I32=glob.Int32Array; var i32=new glob.Int32Array(buf); function f() {} return f'); +assertAsmLinkFail(m, this, null, {}); +assertAsmLinkAlwaysFail(m, this, null, null); +assertAsmLinkFail(m, this, null, new ArrayBuffer(100)); +assertEq(asmLink(m, this, null, BUF_64KB)(), undefined); + +var m = asmCompile('glob', 'ffis', 'buf', USE_ASM + 'var F32=glob.Float32Array; var i32=new glob.Int32Array(buf); function f() {} return f'); +assertAsmLinkFail(m, this, null, {}); +assertAsmLinkAlwaysFail(m, this, null, null); +assertAsmLinkFail(m, this, null, new ArrayBuffer(100)); +assertEq(asmLink(m, this, null, BUF_64KB)(), undefined); From 8322b99e7653e9d9b9670f0125734141141dc6ae Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Tue, 7 Oct 2014 14:07:52 -0500 Subject: [PATCH 08/77] Bug 965880 - OdinMonkey: allow asm.js to change (resize) heap (r=bbouvier) --- js/src/asmjs/AsmJSLink.cpp | 90 ++-- js/src/asmjs/AsmJSModule.cpp | 64 ++- js/src/asmjs/AsmJSModule.h | 102 +++- js/src/asmjs/AsmJSValidate.cpp | 484 ++++++++++++++++--- js/src/jit-test/lib/asm.js | 1 + js/src/jit-test/tests/asm.js/testResize.js | 241 +++++++++ js/src/jit-test/tests/asm.js/testTimeout7.js | 41 ++ js/src/jit/arm/MacroAssembler-arm.h | 3 + js/src/jit/mips/MacroAssembler-mips.h | 4 + js/src/jit/shared/Assembler-shared.h | 1 + js/src/jit/x64/Assembler-x64.h | 4 + js/src/jit/x64/CodeGenerator-x64.cpp | 11 + js/src/jit/x86/Assembler-x86.h | 3 + 13 files changed, 904 insertions(+), 145 deletions(-) create mode 100644 js/src/jit-test/tests/asm.js/testTimeout7.js diff --git a/js/src/asmjs/AsmJSLink.cpp b/js/src/asmjs/AsmJSLink.cpp index d0b6516a5806..c5ff30ef98f7 100644 --- a/js/src/asmjs/AsmJSLink.cpp +++ b/js/src/asmjs/AsmJSLink.cpp @@ -238,6 +238,30 @@ ValidateArrayView(JSContext *cx, AsmJSModule::Global &global, HandleValue global return true; } +static bool +ValidateByteLength(JSContext *cx, HandleValue globalVal) +{ + RootedPropertyName field(cx, cx->names().byteLength); + RootedValue v(cx); + if (!GetDataProperty(cx, globalVal, field, &v)) + return false; + + if (!v.isObject() || !v.toObject().isBoundFunction()) + return LinkFail(cx, "byteLength must be a bound function object"); + + RootedFunction fun(cx, &v.toObject().as()); + + RootedValue boundTarget(cx, ObjectValue(*fun->getBoundFunctionTarget())); + if (!IsNativeFunction(boundTarget, js_fun_call)) + return LinkFail(cx, "bound target of byteLength must be Function.prototype.call"); + + RootedValue boundThis(cx, fun->getBoundFunctionThis()); + if (!IsNativeFunction(boundThis, ArrayBufferObject::byteLengthGetter)) + return LinkFail(cx, "bound this value must be ArrayBuffer.protototype.byteLength accessor"); + + return true; +} + static bool ValidateMathBuiltinFunction(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal) { @@ -441,9 +465,8 @@ LinkModuleToHeap(JSContext *cx, AsmJSModule &module, Handle msg( - JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (which is the " - "largest constant heap access offset rounded up to the next valid " - "heap size).", + JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (the size implied " + "by const heap accesses and/or change-heap minimum-length requirements).", heapLength, module.minHeapLength())); return LinkFail(cx, msg.get()); @@ -472,17 +495,9 @@ DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) { module.setIsDynamicallyLinked(); - RootedValue globalVal(cx); - if (args.length() > 0) - globalVal = args[0]; - - RootedValue importVal(cx); - if (args.length() > 1) - importVal = args[1]; - - RootedValue bufferVal(cx); - if (args.length() > 2) - bufferVal = args[2]; + HandleValue globalVal = args.get(0); + HandleValue importVal = args.get(1); + HandleValue bufferVal = args.get(2); Rooted heap(cx); if (module.hasArrayView()) { @@ -514,6 +529,10 @@ DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) if (!ValidateArrayView(cx, global, globalVal)) return false; break; + case AsmJSModule::Global::ByteLength: + if (!ValidateByteLength(cx, globalVal)) + return false; + break; case AsmJSModule::Global::MathBuiltinFunction: if (!ValidateMathBuiltinFunction(cx, global, globalVal)) return false; @@ -541,12 +560,32 @@ DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) return true; } +static bool +ChangeHeap(JSContext *cx, AsmJSModule &module, CallArgs args) +{ + HandleValue bufferArg = args.get(0); + if (!IsArrayBuffer(bufferArg)) { + ReportIncompatible(cx, args); + return false; + } + + Rooted newBuffer(cx, &bufferArg.toObject().as()); + bool rval = module.changeHeap(newBuffer, cx); + + args.rval().set(BooleanValue(rval)); + return true; +} + +// An asm.js function stores, in its extended slots: +// - a pointer to the module from which it was returned +// - its index in the ordered list of exported functions static const unsigned ASM_MODULE_SLOT = 0; static const unsigned ASM_EXPORT_INDEX_SLOT = 1; static unsigned FunctionToExportedFunctionIndex(HandleFunction fun) { + MOZ_ASSERT(IsAsmJSFunction(fun)); Value v = fun->getExtendedSlot(ASM_EXPORT_INDEX_SLOT); return v.toInt32(); } @@ -564,18 +603,18 @@ FunctionToEnclosingModule(HandleFunction fun) return fun->getExtendedSlot(ASM_MODULE_SLOT).toObject().as().module(); } -// The JSNative for the functions nested in an asm.js module. Calling this -// native will trampoline into generated code. +// This is the js::Native for functions exported by an asm.js module. static bool CallAsmJS(JSContext *cx, unsigned argc, Value *vp) { CallArgs callArgs = CallArgsFromVp(argc, vp); RootedFunction callee(cx, &callArgs.callee().as()); - - // An asm.js function stores, in its extended slots: - // - a pointer to the module from which it was returned - // - its index in the ordered list of exported functions AsmJSModule &module = FunctionToEnclosingModule(callee); + const AsmJSModule::ExportedFunction &func = FunctionToExportedFunction(callee, module); + + // The heap-changing function is a special-case and is implemented by C++. + if (func.isChangeHeap()) + return ChangeHeap(cx, module, callArgs); // Enable/disable profiling in the asm.js module to match the current global // profiling state. Don't do this if the module is already active on the @@ -584,11 +623,6 @@ CallAsmJS(JSContext *cx, unsigned argc, Value *vp) if (module.profilingEnabled() != cx->runtime()->spsProfiler.enabled() && !module.active()) module.setProfilingEnabled(cx->runtime()->spsProfiler.enabled(), cx); - // An exported function points to the code as well as the exported - // function's signature, which implies the dynamic coercions performed on - // the arguments. - const AsmJSModule::ExportedFunction &func = FunctionToExportedFunction(callee, module); - // The calling convention for an external call into asm.js is to pass an // array of 16-byte values where each value contains either a coerced int32 // (in the low word), a double value (in the low dword) or a SIMD vector @@ -704,9 +738,9 @@ NewExportedFunction(JSContext *cx, const AsmJSModule::ExportedFunction &func, HandleObject moduleObj, unsigned exportIndex) { RootedPropertyName name(cx, func.name()); - JSFunction *fun = NewFunction(cx, NullPtr(), CallAsmJS, func.numArgs(), - JSFunction::ASMJS_CTOR, cx->global(), name, - JSFunction::ExtendedFinalizeKind); + unsigned numArgs = func.isChangeHeap() ? 1 : func.numArgs(); + JSFunction *fun = NewFunction(cx, NullPtr(), CallAsmJS, numArgs, JSFunction::ASMJS_CTOR, + cx->global(), name, JSFunction::ExtendedFinalizeKind); if (!fun) return nullptr; diff --git a/js/src/asmjs/AsmJSModule.cpp b/js/src/asmjs/AsmJSModule.cpp index 6bcef9a799ef..cf169e109d51 100644 --- a/js/src/asmjs/AsmJSModule.cpp +++ b/js/src/asmjs/AsmJSModule.cpp @@ -337,8 +337,10 @@ AsmJSModule::finish(ExclusiveContext *cx, TokenStream &tokenStream, MacroAssembl AsmJSHeapAccess &a = heapAccesses_[i]; a.setOffset(masm.actualOffset(a.offset())); } - for (unsigned i = 0; i < numExportedFunctions(); i++) - exportedFunction(i).updateCodeOffset(masm); + for (unsigned i = 0; i < numExportedFunctions(); i++) { + if (!exportedFunction(i).isChangeHeap()) + exportedFunction(i).updateCodeOffset(masm); + } for (unsigned i = 0; i < numExits(); i++) exit(i).updateOffsets(masm); for (size_t i = 0; i < callSites_.length(); i++) { @@ -765,13 +767,13 @@ AsmJSModule::initHeap(Handle heap, JSContext *cx X86Assembler::setPointer(addr, (void *)(heapOffset + disp)); } #elif defined(JS_CODEGEN_X64) - int32_t heapLength = int32_t(intptr_t(heap->byteLength())); if (usesSignalHandlersForOOB()) return; // If we cannot use the signal handlers, we need to patch the heap length // checks at the right places. All accesses that have been recorded are the // only ones that need bound checks (see also // CodeGeneratorX64::visitAsmJS{Load,Store}Heap) + int32_t heapLength = int32_t(intptr_t(heap->byteLength())); for (size_t i = 0; i < heapAccesses_.length(); i++) { const jit::AsmJSHeapAccess &access = heapAccesses_[i]; if (access.hasLengthCheck()) @@ -787,8 +789,29 @@ AsmJSModule::initHeap(Handle heap, JSContext *cx } void -AsmJSModule::restoreToInitialState(uint8_t *prevCode, - ArrayBufferObjectMaybeShared *maybePrevBuffer, +AsmJSModule::restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer) +{ +#if defined(JS_CODEGEN_X86) + if (maybePrevBuffer) { + // Subtract out the base-pointer added by AsmJSModule::initHeap. + uint8_t *ptrBase = maybePrevBuffer->dataPointer(); + for (unsigned i = 0; i < heapAccesses_.length(); i++) { + const jit::AsmJSHeapAccess &access = heapAccesses_[i]; + void *addr = access.patchOffsetAt(code_); + uint8_t *ptr = reinterpret_cast(X86Assembler::getPointer(addr)); + MOZ_ASSERT(ptr >= ptrBase); + X86Assembler::setPointer(addr, (void *)(ptr - ptrBase)); + } + } +#endif + + maybeHeap_ = nullptr; + heapDatum() = nullptr; +} + +void +AsmJSModule::restoreToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer, + uint8_t *prevCode, ExclusiveContext *cx) { #ifdef DEBUG @@ -817,19 +840,7 @@ AsmJSModule::restoreToInitialState(uint8_t *prevCode, } #endif - if (maybePrevBuffer) { -#if defined(JS_CODEGEN_X86) - // Subtract out the base-pointer added by AsmJSModule::initHeap. - uint8_t *ptrBase = maybePrevBuffer->dataPointer(); - for (unsigned i = 0; i < heapAccesses_.length(); i++) { - const jit::AsmJSHeapAccess &access = heapAccesses_[i]; - void *addr = access.patchOffsetAt(code_); - uint8_t *ptr = reinterpret_cast(X86Assembler::getPointer(addr)); - MOZ_ASSERT(ptr >= ptrBase); - X86Assembler::setPointer(addr, (void *)(ptr - ptrBase)); - } -#endif - } + restoreHeapToInitialState(maybePrevBuffer); } static void @@ -1550,7 +1561,22 @@ AsmJSModule::clone(JSContext *cx, ScopedJSDeletePtr *moduleOut) con // flush all of them at once. out.setAutoFlushICacheRange(); - out.restoreToInitialState(code_, maybeHeap_, cx); + out.restoreToInitialState(maybeHeap_, code_, cx); + return true; +} + +bool +AsmJSModule::changeHeap(Handle newBuffer, JSContext *cx) +{ + uint32_t heapLength = newBuffer->byteLength(); + if (heapLength & pod.heapLengthMask_ || heapLength < pod.minHeapLength_) + return false; + + MOZ_ASSERT(IsValidAsmJSHeapLength(heapLength)); + MOZ_ASSERT(!IsDeprecatedAsmJSHeapLength(heapLength)); + + restoreHeapToInitialState(maybeHeap_); + initHeap(newBuffer, cx); return true; } diff --git a/js/src/asmjs/AsmJSModule.h b/js/src/asmjs/AsmJSModule.h index bf7ba366c75d..e256d416ee46 100644 --- a/js/src/asmjs/AsmJSModule.h +++ b/js/src/asmjs/AsmJSModule.h @@ -201,7 +201,7 @@ class AsmJSModule { public: enum Which { Variable, FFI, ArrayView, ArrayViewCtor, MathBuiltinFunction, Constant, - SimdCtor, SimdOperation}; + SimdCtor, SimdOperation, ByteLength }; enum VarInitKind { InitConstant, InitImport }; enum ConstantKind { GlobalConstant, MathConstant }; @@ -410,13 +410,11 @@ class AsmJSModule PropertyName *maybeFieldName_; ArgCoercionVector argCoercions_; struct Pod { + bool isChangeHeap_; ReturnType returnType_; uint32_t codeOffset_; - // These two fields are offsets to the beginning of the ScriptSource - // of the module, and thus invariant under serialization (unlike - // absolute offsets into ScriptSource). - uint32_t startOffsetInModule_; - uint32_t endOffsetInModule_; + uint32_t startOffsetInModule_; // Store module-start-relative offsets + uint32_t endOffsetInModule_; // so preserved by serialization. } pod; friend class AsmJSModule; @@ -427,14 +425,29 @@ class AsmJSModule ArgCoercionVector &&argCoercions, ReturnType returnType) { + MOZ_ASSERT(name->isTenured()); + MOZ_ASSERT_IF(maybeFieldName, maybeFieldName->isTenured()); name_ = name; maybeFieldName_ = maybeFieldName; argCoercions_ = mozilla::Move(argCoercions); + pod.isChangeHeap_ = false; pod.returnType_ = returnType; pod.codeOffset_ = UINT32_MAX; pod.startOffsetInModule_ = startOffsetInModule; pod.endOffsetInModule_ = endOffsetInModule; - MOZ_ASSERT_IF(maybeFieldName_, name_->isTenured()); + } + + ExportedFunction(PropertyName *name, + uint32_t startOffsetInModule, uint32_t endOffsetInModule, + PropertyName *maybeFieldName) + { + MOZ_ASSERT(name->isTenured()); + MOZ_ASSERT_IF(maybeFieldName, maybeFieldName->isTenured()); + name_ = name; + maybeFieldName_ = maybeFieldName; + pod.isChangeHeap_ = true; + pod.startOffsetInModule_ = startOffsetInModule; + pod.endOffsetInModule_ = endOffsetInModule; } void trace(JSTracer *trc) { @@ -452,34 +465,43 @@ class AsmJSModule pod = rhs.pod; } - void initCodeOffset(unsigned off) { - MOZ_ASSERT(pod.codeOffset_ == UINT32_MAX); - pod.codeOffset_ = off; - } - void updateCodeOffset(jit::MacroAssembler &masm) { - pod.codeOffset_ = masm.actualOffset(pod.codeOffset_); - } - - PropertyName *name() const { return name_; } + PropertyName *maybeFieldName() const { + return maybeFieldName_; + } uint32_t startOffsetInModule() const { return pod.startOffsetInModule_; } uint32_t endOffsetInModule() const { return pod.endOffsetInModule_; } - PropertyName *maybeFieldName() const { - return maybeFieldName_; + + bool isChangeHeap() const { + return pod.isChangeHeap_; } + + void initCodeOffset(unsigned off) { + MOZ_ASSERT(!isChangeHeap()); + MOZ_ASSERT(pod.codeOffset_ == UINT32_MAX); + pod.codeOffset_ = off; + } + void updateCodeOffset(jit::MacroAssembler &masm) { + MOZ_ASSERT(!isChangeHeap()); + pod.codeOffset_ = masm.actualOffset(pod.codeOffset_); + } + unsigned numArgs() const { + MOZ_ASSERT(!isChangeHeap()); return argCoercions_.length(); } AsmJSCoercion argCoercion(unsigned i) const { + MOZ_ASSERT(!isChangeHeap()); return argCoercions_[i]; } ReturnType returnType() const { + MOZ_ASSERT(!isChangeHeap()); return pod.returnType_; } @@ -752,6 +774,7 @@ class AsmJSModule size_t codeBytes_; // function bodies and stubs size_t totalBytes_; // function bodies, stubs, and global data uint32_t minHeapLength_; + uint32_t heapLengthMask_; uint32_t numGlobalScalarVars_; uint32_t numGlobalSimdVars_; uint32_t numFFIs_; @@ -759,6 +782,7 @@ class AsmJSModule uint32_t srcLengthWithRightBrace_; bool strict_; bool hasArrayView_; + bool hasFixedMinHeapLength_; bool usesSignalHandlers_; } pod; @@ -961,6 +985,11 @@ class AsmJSModule g.pod.u.viewType_ = vt; return globals_.append(g); } + bool addByteLength() { + MOZ_ASSERT(!isFinishedWithModulePrologue()); + Global g(Global::ByteLength, nullptr); + return globals_.append(g); + } bool addMathBuiltinFunction(AsmJSMathBuiltinFunction func, PropertyName *field) { MOZ_ASSERT(!isFinishedWithModulePrologue()); Global g(Global::MathBuiltinFunction, field); @@ -1010,11 +1039,23 @@ class AsmJSModule /*************************************************************************/ // These functions are called while parsing/compiling function bodies: - void requireHeapLengthToBeAtLeast(uint32_t len) { + void addChangeHeap(uint32_t mask, uint32_t min) { + MOZ_ASSERT(isFinishedWithModulePrologue()); + MOZ_ASSERT(!pod.hasFixedMinHeapLength_); + MOZ_ASSERT(IsValidAsmJSHeapLength(mask + 1)); + MOZ_ASSERT(min >= RoundUpToNextValidAsmJSHeapLength(0)); + pod.heapLengthMask_ = mask; + pod.minHeapLength_ = min; + pod.hasFixedMinHeapLength_ = true; + } + bool tryRequireHeapLengthToBeAtLeast(uint32_t len) { MOZ_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies()); + if (pod.hasFixedMinHeapLength_ && len > pod.minHeapLength_) + return false; len = RoundUpToNextValidAsmJSHeapLength(len); if (len > pod.minHeapLength_) pod.minHeapLength_ = len; + return true; } bool addCodeRange(CodeRange::Kind kind, uint32_t begin, uint32_t end) { return codeRanges_.append(CodeRange(kind, begin, end)); @@ -1142,6 +1183,19 @@ class AsmJSModule maybeFieldName, mozilla::Move(argCoercions), returnType); return exports_.length() < UINT32_MAX && exports_.append(mozilla::Move(func)); } + bool addExportedChangeHeap(PropertyName *name, + uint32_t funcSrcBegin, + uint32_t funcSrcEnd, + PropertyName *maybeFieldName) + { + // See addExportedFunction. + MOZ_ASSERT(isFinishedWithFunctionBodies() && !isFinished()); + MOZ_ASSERT(srcStart_ < funcSrcBegin); + MOZ_ASSERT(funcSrcBegin < funcSrcEnd); + ExportedFunction func(name, funcSrcBegin - srcStart_, funcSrcEnd - srcStart_, + maybeFieldName); + return exports_.length() < UINT32_MAX && exports_.append(mozilla::Move(func)); + } unsigned numExportedFunctions() const { MOZ_ASSERT(isFinishedWithFunctionBodies()); return exports_.length(); @@ -1273,6 +1327,7 @@ class AsmJSModule return activation() != nullptr; } static unsigned heapGlobalDataOffset() { + JS_STATIC_ASSERT(jit::AsmJSHeapGlobalDataOffset == sizeof(void*)); return sizeof(void*); } uint8_t *&heapDatum() const { @@ -1387,16 +1442,19 @@ class AsmJSModule MOZ_ASSERT(isDynamicallyLinked()); } void initHeap(Handle heap, JSContext *cx); - bool clone(JSContext *cx, ScopedJSDeletePtr *moduleOut) const; - void restoreToInitialState(uint8_t *prevCode, - ArrayBufferObjectMaybeShared *maybePrevBuffer, + void restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer); + void restoreToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer, + uint8_t *prevCode, ExclusiveContext *cx); + bool clone(JSContext *cx, ScopedJSDeletePtr *moduleOut) const; + bool changeHeap(Handle newHeap, JSContext *cx); /*************************************************************************/ // Functions that can be called after dynamic linking succeeds: CodePtr entryTrampoline(const ExportedFunction &func) const { MOZ_ASSERT(isDynamicallyLinked()); + MOZ_ASSERT(!func.isChangeHeap()); return JS_DATA_TO_FUNC_PTR(CodePtr, code_ + func.pod.codeOffset_); } uint8_t *interruptExit() const { diff --git a/js/src/asmjs/AsmJSValidate.cpp b/js/src/asmjs/AsmJSValidate.cpp index ba40df14e454..2b77c733117b 100644 --- a/js/src/asmjs/AsmJSValidate.cpp +++ b/js/src/asmjs/AsmJSValidate.cpp @@ -1089,7 +1089,9 @@ class MOZ_STACK_CLASS ModuleCompiler ArrayViewCtor, MathBuiltinFunction, SimdCtor, - SimdOperation + SimdOperation, + ByteLength, + ChangeHeap }; private: @@ -1110,6 +1112,10 @@ class MOZ_STACK_CLASS ModuleCompiler AsmJSSimdType type_; AsmJSSimdOperation which_; } simdOp; + struct { + uint32_t srcBegin_; + uint32_t srcEnd_; + } changeHeap; } u; friend class ModuleCompiler; @@ -1177,6 +1183,14 @@ class MOZ_STACK_CLASS ModuleCompiler MOZ_ASSERT(which_ == SimdOperation); return u.simdOp.type_; } + uint32_t changeHeapSrcBegin() const { + MOZ_ASSERT(which_ == ChangeHeap); + return u.changeHeap.srcBegin_; + } + uint32_t changeHeapSrcEnd() const { + MOZ_ASSERT(which_ == ChangeHeap); + return u.changeHeap.srcEnd_; + } }; typedef Vector FuncPtrVector; @@ -1264,6 +1278,16 @@ class MOZ_STACK_CLASS ModuleCompiler } }; + struct ArrayView + { + ArrayView(PropertyName *name, Scalar::Type type) + : name(name), type(type) + {} + + PropertyName *name; + Scalar::Type type; + }; + private: struct SlowFunction { @@ -1283,6 +1307,7 @@ class MOZ_STACK_CLASS ModuleCompiler typedef Vector FuncVector; typedef Vector GlobalAccessVector; typedef Vector SlowFunctionVector; + typedef Vector ArrayViewVector; ExclusiveContext * cx_; AsmJSParser & parser_; @@ -1297,6 +1322,7 @@ class MOZ_STACK_CLASS ModuleCompiler GlobalMap globals_; FuncVector functions_; FuncPtrTableVector funcPtrTables_; + ArrayViewVector arrayViews_; ExitMap exits_; MathNameMap standardLibraryMathNames_; SimdOperationNameMap standardLibrarySimdOpNames_; @@ -1313,6 +1339,8 @@ class MOZ_STACK_CLASS ModuleCompiler DebugOnly finishedFunctionBodies_; bool supportsSimd_; + bool canValidateChangeHeap_; + bool hasChangeHeap_; bool addStandardLibraryMathName(const char *name, AsmJSMathBuiltinFunction func) { JSAtom *atom = Atomize(cx_, name, strlen(name)); @@ -1346,6 +1374,7 @@ class MOZ_STACK_CLASS ModuleCompiler globals_(cx), functions_(cx), funcPtrTables_(cx), + arrayViews_(cx), exits_(cx), standardLibraryMathNames_(cx), standardLibrarySimdOpNames_(cx), @@ -1355,7 +1384,9 @@ class MOZ_STACK_CLASS ModuleCompiler usecBefore_(PRMJ_Now()), slowFunctions_(cx), finishedFunctionBodies_(false), - supportsSimd_(cx->jitSupportsSimd()) + supportsSimd_(cx->jitSupportsSimd()), + canValidateChangeHeap_(false), + hasChangeHeap_(false) { MOZ_ASSERT(moduleFunctionNode_->pn_funbox == parser.pc->sc->asFunctionBox()); } @@ -1542,6 +1573,12 @@ class MOZ_STACK_CLASS ModuleCompiler ExitMap::Range allExits() const { return exits_.all(); } + unsigned numArrayViews() const { + return arrayViews_.length(); + } + const ArrayView &arrayView(unsigned i) const { + return arrayViews_[i]; + } /***************************************************** Mutable interface */ @@ -1614,6 +1651,23 @@ class MOZ_STACK_CLASS ModuleCompiler *table = &funcPtrTables_.back(); return true; } + bool addByteLength(PropertyName *name) { + canValidateChangeHeap_ = true; + if (!module_->addByteLength()) + return false; + Global *global = moduleLifo_.new_(Global::ByteLength); + return global && globals_.putNew(name, global); + } + bool addChangeHeap(PropertyName *name, ParseNode *fn, uint32_t mask, uint32_t min) { + hasChangeHeap_ = true; + module_->addChangeHeap(mask, min); + Global *global = moduleLifo_.new_(Global::ChangeHeap); + if (!global) + return false; + global->u.changeHeap.srcBegin_ = fn->pn_pos.begin; + global->u.changeHeap.srcEnd_ = fn->pn_pos.end; + return globals_.putNew(name, global); + } bool addFFI(PropertyName *varName, PropertyName *field) { Global *global = moduleLifo_.new_(Global::FFI); if (!global) @@ -1625,6 +1679,8 @@ class MOZ_STACK_CLASS ModuleCompiler return globals_.putNew(varName, global); } bool addArrayView(PropertyName *varName, Scalar::Type vt, PropertyName *maybeField) { + if (!arrayViews_.append(ArrayView(varName, vt))) + return false; Global *global = moduleLifo_.new_(Global::ArrayView); if (!global) return false; @@ -1693,17 +1749,21 @@ class MOZ_STACK_CLASS ModuleCompiler return false; return addGlobalDoubleConstant(varName, constant); } - bool addExportedFunction(const Func *func, PropertyName *maybeFieldName) { + bool addExportedFunction(const Func &func, PropertyName *maybeFieldName) { AsmJSModule::ArgCoercionVector argCoercions; - const VarTypeVector &args = func->sig().args(); + const VarTypeVector &args = func.sig().args(); if (!argCoercions.resize(args.length())) return false; for (unsigned i = 0; i < args.length(); i++) argCoercions[i] = args[i].toCoercion(); - AsmJSModule::ReturnType retType = func->sig().retType().toModuleReturnType(); - return module_->addExportedFunction(func->name(), func->srcBegin(), func->srcEnd(), + AsmJSModule::ReturnType retType = func.sig().retType().toModuleReturnType(); + return module_->addExportedFunction(func.name(), func.srcBegin(), func.srcEnd(), maybeFieldName, Move(argCoercions), retType); } + bool addExportedChangeHeap(PropertyName *name, const Global &g, PropertyName *maybeFieldName) { + return module_->addExportedChangeHeap(name, g.changeHeapSrcBegin(), g.changeHeapSrcEnd(), + maybeFieldName); + } bool addExit(unsigned ffiIndex, PropertyName *name, Signature &&sig, unsigned *exitIndex) { ExitDescriptor exitDescriptor(name, Move(sig)); ExitMap::AddPtr p = exits_.lookupForAdd(exitDescriptor); @@ -1716,8 +1776,8 @@ class MOZ_STACK_CLASS ModuleCompiler return exits_.add(p, Move(exitDescriptor), *exitIndex); } - void requireHeapLengthToBeAtLeast(uint32_t len) { - module_->requireHeapLengthToBeAtLeast(len); + bool tryRequireHeapLengthToBeAtLeast(uint32_t len) { + return module_->tryRequireHeapLengthToBeAtLeast(len); } uint32_t minHeapLength() const { return module_->minHeapLength(); @@ -1729,6 +1789,14 @@ class MOZ_STACK_CLASS ModuleCompiler void startFunctionBodies() { module_->startFunctionBodies(); } + bool tryOnceToValidateChangeHeap() { + bool ret = canValidateChangeHeap_; + canValidateChangeHeap_ = false; + return ret; + } + bool hasChangeHeap() { + return hasChangeHeap_; + } bool finishGeneratingFunction(Func &func, CodeGenerator &codegen, const AsmJSFunctionLabels &labels) { @@ -1976,8 +2044,10 @@ IsSimdTuple(ModuleCompiler &m, ParseNode *pn, AsmJSSimdType *type) static bool IsNumericLiteral(ModuleCompiler &m, ParseNode *pn); + static AsmJSNumLit ExtractNumericLiteral(ModuleCompiler &m, ParseNode *pn); + static inline bool IsLiteralInt(ModuleCompiler &m, ParseNode *pn, uint32_t *u32); @@ -2192,6 +2262,8 @@ class FunctionCompiler LabeledBlockMap labeledBreaks_; LabeledBlockMap labeledContinues_; + unsigned heapExpressionDepth_; + public: FunctionCompiler(ModuleCompiler &m, ParseNode *fn, LifoAlloc &lifo) : m_(m), @@ -2209,7 +2281,8 @@ class FunctionCompiler unlabeledBreaks_(m.cx()), unlabeledContinues_(m.cx()), labeledBreaks_(m.cx()), - labeledContinues_(m.cx()) + labeledContinues_(m.cx()), + heapExpressionDepth_(0) {} ModuleCompiler & m() const { return m_; } @@ -2371,6 +2444,19 @@ class FunctionCompiler return m_.supportsSimd(); } + /*************************************************************************/ + + void enterHeapExpression() { + heapExpressionDepth_++; + } + void leaveHeapExpression() { + MOZ_ASSERT(heapExpressionDepth_ > 0); + heapExpressionDepth_--; + } + bool canCall() const { + return heapExpressionDepth_ == 0 || !m_.hasChangeHeap(); + } + /***************************** Code generation (after local scope setup) */ MDefinition *constant(const AsmJSNumLit &lit) @@ -3534,6 +3620,19 @@ IsArrayViewCtorName(ModuleCompiler &m, PropertyName *name, Scalar::Type *type) return true; } +static bool +CheckNewArrayViewArgs(ModuleCompiler &m, ParseNode *ctorExpr, PropertyName *bufferName) +{ + ParseNode *bufArg = NextNode(ctorExpr); + if (!bufArg || NextNode(bufArg) != nullptr) + return m.fail(ctorExpr, "array view constructor takes exactly one argument"); + + if (!IsUseOfName(bufArg, bufferName)) + return m.failName(bufArg, "argument to array view constructor must be '%s'", bufferName); + + return true; +} + static bool CheckNewArrayView(ModuleCompiler &m, PropertyName *varName, ParseNode *newExpr) { @@ -3574,12 +3673,8 @@ CheckNewArrayView(ModuleCompiler &m, PropertyName *varName, ParseNode *newExpr) type = global->viewType(); } - ParseNode *bufArg = NextNode(ctorExpr); - if (!bufArg || NextNode(bufArg) != nullptr) - return m.fail(ctorExpr, "array view constructor takes exactly one argument"); - - if (!IsUseOfName(bufArg, bufferName)) - return m.failName(bufArg, "argument to array view constructor must be '%s'", bufferName); + if (!CheckNewArrayViewArgs(m, ctorExpr, bufferName)) + return false; return m.addArrayView(varName, type, field); } @@ -3699,6 +3794,8 @@ CheckGlobalDotImport(ModuleCompiler &m, PropertyName *varName, ParseNode *initNo return m.addGlobalConstant(varName, GenericNaN(), field); if (field == m.cx()->names().Infinity) return m.addGlobalConstant(varName, PositiveInfinity(), field); + if (field == m.cx()->names().byteLength) + return m.addByteLength(varName); Scalar::Type type; if (IsArrayViewCtorName(m, field, &type)) @@ -3973,6 +4070,8 @@ CheckVarRef(FunctionCompiler &f, ParseNode *varRef, MDefinition **def, Type *typ case ModuleCompiler::Global::ArrayViewCtor: case ModuleCompiler::Global::SimdCtor: case ModuleCompiler::Global::SimdOperation: + case ModuleCompiler::Global::ByteLength: + case ModuleCompiler::Global::ChangeHeap: return f.failName(varRef, "'%s' may not be accessed by ordinary expressions", name); } return true; @@ -4050,7 +4149,10 @@ CheckArrayAccess(FunctionCompiler &f, ParseNode *elem, Scalar::Type *viewType, return f.fail(indexExpr, "constant index out of range"); unsigned elementSize = 1 << TypedArrayShift(*viewType); - f.m().requireHeapLengthToBeAtLeast(byteOffset + elementSize); + if (!f.m().tryRequireHeapLengthToBeAtLeast(byteOffset + elementSize)) { + return f.failf(indexExpr, "constant index outside heap size declared by the " + "change-heap function (0x%x)", f.m().minHeapLength()); + } *needsBoundsCheck = NO_BOUNDS_CHECK; *def = f.constant(Int32Value(byteOffset), Type::Int); @@ -4078,10 +4180,14 @@ CheckArrayAccess(FunctionCompiler &f, ParseNode *elem, Scalar::Type *viewType, if (pointerNode->isKind(PNK_BITAND)) FoldMaskedArrayIndex(f, &pointerNode, &mask, needsBoundsCheck); + f.enterHeapExpression(); + Type pointerType; if (!CheckExpr(f, pointerNode, &pointerDef, &pointerType)) return false; + f.leaveHeapExpression(); + if (!pointerType.isIntish()) return f.failf(indexExpr, "%s is not a subtype of int", pointerType.toChars()); } else { @@ -4094,10 +4200,14 @@ CheckArrayAccess(FunctionCompiler &f, ParseNode *elem, Scalar::Type *viewType, if (indexExpr->isKind(PNK_BITAND)) folded = FoldMaskedArrayIndex(f, &indexExpr, &mask, needsBoundsCheck); + f.enterHeapExpression(); + Type pointerType; if (!CheckExpr(f, indexExpr, &pointerDef, &pointerType)) return false; + f.leaveHeapExpression(); + if (folded) { if (!pointerType.isIntish()) return f.failf(indexExpr, "%s is not a subtype of intish", pointerType.toChars()); @@ -4181,11 +4291,15 @@ CheckStoreArray(FunctionCompiler &f, ParseNode *lhs, ParseNode *rhs, MDefinition if (!CheckArrayAccess(f, lhs, &viewType, &pointerDef, &needsBoundsCheck)) return false; + f.enterHeapExpression(); + MDefinition *rhsDef; Type rhsType; if (!CheckExpr(f, rhs, &rhsDef, &rhsType)) return false; + f.leaveHeapExpression(); + switch (viewType) { case Scalar::Int8: case Scalar::Int16: @@ -5200,6 +5314,11 @@ CheckCoercedCall(FunctionCompiler &f, ParseNode *call, RetType retType, MDefinit { JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); + if (!f.canCall()) { + return f.fail(call, "call expressions may not be nested inside heap expressions when " + "the module contains a change-heap function"); + } + if (IsNumericLiteral(f.m(), call)) { AsmJSNumLit literal = ExtractNumericLiteral(f.m(), call); MDefinition *result = f.constant(literal); @@ -5228,6 +5347,8 @@ CheckCoercedCall(FunctionCompiler &f, ParseNode *call, RetType retType, MDefinit case ModuleCompiler::Global::FuncPtrTable: case ModuleCompiler::Global::ArrayView: case ModuleCompiler::Global::ArrayViewCtor: + case ModuleCompiler::Global::ByteLength: + case ModuleCompiler::Global::ChangeHeap: return f.failName(callee, "'%s' is not callable function", callee->name()); case ModuleCompiler::Global::SimdCtor: case ModuleCompiler::Global::SimdOperation: @@ -6367,6 +6488,195 @@ CheckStatement(FunctionCompiler &f, ParseNode *stmt, LabelVector *maybeLabels) return f.fail(stmt, "unexpected statement kind"); } +static bool +CheckByteLengthCall(ModuleCompiler &m, ParseNode *pn, PropertyName *newBufferName) +{ + if (!pn->isKind(PNK_CALL) || !CallCallee(pn)->isKind(PNK_NAME)) + return m.fail(pn, "expecting call to imported byteLength"); + + const ModuleCompiler::Global *global = m.lookupGlobal(CallCallee(pn)->name()); + if (!global || global->which() != ModuleCompiler::Global::ByteLength) + return m.fail(pn, "expecting call to imported byteLength"); + + if (CallArgListLength(pn) != 1 || !IsUseOfName(CallArgList(pn), newBufferName)) + return m.failName(pn, "expecting %s as argument to byteLength call", newBufferName); + + return true; +} + +static bool +CheckHeapLengthCondition(ModuleCompiler &m, ParseNode *cond, PropertyName *newBufferName, + uint32_t *mask, uint32_t *minimumLength) +{ + if (!cond->isKind(PNK_OR)) + return m.fail(cond, "expecting byteLength & K || byteLength <= L"); + + ParseNode *leftCond = BinaryLeft(cond); + ParseNode *rightCond = BinaryRight(cond); + + if (!leftCond->isKind(PNK_BITAND)) + return m.fail(leftCond, "expecting byteLength & K"); + + if (!CheckByteLengthCall(m, BinaryLeft(leftCond), newBufferName)) + return false; + + ParseNode *maskNode = BinaryRight(leftCond); + if (!IsLiteralInt(m, maskNode, mask)) + return m.fail(maskNode, "expecting integer literal mask"); + if ((*mask & 0xffffff) != 0xffffff) + return m.fail(maskNode, "mask value must have the bits 0xffffff set"); + + if (!rightCond->isKind(PNK_LE)) + return m.fail(rightCond, "expecting byteLength <= L"); + + if (!CheckByteLengthCall(m, BinaryLeft(rightCond), newBufferName)) + return false; + + ParseNode *minLengthNode = BinaryRight(rightCond); + uint32_t minLengthExclusive; + if (!IsLiteralInt(m, minLengthNode, &minLengthExclusive)) + return m.fail(minLengthNode, "expecting integer limit literal"); + if (minLengthExclusive < 0xffffff) + return m.fail(minLengthNode, "limit value must be >= 0xffffff"); + + // Add one to convert from exclusive (the branch rejects if ==) to inclusive. + *minimumLength = minLengthExclusive + 1; + return true; +} + +static bool +CheckReturnBoolLiteral(ModuleCompiler &m, ParseNode *stmt, bool retval) +{ + if (!stmt) + return m.fail(stmt, "expected return statement"); + + if (stmt->isKind(PNK_STATEMENTLIST)) { + stmt = SkipEmptyStatements(ListHead(stmt)); + if (!stmt || NextNonEmptyStatement(stmt)) + return m.fail(stmt, "expected single return statement"); + } + + if (!stmt->isKind(PNK_RETURN)) + return m.fail(stmt, "expected return statement"); + + ParseNode *returnExpr = ReturnExpr(stmt); + if (!returnExpr || !returnExpr->isKind(retval ? PNK_TRUE : PNK_FALSE)) + return m.failf(stmt, "expected 'return %s;'", retval ? "true" : "false"); + + return true; +} + +static bool +CheckReassignmentTo(ModuleCompiler &m, ParseNode *stmt, PropertyName *lhsName, ParseNode **rhs) +{ + if (!stmt || !stmt->isKind(PNK_SEMI)) + return m.fail(stmt, "missing reassignment"); + + ParseNode *assign = UnaryKid(stmt); + if (!assign || !assign->isKind(PNK_ASSIGN)) + return m.fail(stmt, "missing reassignment"); + + ParseNode *lhs = BinaryLeft(assign); + if (!IsUseOfName(lhs, lhsName)) + return m.failName(lhs, "expecting reassignment of %s", lhsName); + + *rhs = BinaryRight(assign); + return true; +} + +static bool +CheckChangeHeap(ModuleCompiler &m, ParseNode *fn, bool *validated) +{ + MOZ_ASSERT(fn->isKind(PNK_FUNCTION)); + + // We don't yet know whether this is a change-heap function. + // The point at which we know we have a change-heap function is once we see + // whether the argument is coerced according to the normal asm.js rules. If + // it is coerced, it's not change-heap and must validate according to normal + // rules; otherwise it must validate as a change-heap function. + *validated = false; + + PropertyName *changeHeapName = FunctionName(fn); + if (!CheckModuleLevelName(m, fn, changeHeapName)) + return false; + + unsigned numFormals; + ParseNode *arg = FunctionArgsList(fn, &numFormals); + if (numFormals != 1) + return true; + + PropertyName *newBufferName; + if (!CheckArgument(m, arg, &newBufferName)) + return false; + + ParseNode *stmtIter = SkipEmptyStatements(ListHead(FunctionStatementList(fn))); + if (!stmtIter || !stmtIter->isKind(PNK_IF)) + return true; + + // We can now issue validation failures if we see something that isn't a + // valid change-heap function. + *validated = true; + + PropertyName *bufferName = m.module().bufferArgumentName(); + if (!bufferName) + return m.fail(fn, "to change heaps, the module must have a buffer argument"); + + ParseNode *cond = TernaryKid1(stmtIter); + ParseNode *thenStmt = TernaryKid2(stmtIter); + if (ParseNode *elseStmt = TernaryKid3(stmtIter)) + return m.fail(elseStmt, "unexpected else statement"); + + uint32_t mask, min; + if (!CheckHeapLengthCondition(m, cond, newBufferName, &mask, &min)) + return false; + + if (!CheckReturnBoolLiteral(m, thenStmt, false)) + return false; + + stmtIter = NextNonEmptyStatement(stmtIter); + + for (unsigned i = 0; i < m.numArrayViews(); i++, stmtIter = NextNonEmptyStatement(stmtIter)) { + const ModuleCompiler::ArrayView &view = m.arrayView(i); + + ParseNode *rhs; + if (!CheckReassignmentTo(m, stmtIter, view.name, &rhs)) + return false; + + if (!rhs->isKind(PNK_NEW)) + return m.failName(rhs, "expecting assignment of new array view to %s", view.name); + + ParseNode *ctorExpr = ListHead(rhs); + if (!ctorExpr->isKind(PNK_NAME)) + return m.fail(rhs, "expecting name of imported typed array constructor"); + + const ModuleCompiler::Global *global = m.lookupGlobal(ctorExpr->name()); + if (!global || global->which() != ModuleCompiler::Global::ArrayViewCtor) + return m.fail(rhs, "expecting name of imported typed array constructor"); + if (global->viewType() != view.type) + return m.fail(rhs, "can't change the type of a global view variable"); + + if (!CheckNewArrayViewArgs(m, ctorExpr, newBufferName)) + return false; + } + + ParseNode *rhs; + if (!CheckReassignmentTo(m, stmtIter, bufferName, &rhs)) + return false; + if (!IsUseOfName(rhs, newBufferName)) + return m.failName(stmtIter, "expecting assignment of new buffer to %s", bufferName); + + stmtIter = NextNonEmptyStatement(stmtIter); + + if (!CheckReturnBoolLiteral(m, stmtIter, true)) + return false; + + stmtIter = NextNonEmptyStatement(stmtIter); + if (stmtIter) + return m.fail(stmtIter, "expecting end of function"); + + return m.addChangeHeap(changeHeapName, fn, mask, min); +} + static bool ParseFunction(ModuleCompiler &m, ParseNode **fnOut) { @@ -6442,6 +6752,16 @@ CheckFunction(ModuleCompiler &m, LifoAlloc &lifo, MIRGenerator **mir, ModuleComp if (!CheckFunctionHead(m, fn)) return false; + if (m.tryOnceToValidateChangeHeap()) { + bool validated; + if (!CheckChangeHeap(m, fn, &validated)) + return false; + if (validated) { + *mir = nullptr; + return true; + } + } + FunctionCompiler f(m, fn, lifo); if (!f.init()) return false; @@ -6551,6 +6871,10 @@ CheckFunctionsSequential(ModuleCompiler &m) if (!CheckFunction(m, lifo, &mir, &func)) return false; + // In the case of the change-heap function, no MIR is produced. + if (!mir) + continue; + int64_t before = PRMJ_Now(); IonContext icx(m.cx(), &mir->alloc()); @@ -6648,7 +6972,7 @@ GetFinishedCompilation(ModuleCompiler &m, ParallelGroupState &group) } static bool -GenerateCodeForFinishedJob(ModuleCompiler &m, ParallelGroupState &group, AsmJSParallelTask **outTask) +GetUsedTask(ModuleCompiler &m, ParallelGroupState &group, AsmJSParallelTask **outTask) { // Block until a used LifoAlloc becomes available. AsmJSParallelTask *task = GetFinishedCompilation(m, group); @@ -6700,10 +7024,9 @@ CheckFunctionsParallel(ModuleCompiler &m, ParallelGroupState &group) #endif HelperThreadState().resetAsmJSFailureState(); + AsmJSParallelTask *task = nullptr; for (unsigned i = 0; PeekToken(m.parser()) == TOK_FUNCTION; i++) { - // Get exclusive access to an empty LifoAlloc from the thread group's pool. - AsmJSParallelTask *task = nullptr; - if (!GetUnusedTask(group, i, &task) && !GenerateCodeForFinishedJob(m, group, &task)) + if (!task && !GetUnusedTask(group, i, &task) && !GetUsedTask(m, group, &task)) return false; // Generate MIR into the LifoAlloc on the main thread. @@ -6712,18 +7035,23 @@ CheckFunctionsParallel(ModuleCompiler &m, ParallelGroupState &group) if (!CheckFunction(m, task->lifo, &mir, &func)) return false; + // In the case of the change-heap function, no MIR is produced. + if (!mir) + continue; + // Perform optimizations and LIR generation on a helper thread. task->init(m.cx()->compartment()->runtimeFromAnyThread(), func, mir); if (!StartOffThreadAsmJSCompile(m.cx(), task)) return false; group.outstandingJobs++; + task = nullptr; } // Block for all outstanding helpers to complete. while (group.outstandingJobs > 0) { AsmJSParallelTask *ignored = nullptr; - if (!GenerateCodeForFinishedJob(m, group, &ignored)) + if (!GetUsedTask(m, group, &ignored)) return false; } @@ -6905,18 +7233,23 @@ CheckFuncPtrTables(ModuleCompiler &m) } static bool -CheckModuleExportFunction(ModuleCompiler &m, ParseNode *returnExpr) +CheckModuleExportFunction(ModuleCompiler &m, ParseNode *pn, PropertyName *maybeFieldName = nullptr) { - if (!returnExpr->isKind(PNK_NAME)) - return m.fail(returnExpr, "export statement must be of the form 'return name'"); + if (!pn->isKind(PNK_NAME)) + return m.fail(pn, "expected name of exported function"); - PropertyName *funcName = returnExpr->name(); + PropertyName *funcName = pn->name(); + const ModuleCompiler::Global *global = m.lookupGlobal(funcName); + if (!global) + return m.failName(pn, "exported function name '%s' not found", funcName); - const ModuleCompiler::Func *func = m.lookupFunction(funcName); - if (!func) - return m.failName(returnExpr, "exported function name '%s' not found", funcName); + if (global->which() == ModuleCompiler::Global::Function) + return m.addExportedFunction(m.function(global->funcIndex()), maybeFieldName); - return m.addExportedFunction(func, /* maybeFieldName = */ nullptr); + if (global->which() == ModuleCompiler::Global::ChangeHeap) + return m.addExportedChangeHeap(funcName, *global, maybeFieldName); + + return m.failName(pn, "'%s' is not a function", funcName); } static bool @@ -6934,13 +7267,7 @@ CheckModuleExportObject(ModuleCompiler &m, ParseNode *object) if (!initNode->isKind(PNK_NAME)) return m.fail(initNode, "initializer of exported object literal must be name of function"); - PropertyName *funcName = initNode->name(); - - const ModuleCompiler::Func *func = m.lookupFunction(funcName); - if (!func) - return m.failName(initNode, "exported function name '%s' not found", funcName); - - if (!m.addExportedFunction(func, fieldName)) + if (!CheckModuleExportFunction(m, initNode, fieldName)) return false; } @@ -7075,10 +7402,9 @@ GenerateEntry(ModuleCompiler &m, unsigned exportIndex) #endif // ARM, MIPS and x64 have a globally-pinned HeapReg (x86 uses immediates in - // effective addresses). -#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) - masm.loadPtr(Address(IntArgReg1, AsmJSModule::heapGlobalDataOffset()), HeapReg); -#endif + // effective addresses). Loading the heap register depends on the global + // register already having been loaded. + masm.loadAsmJSHeapRegisterFromGlobalData(); // Put the 'argv' argument into a non-argument/return register so that we // can use 'argv' while we fill in the arguments for the asm.js callee. @@ -7338,6 +7664,9 @@ GenerateFFIInterpExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &e MOZ_CRASH("SIMD types shouldn't be returned from a FFI"); } + // The heap pointer may have changed during the FFI, so reload it. + masm.loadAsmJSHeapRegisterFromGlobalData(); + Label profilingReturn; GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::SlowFFI, &profilingReturn); return m.finishGeneratingInterpExit(exitIndex, &begin, &profilingReturn) && !masm.oom(); @@ -7345,12 +7674,16 @@ GenerateFFIInterpExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &e // On ARM/MIPS, we need to include an extra word of space at the top of the // stack so we can explicitly store the return address before making the call -// to C++ or Ion. On x86/x64, this isn't necessary since the call instruction -// pushes the return address. +// to C++ or Ion and an extra word to store the pinned global-data register. On +// x86/x64, neither is necessary since the call instruction pushes the return +// address and global data is reachable via immediate or rip-relative +// addressing. #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) static const unsigned MaybeRetAddr = sizeof(void*); +static const unsigned MaybeSavedGlobalReg = sizeof(void*); #else static const unsigned MaybeRetAddr = 0; +static const unsigned MaybeSavedGlobalReg = 0; #endif static bool @@ -7359,27 +7692,16 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit { MacroAssembler &masm = m.masm(); - // Even though the caller has saved volatile registers, we still need to - // save/restore globally-pinned asm.js registers at Ion calls since Ion does - // not preserve non-volatile registers. -#if defined(JS_CODEGEN_X64) - unsigned savedRegBytes = 1 * sizeof(void*); // HeapReg -#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) - unsigned savedRegBytes = 2 * sizeof(void*); // HeapReg, GlobalReg -#else - unsigned savedRegBytes = 0; -#endif - // The same stack frame is used for the call into Ion and (possibly) a call // into C++ to coerce the return type. To do this, we compute the amount of // space required for both calls and take the maximum. In both cases, - // include space for savedRegBytes, since these go below the Ion/coerce. + // include space for MaybeSavedGlobalReg, since this goes below the Ion/coerce. // Ion calls use the following stack layout (sp grows to the left): // | return address | descriptor | callee | argc | this | arg1 | arg2 | ... unsigned offsetToIonArgs = MaybeRetAddr; unsigned ionArgBytes = 3 * sizeof(size_t) + (1 + exit.sig().args().length()) * sizeof(Value); - unsigned totalIonBytes = offsetToIonArgs + ionArgBytes + savedRegBytes; + unsigned totalIonBytes = offsetToIonArgs + ionArgBytes + MaybeSavedGlobalReg; unsigned ionFrameSize = StackDecrementForCall(masm, AsmJSStackAlignment, totalIonBytes); // Coercion calls use the following stack layout (sp grows to the left): @@ -7388,7 +7710,7 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit MIRTypeVector coerceArgTypes(m.cx()); coerceArgTypes.infallibleAppend(MIRType_Pointer); // argv unsigned offsetToCoerceArgv = AlignBytes(StackArgBytes(coerceArgTypes), sizeof(double)); - unsigned totalCoerceBytes = offsetToCoerceArgv + sizeof(Value) + savedRegBytes; + unsigned totalCoerceBytes = offsetToCoerceArgv + sizeof(Value) + MaybeSavedGlobalReg; unsigned coerceFrameSize = StackDecrementForCall(masm, AsmJSStackAlignment, totalCoerceBytes); unsigned framePushed = Max(ionFrameSize, coerceFrameSize); @@ -7442,15 +7764,17 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit argOffset += exit.sig().args().length() * sizeof(Value); MOZ_ASSERT(argOffset == offsetToIonArgs + ionArgBytes); - // 6. Store asm.js pinned registers -#if defined(JS_CODEGEN_X64) - unsigned savedHeapOffset = framePushed - sizeof(void*); - masm.storePtr(HeapReg, Address(StackPointer, savedHeapOffset)); -#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) - unsigned savedHeapOffset = framePushed - 1 * sizeof(void*); - unsigned savedGlobalOffset = framePushed - 2 * sizeof(void*); - masm.storePtr(HeapReg, Address(StackPointer, savedHeapOffset)); - masm.storePtr(GlobalReg, Address(StackPointer, savedGlobalOffset)); + // 6. Ion will clobber all registers, even non-volatiles. GlobalReg and + // HeapReg are removed from the general register set for asm.js code, so + // these will not have been saved by the caller like all other registers, + // so they must be explicitly preserved. Only save GlobalReg since + // HeapReg must be reloaded (from global data) after the call since the + // heap may change during the FFI call. +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) + JS_STATIC_ASSERT(MaybeSavedGlobalReg > 0); + unsigned savedGlobalOffset = framePushed - MaybeSavedGlobalReg; +#else + JS_STATIC_ASSERT(MaybeSavedGlobalReg == 0); #endif { @@ -7524,14 +7848,6 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit masm.storePtr(reg2, Address(reg0, offsetOfJitJSContext)); } - MOZ_ASSERT(masm.framePushed() == framePushed); -#if defined(JS_CODEGEN_X64) - masm.loadPtr(Address(StackPointer, savedHeapOffset), HeapReg); -#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) - masm.loadPtr(Address(StackPointer, savedHeapOffset), HeapReg); - masm.loadPtr(Address(StackPointer, savedGlobalOffset), GlobalReg); -#endif - masm.branchTestMagic(Assembler::Equal, JSReturnOperand, throwLabel); Label oolConvert; @@ -7555,6 +7871,17 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit Label done; masm.bind(&done); + MOZ_ASSERT(masm.framePushed() == framePushed); + + // Reload pinned registers after all calls into arbitrary JS. +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) + JS_STATIC_ASSERT(MaybeSavedGlobalReg > 0); + masm.loadPtr(Address(StackPointer, savedGlobalOffset), GlobalReg); +#else + JS_STATIC_ASSERT(MaybeSavedGlobalReg == 0); +#endif + masm.loadAsmJSHeapRegisterFromGlobalData(); + Label profilingReturn; GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::IonFFI, &profilingReturn); @@ -7767,6 +8094,7 @@ GenerateAsyncInterruptExit(ModuleCompiler &m, Label *throwLabel) // Restore the machine state to before the interrupt. masm.PopRegsInMask(AllRegsExceptSP, AllRegsExceptSP.fpus()); // restore all GP/FP registers (except SP) + masm.loadAsmJSHeapRegisterFromGlobalData(); // In case there was a changeHeap masm.popFlags(); // after this, nothing that sets conditions masm.ret(); // pop resumePC into PC #elif defined(JS_CODEGEN_MIPS) @@ -7806,11 +8134,9 @@ GenerateAsyncInterruptExit(ModuleCompiler &m, Label *throwLabel) // Pop resumePC into PC. Clobber HeapReg to make the jump and restore it // during jump delay slot. - MOZ_ASSERT(Imm16::IsInSignedRange(AsmJSModule::heapGlobalDataOffset() - AsmJSGlobalRegBias)); masm.pop(HeapReg); masm.as_jr(HeapReg); - masm.loadPtr(Address(GlobalReg, AsmJSModule::heapGlobalDataOffset() - AsmJSGlobalRegBias), - HeapReg); + masm.loadAsmJSHeapRegisterFromGlobalData(); // In case there was a changeHeap #elif defined(JS_CODEGEN_ARM) masm.setFramePushed(0); // set to zero so we can use masm.framePushed() below masm.PushRegsInMask(RegisterSet(GeneralRegisterSet(Registers::AllMask & ~(1<> 2] } return f'); + asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[(g()|0) >> 2] } function g() { return 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[(g()|0) >> 2] = 0 } function g() { return 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = g()|0 } function g() { return 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i32[(g()|0)>>2] >> 2] } function g() { return 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i32[(g()|0)>>2] >> 2] = 0 } function g() { return 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = i32[(g()|0)>>2] } function g() { return 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[((i32[i>>2]|0) + (g()|0)) >> 2] } function g() { return 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[((i32[i>>2]|0) + (g()|0)) >> 2] = 0 } function g() { return 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = (i32[i>>2]|0) + (g()|0) } function g() { return 0 } return f'); + +// Tests for constant heap accesses when change-heap is used + +const HEADER = USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & MASK || len(b2) <= MIN) return false; i8=new I8(b2); b=b2; return true } '; +assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { i8[0x1000000] = 0 } return f'); + asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { i8[0xffffff] = 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { i8[0x1000001] = 0 } return f'); + asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { i8[0x1000000] = 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { return i8[0x1000000]|0 } return f'); + asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0xffffff') + 'function f() { return i8[0xffffff]|0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { return i8[0x1000001]|0 } return f'); + asmCompile('glob', 'ffis', 'b', HEADER.replace('MASK', '0xffffff').replace('MIN', '0x1000000') + 'function f() { return i8[0x1000000]|0 } return f'); + +// Tests for validation of heap length + +var body = USE_ASM + IMPORT1 + 'function ch(b2) { if(len(b2) & MASK || len(b2) <= MIN) return false; i8=new I8(b2); b=b2; return true } function f() { return 42 } return ch'; +var m = asmCompile('glob', 'ffis', 'b', body.replace('MASK', '0xffffff').replace('MIN', '0x1ffffff')); +assertAsmLinkFail(m, this, null, new ArrayBuffer(BUF_CHANGE_MIN)); +assertAsmLinkFail(m, this, null, new ArrayBuffer(0x1000000)); +var changeHeap = asmLink(m, this, null, new ArrayBuffer(0x2000000)); +assertEq(changeHeap(new ArrayBuffer(0x1000000)), false); +assertEq(changeHeap(new ArrayBuffer(0x2000000)), true); +assertEq(changeHeap(new ArrayBuffer(0x2000001)), false); +assertThrowsInstanceOf(() => changeHeap(null), TypeError); +assertThrowsInstanceOf(() => changeHeap({}), TypeError); +assertThrowsInstanceOf(() => changeHeap(new Int32Array(100)), TypeError); + +var detached = new ArrayBuffer(BUF_CHANGE_MIN); +neuter(detached, "change-data"); +assertEq(changeHeap(detached), false); + +// Tests for runtime changing heap + +const CHANGE_HEAP = 'var changeHeap = glob.byteLength;'; + +var changeHeapSource = `function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true }`; +var body = `var I32=glob.Int32Array; var i32=new I32(b); + var len=glob.byteLength;` + + changeHeapSource + + `function get(i) { i=i|0; return i32[i>>2]|0 } + function set(i, v) { i=i|0; v=v|0; i32[i>>2] = v } + return {get:get, set:set, changeHeap:ch}`; + +var m = asmCompile('glob', 'ffis', 'b', USE_ASM + body); +var buf1 = new ArrayBuffer(BUF_CHANGE_MIN); +var {get, set, changeHeap} = asmLink(m, this, null, buf1); + +assertEq(m.toString(), "function anonymous(glob, ffis, b) {\n" + USE_ASM + body + "\n}"); +assertEq(m.toSource(), "(function anonymous(glob, ffis, b) {\n" + USE_ASM + body + "\n})"); +assertEq(changeHeap.toString(), changeHeapSource); +assertEq(changeHeap.toSource(), changeHeapSource); + +set(0, 42); +set(4, 13); +assertEq(get(0), 42); +assertEq(get(4), 13); +set(BUF_CHANGE_MIN, 262); +assertEq(get(BUF_CHANGE_MIN), 0); +var buf2 = new ArrayBuffer(2*BUF_CHANGE_MIN); +assertEq(changeHeap(buf2), true); +assertEq(get(0), 0); +assertEq(get(4), 0); +set(BUF_CHANGE_MIN, 262); +assertEq(get(BUF_CHANGE_MIN), 262); +changeHeap(buf1); +assertEq(get(0), 42); +assertEq(get(4), 13); +set(BUF_CHANGE_MIN, 262); +assertEq(get(BUF_CHANGE_MIN), 0); + +var buf1 = new ArrayBuffer(BUF_CHANGE_MIN); +new Int32Array(buf1)[0] = 13; +var buf2 = new ArrayBuffer(BUF_CHANGE_MIN); +new Int32Array(buf2)[0] = 42; + +var m = asmCompile('glob', 'ffis', 'b', USE_ASM + + `var len=glob.byteLength; + function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; b=b2; return true } + return ch`); +var changeHeap = asmLink(m, this, null, buf1); +changeHeap(buf2); + +// Tests for changing heap during an FFI: + +// Set the warmup to '2' so we can hit both interp and ion FFI exits +setJitCompilerOption("ion.warmup.trigger", 2); +setJitCompilerOption("baseline.warmup.trigger", 0); +setJitCompilerOption("offthread-compilation.enable", 0); + +var changeToBuf = null; +var m = asmCompile('glob', 'ffis', 'b', USE_ASM + + `var ffi=ffis.ffi; + var I32=glob.Int32Array; var i32=new I32(b); + var len=glob.byteLength; + function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true } + function test(i) { i=i|0; var sum=0; sum = i32[i>>2]|0; sum = (sum + (ffi()|0))|0; sum = (sum + (i32[i>>2]|0))|0; return sum|0 } + return {test:test, changeHeap:ch}`); +var ffi = function() { changeHeap(changeToBuf); return 1 } +var {test, changeHeap} = asmLink(m, this, {ffi:ffi}, buf1); +changeToBuf = buf1; +assertEq(test(0), 27); +changeToBuf = buf2; +assertEq(test(0), 56); +changeToBuf = buf2; +assertEq(test(0), 85); +changeToBuf = buf1; +assertEq(test(0), 56); +changeToBuf = buf1; +assertEq(test(0), 27); + +var ffi = function() { return { valueOf:function() { changeHeap(changeToBuf); return 100 } } }; +var {test, changeHeap} = asmLink(m, this, {ffi:ffi}, buf1); +changeToBuf = buf1; +assertEq(test(0), 126); +changeToBuf = buf2; +assertEq(test(0), 155); +changeToBuf = buf2; +assertEq(test(0), 184); +changeToBuf = buf1; +assertEq(test(0), 155); +changeToBuf = buf1; +assertEq(test(0), 126); diff --git a/js/src/jit-test/tests/asm.js/testTimeout7.js b/js/src/jit-test/tests/asm.js/testTimeout7.js new file mode 100644 index 000000000000..aa7411def744 --- /dev/null +++ b/js/src/jit-test/tests/asm.js/testTimeout7.js @@ -0,0 +1,41 @@ +load(libdir + "asm.js"); + +// This test may iloop for valid reasons if not compiled with asm.js (namely, +// inlining may allow the heap load to be hoisted out of the loop). +if (!isAsmJSCompilationAvailable()) + quit(); + +var byteLength = + Function.prototype.call.bind(Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength').get); + +var buf1 = new ArrayBuffer(BUF_CHANGE_MIN); +new Int32Array(buf1)[0] = 13; +var buf2 = new ArrayBuffer(BUF_CHANGE_MIN); +new Int32Array(buf2)[0] = 42; + +// Test changeHeap from interrupt (as if that could ever happen...) +var m = asmCompile('glob', 'ffis', 'b', USE_ASM + + `var I32=glob.Int32Array; var i32=new I32(b); + var len=glob.byteLength; + function changeHeap(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true } + function f() {} + function loop(i) { i=i|0; while((i32[i>>2]|0) == 13) { f() } } + return {loop:loop, changeHeap:changeHeap}`); +var { loop, changeHeap } = asmLink(m, this, null, buf1); +timeout(1, function() { changeHeap(buf2); return true }); +loop(0); +timeout(-1); + +// Try again, but this time with signals disabled +setJitCompilerOption("signals.enable", 0); +var m = asmCompile('glob', 'ffis', 'b', USE_ASM + + `var I32=glob.Int32Array; var i32=new I32(b); + var len=glob.byteLength; + function changeHeap(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff) return false; i32=new I32(b2); b=b2; return true } + function f() {} + function loop(i) { i=i|0; while((i32[i>>2]|0) == 13) { f() } } + return {loop:loop, changeHeap:changeHeap}`); +var { loop, changeHeap } = asmLink(m, this, null, buf1); +timeout(1, function() { changeHeap(buf2); return true }); +loop(0); +timeout(-1); diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index 11cb37e3011b..b29215edf9a1 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1645,6 +1645,9 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void loadAsmJSActivation(Register dest) { loadPtr(Address(GlobalReg, AsmJSActivationGlobalDataOffset - AsmJSGlobalRegBias), dest); } + void loadAsmJSHeapRegisterFromGlobalData() { + loadPtr(Address(GlobalReg, AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg); + } }; typedef MacroAssemblerARMCompat MacroAssemblerSpecific; diff --git a/js/src/jit/mips/MacroAssembler-mips.h b/js/src/jit/mips/MacroAssembler-mips.h index 5ce2d3ee8c0d..27a0ccc50791 100644 --- a/js/src/jit/mips/MacroAssembler-mips.h +++ b/js/src/jit/mips/MacroAssembler-mips.h @@ -1304,6 +1304,10 @@ public: void loadAsmJSActivation(Register dest) { loadPtr(Address(GlobalReg, AsmJSActivationGlobalDataOffset - AsmJSGlobalRegBias), dest); } + void loadAsmJSHeapRegisterFromGlobalData() { + MOZ_ASSERT(Imm16::IsInSignedRange(AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias)); + loadPtr(Address(GlobalReg, AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg); + } }; typedef MacroAssemblerMIPSCompat MacroAssemblerSpecific; diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index 62f11f68ec30..d2a495e55d13 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -662,6 +662,7 @@ static const uint32_t AsmJSFrameBytesAfterReturnAddress = sizeof(void*); // A hoisting of constants that would otherwise require #including AsmJSModule.h // everywhere. Values are asserted in AsmJSModule.h. static const unsigned AsmJSActivationGlobalDataOffset = 0; +static const unsigned AsmJSHeapGlobalDataOffset = sizeof(void*); static const unsigned AsmJSNaN64GlobalDataOffset = 2 * sizeof(void*); static const unsigned AsmJSNaN32GlobalDataOffset = 2 * sizeof(void*) + sizeof(double); diff --git a/js/src/jit/x64/Assembler-x64.h b/js/src/jit/x64/Assembler-x64.h index b9720cc0f327..eececce658c0 100644 --- a/js/src/jit/x64/Assembler-x64.h +++ b/js/src/jit/x64/Assembler-x64.h @@ -634,6 +634,10 @@ class Assembler : public AssemblerX86Shared CodeOffsetLabel label = loadRipRelativeInt64(dest); append(AsmJSGlobalAccess(label, AsmJSActivationGlobalDataOffset)); } + void loadAsmJSHeapRegisterFromGlobalData() { + CodeOffsetLabel label = loadRipRelativeInt64(HeapReg); + append(AsmJSGlobalAccess(label, AsmJSHeapGlobalDataOffset)); + } // The below cmpq methods switch the lhs and rhs when it invokes the // macroassembler to conform with intel standard. When calling this diff --git a/js/src/jit/x64/CodeGenerator-x64.cpp b/js/src/jit/x64/CodeGenerator-x64.cpp index 58429139705d..ceefd9c81b33 100644 --- a/js/src/jit/x64/CodeGenerator-x64.cpp +++ b/js/src/jit/x64/CodeGenerator-x64.cpp @@ -244,6 +244,17 @@ bool CodeGeneratorX64::visitAsmJSCall(LAsmJSCall *ins) { emitAsmJSCall(ins); + +#ifdef DEBUG + Register scratch = ABIArgGenerator::NonReturn_VolatileReg0; + masm.movePtr(HeapReg, scratch); + masm.loadAsmJSHeapRegisterFromGlobalData(); + Label ok; + masm.branchPtr(Assembler::Equal, HeapReg, scratch, &ok); + masm.breakpoint(); + masm.bind(&ok); +#endif + return true; } diff --git a/js/src/jit/x86/Assembler-x86.h b/js/src/jit/x86/Assembler-x86.h index 6396a3c6cc4d..3b274225f140 100644 --- a/js/src/jit/x86/Assembler-x86.h +++ b/js/src/jit/x86/Assembler-x86.h @@ -572,6 +572,9 @@ class Assembler : public AssemblerX86Shared CodeOffsetLabel label = movlWithPatch(PatchedAbsoluteAddress(), dest); append(AsmJSGlobalAccess(label, AsmJSActivationGlobalDataOffset)); } + void loadAsmJSHeapRegisterFromGlobalData() { + // x86 doesn't have a pinned heap register. + } static bool canUseInSingleByteInstruction(Register reg) { return !ByteRegRequiresRex(reg.code()); From 9a32e7eeeb98c08946c987187656c5d923d95560 Mon Sep 17 00:00:00 2001 From: Trevor Saunders Date: Tue, 2 Sep 2014 14:54:04 -0400 Subject: [PATCH 09/77] bug 1041070 - fix O(N^2) runtime of tree update r=surkov --- accessible/generic/Accessible-inl.h | 8 +++++ accessible/generic/Accessible.cpp | 23 ++++++++++-- accessible/generic/Accessible.h | 42 ++++++++++++++++++---- accessible/generic/DocAccessible.cpp | 20 +++++++++-- accessible/html/HTMLImageMapAccessible.cpp | 13 ++++--- 5 files changed, 90 insertions(+), 16 deletions(-) diff --git a/accessible/generic/Accessible-inl.h b/accessible/generic/Accessible-inl.h index cfe4eece79f3..ec92e9e6f2f3 100644 --- a/accessible/generic/Accessible-inl.h +++ b/accessible/generic/Accessible-inl.h @@ -67,6 +67,14 @@ Accessible::ScrollTo(uint32_t aHow) const nsCoreUtils::ScrollTo(mDoc->PresShell(), mContent, aHow); } +inline bool +Accessible::UpdateChildren() +{ + AutoTreeMutation mut(this); + InvalidateChildren(); + return EnsureChildren(); +} + } // namespace a11y } // namespace mozilla diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp index 94ab78929fde..4319a035973b 100644 --- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -1936,7 +1936,9 @@ Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent) mParent = aParent; mIndexInParent = aIndexInParent; - mParent->InvalidateChildrenGroupInfo(); +#ifdef DEBUG + AssertInMutatingSubtree(); +#endif // Note: this is currently only used for richlistitems and their children. if (mParent->HasNameDependentParent() || mParent->IsXULListItem()) @@ -1949,7 +1951,9 @@ Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent) void Accessible::UnbindFromParent() { - mParent->InvalidateChildrenGroupInfo(); +#ifdef DEBUG + AssertInMutatingSubtree(); +#endif mParent = nullptr; mIndexInParent = -1; mIndexOfEmbeddedChild = -1; @@ -2658,6 +2662,21 @@ Accessible::StaticAsserts() const "Accessible::mGenericType was oversized by eLastAccGenericType!"); } +void +Accessible::AssertInMutatingSubtree() const +{ + if (IsDoc() || IsApplication()) + return; + + const Accessible *acc = this; + while (!acc->IsDoc() && !(acc->mStateFlags & eSubtreeMutating)) { + acc = acc->Parent(); + if (!acc) + return; + } + + MOZ_ASSERT(acc->mStateFlags & eSubtreeMutating); +} //////////////////////////////////////////////////////////////////////////////// // KeyBinding class diff --git a/accessible/generic/Accessible.h b/accessible/generic/Accessible.h index c53b2df4fd3c..13bd11a0cf63 100644 --- a/accessible/generic/Accessible.h +++ b/accessible/generic/Accessible.h @@ -373,11 +373,7 @@ public: /** * Update the children cache. */ - inline bool UpdateChildren() - { - InvalidateChildren(); - return EnsureChildren(); - } + bool UpdateChildren(); /** * Cache children if necessary. Return true if the accessible is defunct. @@ -948,7 +944,8 @@ protected: eNotNodeMapEntry = 1 << 3, // accessible shouldn't be in document node map eHasNumericValue = 1 << 4, // accessible has a numeric value eGroupInfoDirty = 1 << 5, // accessible needs to update group info - eIgnoreDOMUIEvent = 1 << 6, // don't process DOM UI events for a11y events + eSubtreeMutating = 1 << 6, // subtree is being mutated + eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events eLastStateFlag = eIgnoreDOMUIEvent }; @@ -1064,7 +1061,7 @@ protected: int32_t mIndexInParent; static const uint8_t kChildrenFlagsBits = 2; - static const uint8_t kStateFlagsBits = 7; + static const uint8_t kStateFlagsBits = 8; static const uint8_t kContextFlagsBits = 1; static const uint8_t kTypeBits = 6; static const uint8_t kGenericTypesBits = 13; @@ -1079,9 +1076,11 @@ protected: uint32_t mGenericTypes : kGenericTypesBits; void StaticAsserts() const; + void AssertInMutatingSubtree() const; friend class DocAccessible; friend class xpcAccessible; + friend class AutoTreeMutation; nsAutoPtr mEmbeddedObjCollector; int32_t mIndexOfEmbeddedChild; @@ -1165,6 +1164,35 @@ private: uint32_t mModifierMask; }; +/** + * This class makes sure required tasks are done before and after tree + * mutations. Currently this only includes group info invalidation. You must + * have an object of this class on the stack when calling methods that mutate + * the accessible tree. + */ +class AutoTreeMutation +{ +public: + AutoTreeMutation(Accessible* aRoot, bool aInvalidationRequired = true) : + mInvalidationRequired(aInvalidationRequired), mRoot(aRoot) + { + MOZ_ASSERT(!(mRoot->mStateFlags & Accessible::eSubtreeMutating)); + mRoot->mStateFlags |= Accessible::eSubtreeMutating; + } + ~AutoTreeMutation() + { + if (mInvalidationRequired) + mRoot->InvalidateChildrenGroupInfo(); + + MOZ_ASSERT(mRoot->mStateFlags & Accessible::eSubtreeMutating); + mRoot->mStateFlags &= ~Accessible::eSubtreeMutating; + } + + bool mInvalidationRequired; +private: + Accessible* mRoot; +}; + } // namespace a11y } // namespace mozilla diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp index 8e1a02cfe52f..bd63bf17ec95 100644 --- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -483,7 +483,13 @@ DocAccessible::Shutdown() mDependentIDsHash.Clear(); mNodeToAccessibleMap.Clear(); - ClearCache(mAccessibleCache); + + { + // We're about to get rid of all of our children so there won't be anything + // to invalidate. + AutoTreeMutation mut(this, false); + ClearCache(mAccessibleCache); + } HyperTextAccessibleWrap::Shutdown(); @@ -1318,8 +1324,10 @@ DocAccessible::ProcessInvalidationList() } // Make sure the subtree is created. - if (accessible) + if (accessible) { + AutoTreeMutation mut(accessible); CacheChildrenInSubtree(accessible); + } } mInvalidationList.Clear(); @@ -1425,7 +1433,9 @@ DocAccessible::DoInitialUpdate() SetRoleMapEntry(aria::GetRoleMap(mContent)); } - // Build initial tree. + // Build initial tree. Since its the initial tree there's no group info to + // invalidate. + AutoTreeMutation mut(this, false); CacheChildrenInSubtree(this); // Fire reorder event after the document tree is constructed. Note, since @@ -1659,6 +1669,9 @@ DocAccessible::ProcessContentInserted(Accessible* aContainer, // accessibles into accessible tree. We need to invalidate children even // there's no inserted accessibles in the end because accessible children // are created while parent recaches child accessibles. + // XXX Group invalidation here may be redundant with invalidation in + // UpdateTree. + AutoTreeMutation mut(aContainer); aContainer->InvalidateChildren(); CacheChildrenInSubtree(aContainer); } @@ -1691,6 +1704,7 @@ DocAccessible::UpdateTree(Accessible* aContainer, nsIContent* aChildNode, #endif nsRefPtr reorderEvent = new AccReorderEvent(aContainer); + AutoTreeMutation mut(aContainer); if (child) { updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent); diff --git a/accessible/html/HTMLImageMapAccessible.cpp b/accessible/html/HTMLImageMapAccessible.cpp index c94d05cf942c..9b04f39ae106 100644 --- a/accessible/html/HTMLImageMapAccessible.cpp +++ b/accessible/html/HTMLImageMapAccessible.cpp @@ -85,7 +85,8 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents) if (!imageMapObj) return; - bool doReorderEvent = false; + bool treeChanged = false; + AutoTreeMutation mut(this); nsRefPtr reorderEvent = new AccReorderEvent(this); // Remove areas that are not a valid part of the image map anymore. @@ -98,10 +99,10 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents) nsRefPtr event = new AccHideEvent(area, area->GetContent()); mDoc->FireDelayedEvent(event); reorderEvent->AddSubMutationEvent(event); - doReorderEvent = true; } RemoveChild(area); + treeChanged = true; } // Insert new areas into the tree. @@ -123,14 +124,18 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents) nsRefPtr event = new AccShowEvent(area, areaContent); mDoc->FireDelayedEvent(event); reorderEvent->AddSubMutationEvent(event); - doReorderEvent = true; } + + treeChanged = true; } } // Fire reorder event if needed. - if (doReorderEvent) + if (treeChanged && aDoFireEvents) mDoc->FireDelayedEvent(reorderEvent); + + if (!treeChanged) + mut.mInvalidationRequired = false; } Accessible* From 21acdc6b04fc6ed00911d5230b1d6bf2ec290acb Mon Sep 17 00:00:00 2001 From: Ben Turner Date: Tue, 7 Oct 2014 12:26:59 -0700 Subject: [PATCH 10/77] Bug 1075302 - Allow Necko to do main thread I/O on remote blobs, r=khuey. --- dom/indexedDB/test/mochitest.ini | 2 + .../test/test_blob_worker_xhr_post.html | 113 ++++++++++++++++++ dom/ipc/Blob.cpp | 103 +++++++++++++--- dom/ipc/BlobParent.h | 3 + dom/ipc/DOMTypes.ipdlh | 3 - ipc/glue/InputStreamParams.ipdlh | 5 +- ipc/glue/InputStreamUtils.cpp | 16 +-- 7 files changed, 215 insertions(+), 30 deletions(-) create mode 100644 dom/indexedDB/test/test_blob_worker_xhr_post.html diff --git a/dom/indexedDB/test/mochitest.ini b/dom/indexedDB/test/mochitest.ini index 77aa44516280..17fffaccf578 100644 --- a/dom/indexedDB/test/mochitest.ini +++ b/dom/indexedDB/test/mochitest.ini @@ -115,6 +115,8 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 [test_blob_worker_crash.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 +[test_blob_worker_xhr_post.html] +skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 [test_blocked_order.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 [test_bug937006.html] diff --git a/dom/indexedDB/test/test_blob_worker_xhr_post.html b/dom/indexedDB/test/test_blob_worker_xhr_post.html new file mode 100644 index 000000000000..a421c4256cef --- /dev/null +++ b/dom/indexedDB/test/test_blob_worker_xhr_post.html @@ -0,0 +1,113 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + diff --git a/dom/ipc/Blob.cpp b/dom/ipc/Blob.cpp index 24b85aef8daf..d57fb01f8043 100644 --- a/dom/ipc/Blob.cpp +++ b/dom/ipc/Blob.cpp @@ -359,17 +359,19 @@ public: Serialize(InputStreamParams& aParams, FileDescriptorArray& /* aFileDescriptors */) { + MOZ_RELEASE_ASSERT(mBlobImpl); + nsCOMPtr remote = do_QueryInterface(mBlobImpl); MOZ_ASSERT(remote); - MOZ_ASSERT(remote->GetBlobChild()); - aParams = RemoteInputStreamParams( - nullptr /* sourceParent */, - remote->GetBlobChild() /* sourceChild */); + BlobChild* actor = remote->GetBlobChild(); + MOZ_ASSERT(actor); + + aParams = RemoteInputStreamParams(actor->ParentID()); } bool - Deserialize(const InputStreamParams& aParams, + Deserialize(const InputStreamParams& /* aParams */, const FileDescriptorArray& /* aFileDescriptors */) { // See InputStreamUtils.cpp to see how deserialization of a @@ -419,15 +421,48 @@ public: NS_IMETHOD Available(uint64_t* aAvailable) MOZ_OVERRIDE { - // See large comment in FileInputStreamWrapper::Available. - if (IsOnOwningThread()) { + if (!IsOnOwningThread()) { + nsresult rv = BlockAndWaitForStream(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mStream->Available(aAvailable); + NS_ENSURE_SUCCESS(rv, rv); + } + +#ifdef DEBUG + if (NS_IsMainThread()) { + NS_WARNING("Someone is trying to do main-thread I/O..."); + } +#endif + + nsresult rv; + + // See if we already have our real stream. + nsCOMPtr inputStream; + { + MonitorAutoLock lock(mMonitor); + + inputStream = mStream; + } + + // If we do then just call through. + if (inputStream) { + rv = inputStream->Available(aAvailable); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + + // If the stream is already closed then we can't do anything. + if (!mBlobImpl) { return NS_BASE_STREAM_CLOSED; } - nsresult rv = BlockAndWaitForStream(); - NS_ENSURE_SUCCESS(rv, rv); + // Otherwise fake it... + NS_WARNING("Available() called before real stream has been delivered, " + "guessing the amount of data available!"); - rv = mStream->Available(aAvailable); + rv = mBlobImpl->GetSize(aAvailable); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; @@ -690,7 +725,8 @@ public: aProcessID, aBlobImpl, /* aMayCreate */ true, - /* aMayGet */ false); + /* aMayGet */ false, + /* aIgnoreProcessID */ false); } static already_AddRefed @@ -700,7 +736,19 @@ public: aProcessID, nullptr, /* aMayCreate */ false, - /* aMayGet */ true); + /* aMayGet */ true, + /* aIgnoreProcessID */ false); + } + + static already_AddRefed + Get(const nsID& aID) + { + return GetOrCreateInternal(aID, + 0, + nullptr, + /* aMayCreate */ false, + /* aMayGet */ true, + /* aIgnoreProcessID */ true); } static already_AddRefed @@ -716,7 +764,8 @@ public: aProcessID, aBlobImpl, /* aMayCreate */ true, - /* aMayGet */ true); + /* aMayGet */ true, + /* aIgnoreProcessID */ false); } const nsID& @@ -748,7 +797,8 @@ private: intptr_t aProcessID, DOMFileImpl* aBlobImpl, bool aMayCreate, - bool aMayGet); + bool aMayGet, + bool aIgnoreProcessID); }; // Each instance of this class will be dispatched to the network stream thread @@ -2917,6 +2967,26 @@ BlobParent::Create(PBackgroundParent* aManager, return CreateFromParams(aManager, aParams); } +// static +already_AddRefed +BlobParent::GetBlobImplForID(const nsID& aID) +{ + if (NS_WARN_IF(gProcessType != GeckoProcessType_Default)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + nsRefPtr idTableEntry = IDTableEntry::Get(aID); + if (NS_WARN_IF(!idTableEntry)) { + return nullptr; + } + + nsRefPtr blobImpl = idTableEntry->BlobImpl(); + MOZ_ASSERT(blobImpl); + + return blobImpl.forget(); +} + // static template BlobParent* @@ -3550,7 +3620,8 @@ IDTableEntry::GetOrCreateInternal(const nsID& aID, intptr_t aProcessID, DOMFileImpl* aBlobImpl, bool aMayCreate, - bool aMayGet) + bool aMayGet, + bool aIgnoreProcessID) { MOZ_ASSERT(gProcessType == GeckoProcessType_Default); MOZ_ASSERT(sIDTableMutex); @@ -3578,7 +3649,7 @@ IDTableEntry::GetOrCreateInternal(const nsID& aID, return nullptr; } - if (NS_WARN_IF(entry->mProcessID != aProcessID)) { + if (!aIgnoreProcessID && NS_WARN_IF(entry->mProcessID != aProcessID)) { return nullptr; } } else { diff --git a/dom/ipc/BlobParent.h b/dom/ipc/BlobParent.h index 1d19d0f45a62..922ca1b96fbf 100644 --- a/dom/ipc/BlobParent.h +++ b/dom/ipc/BlobParent.h @@ -107,6 +107,9 @@ public: delete static_cast(aActor); } + static already_AddRefed + GetBlobImplForID(const nsID& aID); + bool HasManager() const { diff --git a/dom/ipc/DOMTypes.ipdlh b/dom/ipc/DOMTypes.ipdlh index 1a5660acf6e9..ede223cb7a03 100644 --- a/dom/ipc/DOMTypes.ipdlh +++ b/dom/ipc/DOMTypes.ipdlh @@ -7,9 +7,6 @@ include protocol PBlob; include InputStreamParams; -using struct nsID - from "nsID.h"; - using struct mozilla::void_t from "ipc/IPCMessageUtils.h"; diff --git a/ipc/glue/InputStreamParams.ipdlh b/ipc/glue/InputStreamParams.ipdlh index 2f693dd42f71..936d0228bcde 100644 --- a/ipc/glue/InputStreamParams.ipdlh +++ b/ipc/glue/InputStreamParams.ipdlh @@ -7,6 +7,9 @@ using struct mozilla::void_t from "ipc/IPCMessageUtils.h"; include protocol PBlob; include protocol PFileDescriptorSet; +using struct nsID + from "nsID.h"; + namespace mozilla { namespace ipc { @@ -39,7 +42,7 @@ struct MultiplexInputStreamParams struct RemoteInputStreamParams { - PBlob remoteBlob; + nsID id; }; // XXX This may only be used for same-process inter-thread communication! The diff --git a/ipc/glue/InputStreamUtils.cpp b/ipc/glue/InputStreamUtils.cpp index b7662cbf1f29..4c0c5180eaec 100644 --- a/ipc/glue/InputStreamUtils.cpp +++ b/ipc/glue/InputStreamUtils.cpp @@ -107,18 +107,14 @@ DeserializeInputStream(const InputStreamParams& aParams, // When the input stream already exists in this process, all we need to do // is retrieve the original instead of sending any data over the wire. case InputStreamParams::TRemoteInputStreamParams: { - const RemoteInputStreamParams& params = - aParams.get_RemoteInputStreamParams(); - - nsRefPtr blobImpl; - if (params.remoteBlobParent()) { - blobImpl = - static_cast(params.remoteBlobParent())->GetBlobImpl(); - } else { - blobImpl = - static_cast(params.remoteBlobChild())->GetBlobImpl(); + if (NS_WARN_IF(XRE_GetProcessType() != GeckoProcessType_Default)) { + return nullptr; } + const nsID& id = aParams.get_RemoteInputStreamParams().id(); + + nsRefPtr blobImpl = BlobParent::GetBlobImplForID(id); + MOZ_ASSERT(blobImpl, "Invalid blob contents"); // If fetching the internal stream fails, we ignore it and return a From ee4122c03aa3c52a68cc5ab5b854cb0f1c6e81b4 Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Wed, 24 Sep 2014 09:16:53 -0400 Subject: [PATCH 11/77] Bug 1079324 - Fix some more bad implicit constructors in layout; r=dholbert --- layout/generic/MathMLTextRunFactory.cpp | 2 +- layout/generic/nsTextFrame.cpp | 2 +- layout/style/CounterStyleManager.h | 2 +- layout/style/FontFace.cpp | 4 ++-- layout/style/FontFaceSet.h | 2 +- layout/style/nsStyleStruct.h | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/layout/generic/MathMLTextRunFactory.cpp b/layout/generic/MathMLTextRunFactory.cpp index 9cf55666baeb..74f4218b16b4 100644 --- a/layout/generic/MathMLTextRunFactory.cpp +++ b/layout/generic/MathMLTextRunFactory.cpp @@ -195,7 +195,7 @@ namespace { struct MathVarMappingWrapper { const MathVarMapping* const mTable; - MathVarMappingWrapper(const MathVarMapping* aTable) : mTable(aTable) {} + explicit MathVarMappingWrapper(const MathVarMapping* aTable) : mTable(aTable) {} uint32_t operator[](size_t index) const { return mTable[index].mKey; } diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 50f5667fcf50..0082032753ce 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -126,7 +126,7 @@ namespace { struct TabwidthAdaptor { const nsTArray& mWidths; - TabwidthAdaptor(const nsTArray& aWidths) + explicit TabwidthAdaptor(const nsTArray& aWidths) : mWidths(aWidths) {} uint32_t operator[](size_t aIdx) const { return mWidths[aIdx].mOffset; diff --git a/layout/style/CounterStyleManager.h b/layout/style/CounterStyleManager.h index 53325203ff16..765616298ea5 100644 --- a/layout/style/CounterStyleManager.h +++ b/layout/style/CounterStyleManager.h @@ -107,7 +107,7 @@ protected: class AnonymousCounterStyle MOZ_FINAL : public CounterStyle { public: - AnonymousCounterStyle(const nsCSSValue::Array* aValue); + explicit AnonymousCounterStyle(const nsCSSValue::Array* aValue); virtual void GetPrefix(nsAString& aResult) MOZ_OVERRIDE; virtual void GetSuffix(nsAString& aResult) MOZ_OVERRIDE; diff --git a/layout/style/FontFace.cpp b/layout/style/FontFace.cpp index 49eb8c4d6f88..cc7c83f6d188 100644 --- a/layout/style/FontFace.cpp +++ b/layout/style/FontFace.cpp @@ -28,7 +28,7 @@ namespace dom { class FontFaceBufferSource : public gfxFontFaceBufferSource { public: - FontFaceBufferSource(FontFace* aFontFace) + explicit FontFaceBufferSource(FontFace* aFontFace) : mFontFace(aFontFace) {} virtual void TakeBuffer(uint8_t*& aBuffer, uint32_t& aLength); @@ -71,7 +71,7 @@ class FontFaceInitializer : public nsIRunnable public: NS_DECL_ISUPPORTS - FontFaceInitializer(FontFace* aFontFace) + explicit FontFaceInitializer(FontFace* aFontFace) : mFontFace(aFontFace) , mSourceBuffer(nullptr) , mSourceBufferLength(0) {} diff --git a/layout/style/FontFaceSet.h b/layout/style/FontFaceSet.h index 816104dbb82f..fce781cb7fc2 100644 --- a/layout/style/FontFaceSet.h +++ b/layout/style/FontFaceSet.h @@ -52,7 +52,7 @@ public: friend class FontFaceSet; public: - UserFontSet(FontFaceSet* aFontFaceSet) + explicit UserFontSet(FontFaceSet* aFontFaceSet) : mFontFaceSet(aFontFaceSet) { } diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index 869c9aa3527a..3bd0ed3d3382 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -2836,7 +2836,7 @@ public: ePolygon }; - nsStyleBasicShape(Type type) + explicit nsStyleBasicShape(Type type) : mType(type) { } From bd911da510327367192e28a7aeb0da0c8b8c85e9 Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Wed, 24 Sep 2014 09:16:53 -0400 Subject: [PATCH 12/77] Bug 1079320 - Fix some more bad implicit constructors in accessible; r=tbsaunde --- accessible/base/ARIAMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accessible/base/ARIAMap.cpp b/accessible/base/ARIAMap.cpp index 316e748bfba4..9b888a77351b 100644 --- a/accessible/base/ARIAMap.cpp +++ b/accessible/base/ARIAMap.cpp @@ -739,7 +739,7 @@ namespace { struct RoleComparator { const nsDependentSubstring& mRole; - RoleComparator(const nsDependentSubstring& aRole) : mRole(aRole) {} + explicit RoleComparator(const nsDependentSubstring& aRole) : mRole(aRole) {} int operator()(const nsRoleMapEntry& aEntry) const { return Compare(mRole, aEntry.ARIARoleString()); } From e11ea02957297d3947ea271ebdad52279e993259 Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Tue, 7 Oct 2014 16:14:20 -0400 Subject: [PATCH 13/77] Bug 1079331 - Simplify test_contextmenu.html by removing privateBrowsingMode.js; r=jdm --- .../base/content/test/general/mochitest.ini | 1 - .../test/general/privateBrowsingMode.js | 3 - .../test/general/test_contextmenu.html | 125 ++++++------------ 3 files changed, 39 insertions(+), 90 deletions(-) delete mode 100644 browser/base/content/test/general/privateBrowsingMode.js diff --git a/browser/base/content/test/general/mochitest.ini b/browser/base/content/test/general/mochitest.ini index 1d14eb1ccd77..52576d87b6aa 100644 --- a/browser/base/content/test/general/mochitest.ini +++ b/browser/base/content/test/general/mochitest.ini @@ -23,7 +23,6 @@ support-files = offlineEvent.cacheManifest offlineEvent.cacheManifest^headers^ offlineEvent.html - privateBrowsingMode.js subtst_contextmenu.html video.ogg diff --git a/browser/base/content/test/general/privateBrowsingMode.js b/browser/base/content/test/general/privateBrowsingMode.js deleted file mode 100644 index a624d5281f35..000000000000 --- a/browser/base/content/test/general/privateBrowsingMode.js +++ /dev/null @@ -1,3 +0,0 @@ -// This file is only present in per-window private browsing buikds. -var perWindowPrivateBrowsing = true; - diff --git a/browser/base/content/test/general/test_contextmenu.html b/browser/base/content/test/general/test_contextmenu.html index d90f21c3e600..6c0b73fbb7af 100644 --- a/browser/base/content/test/general/test_contextmenu.html +++ b/browser/base/content/test/general/test_contextmenu.html @@ -15,8 +15,6 @@ Browser context menu tests.
-
-
 
 
   
 
@@ -30,7 +30,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=887836
     var u = new URLSearchParams();
     ok(u, "URLSearchParams created");
     is(u.has('foo'), false, 'URLSearchParams.has(foo)');
-    is(u.get('foo'), '', 'URLSearchParams.get(foo)');
+    is(u.get('foo'), null, 'URLSearchParams.get(foo)');
     is(u.getAll('foo').length, 0, 'URLSearchParams.getAll(foo)');
 
     u.append('foo', 'bar');
@@ -275,6 +275,19 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=887836
     runTest();
   }
 
+  function testGetNULL() {
+      
+    var u = new URLSearchParams();
+    is(typeof u.get(''), "object", "typeof URL.searchParams.get('')");
+    is(u.get(''), null, "URL.searchParams.get('') should be null");
+
+    var url = new URL('http://www.example.net?a=b');
+    is(url.searchParams.get('b'), null, "URL.searchParams.get('b') should be null");
+    is(url.searchParams.get('a'), 'b', "URL.searchParams.get('a')");
+
+    runTest();
+  }
+
   var tests = [
     testSimpleURLSearchParams,
     testCopyURLSearchParams,
@@ -285,7 +298,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=887836
     testEncoding,
     testMultiURL,
     testOrdering,
-    testDelete
+    testDelete,
+    testGetNULL
   ];
 
   function runTest() {
diff --git a/dom/workers/test/urlSearchParams_worker.js b/dom/workers/test/urlSearchParams_worker.js
index 2997bd511c6b..6bc0ba770728 100644
--- a/dom/workers/test/urlSearchParams_worker.js
+++ b/dom/workers/test/urlSearchParams_worker.js
@@ -22,7 +22,7 @@ onmessage = function() {
     var u = new URLSearchParams();
     ok(u, "URLSearchParams created");
     is(u.has('foo'), false, 'URLSearchParams.has(foo)');
-    is(u.get('foo'), '', 'URLSearchParams.get(foo)');
+    is(u.get('foo'), null, 'URLSearchParams.get(foo)');
     is(u.getAll('foo').length, 0, 'URLSearchParams.getAll(foo)');
 
     u.append('foo', 'bar');

From 97f16397f6fc690221951e0f2e8c061b15ebb371 Mon Sep 17 00:00:00 2001
From: Guilherme Goncalves 
Date: Tue, 7 Oct 2014 10:03:00 +0200
Subject: [PATCH 70/77] Bug 1078451 - Accept a free function in
 ThreadSharedFloatArrayBufferList::SetData. r=jesup

---
 content/media/webaudio/AudioBuffer.cpp   |  2 +-
 content/media/webaudio/AudioNodeEngine.h | 25 +++++++++++++++++-------
 content/media/webaudio/ConvolverNode.cpp |  2 +-
 content/media/webaudio/PeriodicWave.cpp  |  4 ++--
 4 files changed, 22 insertions(+), 11 deletions(-)

diff --git a/content/media/webaudio/AudioBuffer.cpp b/content/media/webaudio/AudioBuffer.cpp
index 04d6136224fc..fc2c90b2f498 100644
--- a/content/media/webaudio/AudioBuffer.cpp
+++ b/content/media/webaudio/AudioBuffer.cpp
@@ -226,7 +226,7 @@ StealJSArrayDataIntoThreadSharedFloatArrayBufferList(JSContext* aJSContext,
                           ? (uint8_t*) JS_StealArrayBufferContents(aJSContext, arrayBuffer)
                           : nullptr;
     if (stolenData) {
-      result->SetData(i, stolenData, reinterpret_cast(stolenData));
+      result->SetData(i, stolenData, js_free, reinterpret_cast(stolenData));
     } else {
       return nullptr;
     }
diff --git a/content/media/webaudio/AudioNodeEngine.h b/content/media/webaudio/AudioNodeEngine.h
index 626d52dd834e..2d30737ab1f2 100644
--- a/content/media/webaudio/AudioNodeEngine.h
+++ b/content/media/webaudio/AudioNodeEngine.h
@@ -38,12 +38,16 @@ public:
   }
 
   struct Storage {
-    Storage()
-    {
-      mDataToFree = nullptr;
-      mSampleData = nullptr;
+    Storage() :
+      mDataToFree(nullptr),
+      mFree(nullptr),
+      mSampleData(nullptr)
+    {}
+    ~Storage() {
+      if (mFree) {
+        mFree(mDataToFree);
+      } else { MOZ_ASSERT(!mDataToFree); }
     }
-    ~Storage() { free(mDataToFree); }
     size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
     {
       // NB: mSampleData might not be owned, if it is it just points to
@@ -51,6 +55,7 @@ public:
       return aMallocSizeOf(mDataToFree);
     }
     void* mDataToFree;
+    void (*mFree)(void*);
     const float* mSampleData;
   };
 
@@ -67,11 +72,17 @@ public:
    * Call this only during initialization, before the object is handed to
    * any other thread.
    */
-  void SetData(uint32_t aIndex, void* aDataToFree, const float* aData)
+  void SetData(uint32_t aIndex, void* aDataToFree, void (*aFreeFunc)(void*), const float* aData)
   {
     Storage* s = &mContents[aIndex];
-    free(s->mDataToFree);
+    if (s->mFree) {
+      s->mFree(s->mDataToFree);
+    } else {
+      MOZ_ASSERT(!s->mDataToFree);
+    }
+
     s->mDataToFree = aDataToFree;
+    s->mFree = aFreeFunc;
     s->mSampleData = aData;
   }
 
diff --git a/content/media/webaudio/ConvolverNode.cpp b/content/media/webaudio/ConvolverNode.cpp
index 1f14c555e4c0..9937f3e6bb7c 100644
--- a/content/media/webaudio/ConvolverNode.cpp
+++ b/content/media/webaudio/ConvolverNode.cpp
@@ -258,7 +258,7 @@ ConvolverNode::SetBuffer(JSContext* aCx, AudioBuffer* aBuffer, ErrorResult& aRv)
       for (uint32_t i = 0; i < data->GetChannels(); ++i) {
         PodCopy(channelData + length * i, data->GetData(i), mBuffer->Length());
         PodZero(channelData + length * i + mBuffer->Length(), WEBAUDIO_BLOCK_SIZE - mBuffer->Length());
-        paddedBuffer->SetData(i, (i == 0) ? channelData : nullptr, channelData);
+        paddedBuffer->SetData(i, (i == 0) ? channelData : nullptr, free, channelData);
       }
       data = paddedBuffer;
     }
diff --git a/content/media/webaudio/PeriodicWave.cpp b/content/media/webaudio/PeriodicWave.cpp
index 1d0e9a74a120..7a86f39cd4dc 100644
--- a/content/media/webaudio/PeriodicWave.cpp
+++ b/content/media/webaudio/PeriodicWave.cpp
@@ -38,9 +38,9 @@ PeriodicWave::PeriodicWave(AudioContext* aContext,
     return;
   }
   PodCopy(buffer, aRealData, aLength);
-  mCoefficients->SetData(0, buffer, buffer);
+  mCoefficients->SetData(0, buffer, free, buffer);
   PodCopy(buffer+aLength, aImagData, aLength);
-  mCoefficients->SetData(1, nullptr, buffer+aLength);
+  mCoefficients->SetData(1, nullptr, free, buffer+aLength);
 }
 
 size_t

From 6c7ddd6c983a13723a059f722afbb0abaa08745a Mon Sep 17 00:00:00 2001
From: ziyunfei <446240525@qq.com>
Date: Mon, 6 Oct 2014 23:39:00 +0200
Subject: [PATCH 71/77] Bug 1079090 - Coerce the argument passed to
 Object.getPrototypeOf using ToObject. r=till

---
 js/src/builtin/Object.cpp                     | 25 +++++--------------
 js/src/jit-test/tests/basic/setPrototypeOf.js |  3 ---
 .../basic/testErrorReportIn_getPrototypeOf.js |  9 -------
 .../tests/ecma_3_1/Object/regress-444787.js   | 17 -------------
 js/src/tests/ecma_6/Object/getPrototypeOf.js  | 22 ++++++++++++++++
 5 files changed, 28 insertions(+), 48 deletions(-)
 delete mode 100644 js/src/jit-test/tests/basic/testErrorReportIn_getPrototypeOf.js
 create mode 100644 js/src/tests/ecma_6/Object/getPrototypeOf.js

diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp
index 5d28abd56865..a22c77c84793 100644
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -531,33 +531,20 @@ obj_lookupSetter(JSContext *cx, unsigned argc, Value *vp)
 }
 #endif /* JS_OLD_GETTER_SETTER_METHODS */
 
-/* ES5 15.2.3.2. */
+// ES6 draft rev27 (2014/08/24) 19.1.2.9 Object.getPrototypeOf(O)
 bool
 js::obj_getPrototypeOf(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    /* Step 1. */
-    if (args.length() == 0) {
-        js_ReportMissingArg(cx, args.calleev(), 0);
+    /* Steps 1-2. */
+    RootedObject obj(cx, ToObject(cx, args.get(0)));
+    if (!obj)
         return false;
-    }
 
-    if (args[0].isPrimitive()) {
-        RootedValue val(cx, args[0]);
-        char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, val, NullPtr());
-        if (!bytes)
-            return false;
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
-                             JSMSG_UNEXPECTED_TYPE, bytes, "not an object");
-        js_free(bytes);
-        return false;
-    }
-
-    /* Step 2. */
-    RootedObject thisObj(cx, &args[0].toObject());
+    /* Step 3. */
     RootedObject proto(cx);
-    if (!JSObject::getProto(cx, thisObj, &proto))
+    if (!JSObject::getProto(cx, obj, &proto))
         return false;
     args.rval().setObjectOrNull(proto);
     return true;
diff --git a/js/src/jit-test/tests/basic/setPrototypeOf.js b/js/src/jit-test/tests/basic/setPrototypeOf.js
index c5ac67148b3c..88e213539e67 100644
--- a/js/src/jit-test/tests/basic/setPrototypeOf.js
+++ b/js/src/jit-test/tests/basic/setPrototypeOf.js
@@ -31,9 +31,6 @@ function TestSetPrototypeOf(object, proto) {
 // check if Object.setPrototypeOf works with coercible values
 for(var value of coercibleValues) {
     assertEq(Object.setPrototypeOf(value, {}), value);
-
-    assertThrowsInstanceOf(() => Object.getPrototypeOf(value),
-        TypeError, "Coercible values should not have a prototype");
 }
 
 // check if Object.setPrototypeOf fails on non-coercible values
diff --git a/js/src/jit-test/tests/basic/testErrorReportIn_getPrototypeOf.js b/js/src/jit-test/tests/basic/testErrorReportIn_getPrototypeOf.js
deleted file mode 100644
index e557119d36a7..000000000000
--- a/js/src/jit-test/tests/basic/testErrorReportIn_getPrototypeOf.js
+++ /dev/null
@@ -1,9 +0,0 @@
-actual = "";
-expect = "TypeError: \"kittens\" is not an object";
-try {
-    Object.getPrototypeOf.apply(null, ["kittens",4,3])
-} catch (e) {
-    actual = "" + e;
-}
-
-assertEq(actual, expect);
diff --git a/js/src/tests/ecma_3_1/Object/regress-444787.js b/js/src/tests/ecma_3_1/Object/regress-444787.js
index 4a1321932fa4..106a4e684a12 100644
--- a/js/src/tests/ecma_3_1/Object/regress-444787.js
+++ b/js/src/tests/ecma_3_1/Object/regress-444787.js
@@ -101,22 +101,5 @@ function test()
     reportCompare(expect, actual, summary + ' instance: ' + instance + ', type: ' + type.name);
   }
 
-  var non_objects = [ true, false, 1.0, Infinity, NaN, Math.PI, "bar" ];
-
-  for (i = 0; i < non_objects.length; i++)
-  {
-    instance = non_objects[i];
-    expect = 'TypeError: instance is not an object';
-    try
-    {
-      actual = Object.getPrototypeOf(instance);
-    }
-    catch(ex)
-    {
-      actual = ex + '';
-    }
-    reportCompare(expect, actual, summary + ' non-object: ' + actual);
-  }
-
   exitFunc ('test');
 }
diff --git a/js/src/tests/ecma_6/Object/getPrototypeOf.js b/js/src/tests/ecma_6/Object/getPrototypeOf.js
new file mode 100644
index 000000000000..71cd30c62cb9
--- /dev/null
+++ b/js/src/tests/ecma_6/Object/getPrototypeOf.js
@@ -0,0 +1,22 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var BUGNUMBER = 1079090;
+var summary = "Coerce the argument passed to Object.getPrototypeOf using ToObject";
+print(BUGNUMBER + ": " + summary);
+
+assertThrowsInstanceOf(() => Object.getPrototypeOf(), TypeError);
+assertThrowsInstanceOf(() => Object.getPrototypeOf(undefined), TypeError);
+assertThrowsInstanceOf(() => Object.getPrototypeOf(null), TypeError);
+
+assertEq(Object.getPrototypeOf(1), Number.prototype);
+assertEq(Object.getPrototypeOf(true), Boolean.prototype);
+assertEq(Object.getPrototypeOf("foo"), String.prototype);
+if (typeof Symbol === "function") {
+    assertEq(Object.getPrototypeOf(Symbol("foo")), Symbol.prototype);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);

From daa5cbe4196476ff3f722eb002ab1806f1691628 Mon Sep 17 00:00:00 2001
From: JW Wang 
Date: Tue, 7 Oct 2014 15:55:00 +0200
Subject: [PATCH 72/77] Bug 1079174 - allow video playback when AudioStream
 init fails. r=kinetik

---
 content/media/MediaDecoderStateMachine.cpp | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/content/media/MediaDecoderStateMachine.cpp b/content/media/MediaDecoderStateMachine.cpp
index 5575e2336a77..d88a0d3a8502 100644
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -3143,7 +3143,13 @@ void MediaDecoderStateMachine::OnAudioSinkError()
 
   mAudioCompleted = true;
 
-  // Notify media decoder/element about this error.
+  // Make the best effort to continue playback when there is video.
+  if (HasVideo()) {
+    return;
+  }
+
+  // Otherwise notify media decoder/element about this error for it makes
+  // no sense to play an audio-only file without sound output.
   RefPtr task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::OnDecodeError));
   nsresult rv = mDecodeTaskQueue->Dispatch(task);

From 2faf58994e2e6583f813de8b711348a39cabd201 Mon Sep 17 00:00:00 2001
From: ziyunfei <446240525@qq.com>
Date: Tue, 7 Oct 2014 09:21:00 +0200
Subject: [PATCH 73/77] Bug 1079188 - Coerce the argument passed to
 Object.getOwnPropertyDescriptor using ToObject. r=till

---
 js/src/builtin/Object.cpp                     | 11 ++++--
 .../ecma_6/Object/getOwnPropertyDescriptor.js | 35 +++++++++++++++++++
 2 files changed, 44 insertions(+), 2 deletions(-)
 create mode 100644 js/src/tests/ecma_6/Object/getOwnPropertyDescriptor.js

diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp
index a22c77c84793..a8bf8174cb5c 100644
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -813,16 +813,23 @@ js::obj_create(JSContext *cx, unsigned argc, Value *vp)
     return true;
 }
 
+// ES6 draft rev27 (2014/08/24) 19.1.2.6 Object.getOwnPropertyDescriptor(O, P)
 bool
 js::obj_getOwnPropertyDescriptor(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    RootedObject obj(cx);
-    if (!GetFirstArgumentAsObject(cx, args, "Object.getOwnPropertyDescriptor", &obj))
+
+    // Steps 1-2.
+    RootedObject obj(cx, ToObject(cx, args.get(0)));
+    if (!obj)
         return false;
+
+    // Steps 3-4.
     RootedId id(cx);
     if (!ValueToId(cx, args.get(1), &id))
         return false;
+
+    // Steps 5-7.
     return GetOwnPropertyDescriptor(cx, obj, id, args.rval());
 }
 
diff --git a/js/src/tests/ecma_6/Object/getOwnPropertyDescriptor.js b/js/src/tests/ecma_6/Object/getOwnPropertyDescriptor.js
new file mode 100644
index 000000000000..52d5762c3bfd
--- /dev/null
+++ b/js/src/tests/ecma_6/Object/getOwnPropertyDescriptor.js
@@ -0,0 +1,35 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var BUGNUMBER = 1079188;
+var summary = "Coerce the argument passed to Object.getOwnPropertyDescriptor using ToObject";
+print(BUGNUMBER + ": " + summary);
+
+assertThrowsInstanceOf(() => Object.getOwnPropertyDescriptor(), TypeError);
+assertThrowsInstanceOf(() => Object.getOwnPropertyDescriptor(undefined), TypeError);
+assertThrowsInstanceOf(() => Object.getOwnPropertyDescriptor(null), TypeError);
+
+Object.getOwnPropertyDescriptor(1);
+Object.getOwnPropertyDescriptor(true);
+if (typeof Symbol === "function") {
+    Object.getOwnPropertyDescriptor(Symbol("foo"));
+}
+
+assertDeepEq(Object.getOwnPropertyDescriptor("foo", "length"), {
+    configurable: false,
+    enumerable: false,
+    value: 3,
+    writable: false
+});
+
+assertDeepEq(Object.getOwnPropertyDescriptor("foo", 0), {
+    configurable: false,
+    enumerable: true,
+    value: "f",
+    writable: false
+});
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);

From 2af2be4c4c881fc7bdec1023ba16837758345b1f Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" 
Date: Tue, 7 Oct 2014 12:06:00 +0200
Subject: [PATCH 74/77] Bug 1078797 - Wait for running state before emitting
 install. r=ochameau

---
 toolkit/devtools/apps/app-actor-front.js | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/toolkit/devtools/apps/app-actor-front.js b/toolkit/devtools/apps/app-actor-front.js
index 4af3c7cc4ac4..a397d7dff50f 100644
--- a/toolkit/devtools/apps/app-actor-front.js
+++ b/toolkit/devtools/apps/app-actor-front.js
@@ -665,7 +665,6 @@ AppActorFront.prototype = {
   },
 
   _clientListener: function (type, message) {
-
     let { manifestURL } = message;
 
     // Reset the app object to get a fresh copy when we (re)install the app.
@@ -677,9 +676,11 @@ AppActorFront.prototype = {
       switch(type) {
         case "appOpen":
           app.running = true;
+          this._notifyListeners("appOpen", app);
           break;
         case "appClose":
           app.running = false;
+          this._notifyListeners("appClose", app);
           break;
         case "appInstall":
           // The call to _getApp is going to create App object
@@ -694,7 +695,10 @@ AppActorFront.prototype = {
               .then(res => {
                 if (res.apps.indexOf(manifestURL) !== -1) {
                   app.running = true;
+                  this._notifyListeners("appInstall", app);
                   this._notifyListeners("appOpen", app);
+                } else {
+                  this._notifyListeners("appInstall", app);
                 }
               });
           break;
@@ -705,12 +709,11 @@ AppActorFront.prototype = {
             this._notifyListeners("appClose", app);
           }
           this._apps.delete(manifestURL);
+          this._notifyListeners("appUninstall", app);
           break;
         default:
           return;
       }
-      this._notifyListeners(type, app);
-
     });
   },
 

From 049052f05d371a548ab471e17be26232035f0fdc Mon Sep 17 00:00:00 2001
From: James Lal 
Date: Tue, 7 Oct 2014 15:39:00 +0200
Subject: [PATCH 75/77] Bug 1079432 - Turn off minification in debug builds.
 r=mshal

---
 b2g/installer/Makefile.in | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/b2g/installer/Makefile.in b/b2g/installer/Makefile.in
index 416698c75dd0..80c8059ec6d1 100644
--- a/b2g/installer/Makefile.in
+++ b/b2g/installer/Makefile.in
@@ -57,8 +57,10 @@ include $(topsrcdir)/toolkit/mozapps/installer/packager.mk
 # including that file. MOZ_PACKAGER_MINIFY_JS is used in packager.mk, but since
 # recipe evaluation is deferred, we can set it here after the inclusion.
 ifneq (,$(JS_BINARY))
+ifndef MOZ_DEBUG
 MOZ_PACKAGER_MINIFY_JS=1
 endif
+endif
 
 ifeq (bundle, $(MOZ_FS_LAYOUT))
 BINPATH = $(_BINPATH)

From 83dc13845443ccfac8c8a2c7f112db21e5e9d362 Mon Sep 17 00:00:00 2001
From: JW Wang 
Date: Tue, 7 Oct 2014 19:45:00 +0200
Subject: [PATCH 76/77] Bug 1069289 - take |mAudioEndTime| into account when
 updating playback position at the end of playback. r=kinetik

---
 content/media/MediaDecoderStateMachine.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/content/media/MediaDecoderStateMachine.cpp b/content/media/MediaDecoderStateMachine.cpp
index d88a0d3a8502..eda5925ce9a9 100644
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -2478,8 +2478,8 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
       // will take care of calling MediaDecoder::PlaybackEnded.
       if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING &&
           !mDecoder->GetDecodedStream()) {
-        int64_t videoTime = HasVideo() ? mVideoFrameEndTime : 0;
-        int64_t clockTime = std::max(mEndTime, videoTime);
+        int64_t clockTime = std::max(mAudioEndTime, mVideoFrameEndTime);
+        clockTime = std::max(int64_t(0), std::max(clockTime, mEndTime));
         UpdatePlaybackPosition(clockTime);
 
         {

From 12f1280ce372b2abfb8ef283302bd1ed0a8a4ec3 Mon Sep 17 00:00:00 2001
From: "Carsten \"Tomcat\" Book" 
Date: Wed, 8 Oct 2014 15:41:35 +0200
Subject: [PATCH 77/77] Backed out changeset 88bb2a142e10 (bug 902799) on
 smaugs requests for regressions/crashes

---
 dom/canvas/CanvasRenderingContext2D.cpp | 116 ++++++------------------
 gfx/2d/Helpers.h                        |   4 -
 2 files changed, 26 insertions(+), 94 deletions(-)

diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
index 983fcaaa50c0..99020c854a8f 100644
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3091,18 +3091,11 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcess
   virtual void SetText(const char16_t* text, int32_t length, nsBidiDirection direction)
   {
     mFontgrp->UpdateUserFonts(); // ensure user font generation is current
-    // adjust flags for current direction run
-    uint32_t flags = mTextRunFlags;
-    if (direction & 1) {
-      flags |= gfxTextRunFactory::TEXT_IS_RTL;
-    } else {
-      flags &= ~gfxTextRunFactory::TEXT_IS_RTL;
-    }
     mTextRun = mFontgrp->MakeTextRun(text,
                                      length,
                                      mThebes,
                                      mAppUnitsPerDevPixel,
-                                     flags);
+                                     direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
   }
 
   virtual nscoord GetWidth()
@@ -3128,14 +3121,10 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcess
   virtual void DrawText(nscoord xOffset, nscoord width)
   {
     gfxPoint point = mPt;
-    bool rtl = mTextRun->IsRightToLeft();
-    bool verticalRun = mTextRun->IsVertical();
-
-    gfxFloat& inlineCoord = verticalRun ? point.y : point.x;
-    inlineCoord += xOffset;
+    point.x += xOffset;
 
     // offset is given in terms of left side of string
-    if (rtl) {
+    if (mTextRun->IsRightToLeft()) {
       // Bug 581092 - don't use rounded pixel width to advance to
       // right-hand end of run, because this will cause different
       // glyph positioning for LTR vs RTL drawing of the same
@@ -3149,7 +3138,7 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcess
                                   gfxFont::LOOSE_INK_EXTENTS,
                               mThebes,
                               nullptr);
-      inlineCoord += textRunMetrics.mAdvanceWidth;
+      point.x += textRunMetrics.mAdvanceWidth;
       // old code was:
       //   point.x += width * mAppUnitsPerDevPixel;
       // TODO: restore this if/when we move to fractional coords
@@ -3168,15 +3157,6 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcess
     mCtx->EnsureTarget();
     for (uint32_t c = 0; c < numRuns; c++) {
       gfxFont *font = runs[c].mFont;
-
-      bool verticalFont =
-        runs[c].mOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
-
-      const float& baselineOriginInline =
-        verticalFont ? baselineOrigin.y : baselineOrigin.x;
-      const float& baselineOriginBlock =
-        verticalFont ? baselineOrigin.x : baselineOrigin.y;
-
       uint32_t endRun = 0;
       if (c + 1 < numRuns) {
         endRun = runs[c + 1].mCharacterOffset;
@@ -3194,53 +3174,23 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcess
         return;
       }
 
-      AutoRestoreTransform sidewaysRestore;
-      if (runs[c].mOrientation ==
-          gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT) {
-        sidewaysRestore.Init(mCtx->mTarget);
-        // TODO: The baseline adjustment here is kinda ad-hoc; eventually
-        // perhaps we should check for horizontal and vertical baseline data
-        // in the font, and adjust accordingly.
-        // (The same will be true for HTML text layout.)
-        const gfxFont::Metrics& metrics = mTextRun->GetFontGroup()->
-          GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal);
-        mCtx->mTarget->SetTransform(mCtx->mTarget->GetTransform().Copy().
-          PreTranslate(baselineOrigin).      // translate origin for rotation
-          PreRotate(gfx::Float(M_PI / 2.0)). // turn 90deg clockwise
-          PreTranslate(-baselineOrigin).     // undo the translation
-          PreTranslate(Point(0, metrics.emAscent - metrics.emDescent) / 2));
-                              // and offset the (alphabetic) baseline of the
-                              // horizontally-shaped text from the (centered)
-                              // default baseline used for vertical
-      }
-
       RefPtr renderingOptions = font->GetGlyphRenderingOptions();
 
       GlyphBuffer buffer;
 
       std::vector glyphBuf;
 
-      // TODO:
-      // This more-or-less duplicates the code found in gfxTextRun::Draw
-      // and the gfxFont methods that uses (Draw, DrawGlyphs, DrawOneGlyph);
-      // it would be nice to refactor and share that code.
       for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) {
         Glyph newGlyph;
-
-        float& inlinePos =
-          verticalFont ? newGlyph.mPosition.y : newGlyph.mPosition.x;
-        float& blockPos =
-          verticalFont ? newGlyph.mPosition.x : newGlyph.mPosition.y;
-
         if (glyphs[i].IsSimpleGlyph()) {
           newGlyph.mIndex = glyphs[i].GetSimpleGlyph();
-          if (rtl) {
-            inlinePos = baselineOriginInline - advanceSum -
+          if (mTextRun->IsRightToLeft()) {
+            newGlyph.mPosition.x = baselineOrigin.x - advanceSum -
               glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit;
           } else {
-            inlinePos = baselineOriginInline + advanceSum;
+            newGlyph.mPosition.x = baselineOrigin.x + advanceSum;
           }
-          blockPos = baselineOriginBlock;
+          newGlyph.mPosition.y = baselineOrigin.y;
           advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit;
           glyphBuf.push_back(newGlyph);
           continue;
@@ -3250,34 +3200,34 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcess
           continue;
         }
 
-        const gfxTextRun::DetailedGlyph *d = mTextRun->GetDetailedGlyphs(i);
+        gfxTextRun::DetailedGlyph *detailedGlyphs =
+          mTextRun->GetDetailedGlyphs(i);
 
         if (glyphs[i].IsMissing()) {
           newGlyph.mIndex = 0;
-          if (rtl) {
-            inlinePos = baselineOriginInline - advanceSum -
-              d->mAdvance * devUnitsPerAppUnit;
+          if (mTextRun->IsRightToLeft()) {
+            newGlyph.mPosition.x = baselineOrigin.x - advanceSum -
+              detailedGlyphs[0].mAdvance * devUnitsPerAppUnit;
           } else {
-            inlinePos = baselineOriginInline + advanceSum;
+            newGlyph.mPosition.x = baselineOrigin.x + advanceSum;
           }
-          blockPos = baselineOriginBlock;
-          advanceSum += d->mAdvance * devUnitsPerAppUnit;
+          newGlyph.mPosition.y = baselineOrigin.y;
+          advanceSum += detailedGlyphs[0].mAdvance * devUnitsPerAppUnit;
           glyphBuf.push_back(newGlyph);
           continue;
         }
 
-        for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++, d++) {
-          newGlyph.mIndex = d->mGlyphID;
-          if (rtl) {
-            inlinePos = baselineOriginInline - advanceSum -
-              d->mAdvance * devUnitsPerAppUnit;
+        for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++) {
+          newGlyph.mIndex = detailedGlyphs[c].mGlyphID;
+          if (mTextRun->IsRightToLeft()) {
+            newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit -
+              advanceSum - detailedGlyphs[c].mAdvance * devUnitsPerAppUnit;
           } else {
-            inlinePos = baselineOriginInline + advanceSum;
+            newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit + advanceSum;
           }
-          inlinePos += d->mXOffset * devUnitsPerAppUnit;
-          blockPos = baselineOriginBlock + d->mYOffset * devUnitsPerAppUnit;
+          newGlyph.mPosition.y = baselineOrigin.y + detailedGlyphs[c].mYOffset * devUnitsPerAppUnit;
           glyphBuf.push_back(newGlyph);
-          advanceSum += d->mAdvance * devUnitsPerAppUnit;
+          advanceSum += detailedGlyphs[c].mAdvance * devUnitsPerAppUnit;
         }
       }
 
@@ -3352,9 +3302,6 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcess
   // union of bounding boxes of all runs, needed for shadows
   gfxRect mBoundingBox;
 
-  // flags to use when creating textrun, based on CSS style
-  uint32_t mTextRunFlags;
-
   // true iff the bounding box should be measured
   bool mDoMeasureBoundingBox;
 };
@@ -3394,10 +3341,9 @@ CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText,
   // for now, default to ltr if not in doc
   bool isRTL = false;
 
-  nsRefPtr canvasStyle;
   if (mCanvasElement && mCanvasElement->IsInDoc()) {
     // try to find the closest context
-    canvasStyle =
+    nsRefPtr canvasStyle =
       nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement,
                                                     nullptr,
                                                     presShell);
@@ -3432,14 +3378,6 @@ CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText,
 
   CanvasBidiProcessor processor;
 
-  // If we don't have a style context, we can't set up vertical-text flags
-  // (for now, at least; perhaps we need new Canvas API to control this).
-  processor.mTextRunFlags = canvasStyle ?
-    nsLayoutUtils::GetTextRunFlagsForStyle(canvasStyle,
-                                           canvasStyle->StyleFont(),
-                                           canvasStyle->StyleText(),
-                                           0) : 0;
-
   GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr);
   processor.mPt = gfxPoint(aX, aY);
   processor.mThebes =
@@ -3506,9 +3444,7 @@ CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText,
   // offset pt.y based on text baseline
   processor.mFontgrp->UpdateUserFonts(); // ensure user font generation is current
   const gfxFont::Metrics& fontMetrics =
-    processor.mFontgrp->GetFirstValidFont()->GetMetrics(
-      processor.mTextRun->IsVertical() ? gfxFont::eVertical :
-                                         gfxFont::eHorizontal);
+    processor.mFontgrp->GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal); // XXX vertical?
 
   gfxFloat anchorY;
 
diff --git a/gfx/2d/Helpers.h b/gfx/2d/Helpers.h
index ca51d04af4cf..dc93ff7fb460 100644
--- a/gfx/2d/Helpers.h
+++ b/gfx/2d/Helpers.h
@@ -14,10 +14,6 @@ namespace gfx {
 class AutoRestoreTransform
 {
  public:
-  AutoRestoreTransform()
-  {
-  }
-
   explicit AutoRestoreTransform(DrawTarget *aTarget)
    : mDrawTarget(aTarget),
      mOldTransform(aTarget->GetTransform())