author | Sebastian Hengst <archaeopteryx@coole-files.de> |
Tue, 12 Sep 2017 11:35:15 +0200 | |
changeset 380261 | b0e945eed81db8bf076daf64e381c514f70144f0 |
parent 380260 | 175f1366daee80cde53386a4627402ed3af4c038 (current diff) |
parent 380164 | 65261dd25999fe586192298a25bdb69fd8fc63d4 (diff) |
child 380262 | 485c8d248bfc4acc97f92cdc130210d7388bca32 |
child 380388 | 4069ad6982eabbab5b30fd45de159fe746e5a517 |
child 380403 | 7c348ad2e0514a79d925af96cd93278ac6462f1d |
push id | 94856 |
push user | archaeopteryx@coole-files.de |
push date | Tue, 12 Sep 2017 09:38:30 +0000 |
treeherder | mozilla-inbound@485c8d248bfc [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge, merge |
milestone | 57.0a1 |
first release with | nightly linux32
b0e945eed81d
/
57.0a1
/
20170912100139
/
files
nightly linux64
b0e945eed81d
/
57.0a1
/
20170912100139
/
files
nightly mac
b0e945eed81d
/
57.0a1
/
20170912100139
/
files
nightly win32
b0e945eed81d
/
57.0a1
/
20170912100139
/
files
nightly win64
b0e945eed81d
/
57.0a1
/
20170912100139
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
57.0a1
/
20170912100139
/
pushlog to previous
nightly linux64
57.0a1
/
20170912100139
/
pushlog to previous
nightly mac
57.0a1
/
20170912100139
/
pushlog to previous
nightly win32
57.0a1
/
20170912100139
/
pushlog to previous
nightly win64
57.0a1
/
20170912100139
/
pushlog to previous
|
--- a/accessible/tests/browser/bounds/browser.ini +++ b/accessible/tests/browser/bounds/browser.ini @@ -3,9 +3,9 @@ support-files = head.js !/accessible/tests/browser/events.js !/accessible/tests/browser/shared-head.js !/accessible/tests/mochitest/*.js !/accessible/tests/mochitest/letters.gif [browser_test_zoom.js] [browser_test_zoom_text.js] -skip-if = true # Bug 1372296, Bug 1379808, Bug 1391453 +skip-if = e10s && os == 'win' # bug 1372296
--- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -186,16 +186,28 @@ function isObject(aObj, aExpectedObj, aM return; } ok(false, aMsg + " - got '" + prettyName(aObj) + "', expected '" + prettyName(aExpectedObj) + "'"); } +/** + * is() function checking the expected value is within the range. + */ +function isWithin(aExpected, aGot, aWithin, aMsg) { + if (Math.abs(aGot - aExpected) <= aWithin) { + ok(true, `${aMsg} - Got ${aGot}`); + } else { + ok(false, + `${aMsg} - Got ${aGot}, expected ${aExpected} with error of ${aWithin}`); + } +} + // ////////////////////////////////////////////////////////////////////////////// // Helpers for getting DOM node/accessible /** * Return the DOM node by identifier (may be accessible, DOM node or ID). */ function getNode(aAccOrNodeOrID, aDocument) { if (!aAccOrNodeOrID)
--- a/accessible/tests/mochitest/layout.js +++ b/accessible/tests/mochitest/layout.js @@ -145,33 +145,39 @@ function testTextPos(aID, aOffset, aPoin */ function testTextBounds(aID, aStartOffset, aEndOffset, aRect, aCoordOrigin) { var [expectedX, expectedY, expectedWidth, expectedHeight] = aRect; var xObj = {}, yObj = {}, widthObj = {}, heightObj = {}; var hyperText = getAccessible(aID, [nsIAccessibleText]); hyperText.getRangeExtents(aStartOffset, aEndOffset, xObj, yObj, widthObj, heightObj, aCoordOrigin); + + // x is(xObj.value, expectedX, "Wrong x coordinate of text between offsets (" + aStartOffset + ", " + aEndOffset + ") for " + prettyName(aID)); - is(yObj.value, expectedY, - "Wrong y coordinate of text between offsets (" + aStartOffset + ", " + - aEndOffset + ") for " + prettyName(aID)); + // y + isWithin(yObj.value, expectedY, 1, + `y coord of text between offsets (${aStartOffset}, ${aEndOffset}) ` + + `for ${prettyName(aID)}`); + + // Width var msg = "Wrong width of text between offsets (" + aStartOffset + ", " + aEndOffset + ") for " + prettyName(aID); if (widthObj.value == expectedWidth) ok(true, msg); else todo(false, msg); // fails on some windows machines - is(heightObj.value, expectedHeight, - "Wrong height of text between offsets (" + aStartOffset + ", " + - aEndOffset + ") for " + prettyName(aID)); + // Height + isWithin(heightObj.value, expectedHeight, 1, + `height of text between offsets (${aStartOffset}, ${aEndOffset}) ` + + `for ${prettyName(aID)}`); } /** * Return the accessible coordinates relative to the screen in device pixels. */ function getPos(aID) { var accessible = getAccessible(aID); var x = {}, y = {};
--- a/browser/components/customizableui/CustomizableUI.jsm +++ b/browser/components/customizableui/CustomizableUI.jsm @@ -4348,17 +4348,17 @@ OverflowableToolbar.prototype = { let win = this._target.ownerGlobal; win.UpdateUrlbarSearchSplitterState(); }, _onResize(aEvent) { if (!this._lazyResizeHandler) { this._lazyResizeHandler = new DeferredTask(this._onLazyResize.bind(this), - LAZY_RESIZE_INTERVAL_MS); + LAZY_RESIZE_INTERVAL_MS, 0); } this._lazyResizeHandler.arm(); }, _moveItemsBackToTheirOrigin(shouldMoveAllItems) { let placements = gPlacements.get(this._toolbar.id); while (this._list.firstChild) { let child = this._list.firstChild;
--- a/browser/components/extensions/ext-browser.js +++ b/browser/components/extensions/ext-browser.js @@ -512,19 +512,17 @@ class TabTracker extends TabTrackerBase this.emit("tab-removed", {nativeTab, tabId, windowId, isWindowClosing}); }); } getBrowserData(browser) { if (browser.ownerDocument.documentURI === "about:addons") { // When we're loaded into a <browser> inside about:addons, we need to go up // one more level. - browser = browser.ownerGlobal.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell) - .chromeEventHandler; + browser = browser.ownerDocument.docShell.chromeEventHandler; } let result = { tabId: -1, windowId: -1, }; let {gBrowser} = browser.ownerGlobal;
--- a/browser/components/extensions/ext-browsingData.js +++ b/browser/components/extensions/ext-browsingData.js @@ -45,20 +45,17 @@ const clearCache = () => { const clearCookies = async function(options) { let cookieMgr = Services.cookies; // This code has been borrowed from sanitize.js. let yieldCounter = 0; if (options.since || options.hostnames) { // Iterate through the cookies and delete any created after our cutoff. - let cookiesEnum = cookieMgr.enumerator; - while (cookiesEnum.hasMoreElements()) { - let cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2); - + for (const cookie of XPCOMUtils.IterSimpleEnumerator(cookieMgr.enumerator, Ci.nsICookie2)) { if ((!options.since || cookie.creationTime >= PlacesUtils.toPRTime(options.since)) && (!options.hostnames || options.hostnames.includes(cookie.host.replace(/^\./, "")))) { // This cookie was created after our cutoff, clear it. cookieMgr.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes); if (++yieldCounter % YIELD_PERIOD == 0) { await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
--- a/browser/components/extensions/ext-tabs.js +++ b/browser/components/extensions/ext-tabs.js @@ -729,19 +729,17 @@ this.tabs = class extends ExtensionAPI { let zoomListener = event => { let browser = event.originalTarget; // For non-remote browsers, this event is dispatched on the document // rather than on the <browser>. if (browser instanceof Ci.nsIDOMDocument) { - browser = browser.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell) - .chromeEventHandler; + browser = browser.docShell.chromeEventHandler; } let {gBrowser} = browser.ownerGlobal; let nativeTab = gBrowser.getTabForBrowser(browser); if (!nativeTab) { // We only care about zoom events in the top-level browser of a tab. return; }
--- a/browser/installer/windows/nsis/shared.nsh +++ b/browser/installer/windows/nsis/shared.nsh @@ -108,19 +108,22 @@ ${RemoveDeprecatedFiles} ; Fix the distribution.ini file if applicable ${FixDistributionsINI} RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}" - ; Register AccessibleHandler.dll with COM (this writes to HKLM) + ; Register AccessibleHandler.dll with COM (this requires write access to HKLM) ${RegisterAccessibleHandler} + ; Register AccessibleMarshal.dll with COM (this requires write access to HKLM) + ${RegisterAccessibleMarshal} + !ifdef MOZ_MAINTENANCE_SERVICE Call IsUserAdmin Pop $R0 ${If} $R0 == "true" ; Only proceed if we have HKLM write access ${AndIf} $TmpVal == "HKLM" ; We check to see if the maintenance service install was already attempted. ; Since the Maintenance service can be installed either x86 or x64, @@ -1009,16 +1012,21 @@ !define AddMaintCertKeys "!insertmacro AddMaintCertKeys" !endif !macro RegisterAccessibleHandler ${RegisterDLL} "$INSTDIR\AccessibleHandler.dll" !macroend !define RegisterAccessibleHandler "!insertmacro RegisterAccessibleHandler" +!macro RegisterAccessibleMarshal + ${RegisterDLL} "$INSTDIR\AccessibleMarshal.dll" +!macroend +!define RegisterAccessibleMarshal "!insertmacro RegisterAccessibleMarshal" + ; Removes various registry entries for reasons noted below (does not use SHCTX). !macro RemoveDeprecatedKeys StrCpy $0 "SOFTWARE\Classes" ; Remove support for launching chrome urls from the shell during install or ; update if the DefaultIcon is from firefox.exe (Bug 301073). ${RegCleanAppHandler} "chrome" ; Remove protocol handler registry keys added by the MS shim
--- a/devtools/client/framework/test/browser.ini +++ b/devtools/client/framework/test/browser.ini @@ -67,17 +67,16 @@ skip-if = debug # Bug 1282269 [browser_source_map-inline.js] [browser_source_map-no-race.js] [browser_source_map-reload.js] [browser_target_from_url.js] [browser_target_events.js] [browser_target_remote.js] [browser_target_support.js] [browser_toolbox_custom_host.js] -skip-if = true # Bug 1386410 [browser_toolbox_dynamic_registration.js] [browser_toolbox_getpanelwhenready.js] [browser_toolbox_highlight.js] [browser_toolbox_hosts.js] [browser_toolbox_hosts_size.js] [browser_toolbox_hosts_telemetry.js] [browser_toolbox_keyboard_navigation.js] skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
--- a/devtools/server/performance/memory.js +++ b/devtools/server/performance/memory.js @@ -190,17 +190,17 @@ Memory.prototype = { this.drainAllocationsTimeoutTimer = options.drainAllocationsTimeout; if (this.drainAllocationsTimeoutTimer != null) { if (this._poller) { this._poller.disarm(); } this._poller = new DeferredTask(this._emitAllocations, - this.drainAllocationsTimeoutTimer); + this.drainAllocationsTimeoutTimer, 0); this._poller.arm(); } if (options.maxLogLength != null) { this.dbg.memory.maxAllocationsLogLength = options.maxLogLength; } this.dbg.memory.trackingAllocationSites = true;
--- a/devtools/server/performance/profiler.js +++ b/devtools/server/performance/profiler.js @@ -382,17 +382,17 @@ const ProfilerManager = (function () { /** * Will enable or disable "profiler-status" events depending on * if there are subscribers and if the profiler is current recording. */ _updateProfilerStatusPolling: function () { if (this._profilerStatusSubscribers > 0 && nsIProfilerModule.IsActive()) { if (!this._poller) { this._poller = new DeferredTask(this._emitProfilerStatus.bind(this), - this._profilerStatusInterval); + this._profilerStatusInterval, 0); } this._poller.arm(); } else if (this._poller) { // No subscribers; turn off if it exists. this._poller.disarm(); } },
--- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -12759,17 +12759,17 @@ nsGlobalWindow::FireDelayedDOMEvents() //***************************************************************************** nsPIDOMWindowOuter* nsGlobalWindow::GetParentInternal() { if (IsInnerWindow()) { nsGlobalWindow* outer = GetOuterWindowInternal(); if (!outer) { - NS_WARNING("No outer window available!"); + // No outer window available! return nullptr; } return outer->GetParentInternal(); } nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent(); if (parent && parent != AsOuter()) {
--- a/dom/ipc/CoalescedMouseData.cpp +++ b/dom/ipc/CoalescedMouseData.cpp @@ -21,19 +21,16 @@ CoalescedMouseData::Coalesce(const Widge mGuid = aGuid; mInputBlockId = aInputBlockId; } else { MOZ_ASSERT(mGuid == aGuid); MOZ_ASSERT(mInputBlockId == aInputBlockId); MOZ_ASSERT(mCoalescedInputEvent->mModifiers == aEvent.mModifiers); MOZ_ASSERT(mCoalescedInputEvent->mReason == aEvent.mReason); MOZ_ASSERT(mCoalescedInputEvent->inputSource == aEvent.inputSource); - - // Assuming button changes should trigger other mouse events and dispatch - // the coalesced mouse move events. MOZ_ASSERT(mCoalescedInputEvent->button == aEvent.button); MOZ_ASSERT(mCoalescedInputEvent->buttons == aEvent.buttons); mCoalescedInputEvent->mTimeStamp = aEvent.mTimeStamp; mCoalescedInputEvent->mRefPoint = aEvent.mRefPoint; mCoalescedInputEvent->pressure = aEvent.pressure; mCoalescedInputEvent->AssignPointerHelperData(aEvent); } } @@ -42,16 +39,18 @@ bool CoalescedMouseData::CanCoalesce(const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId) { return !mCoalescedInputEvent || (mCoalescedInputEvent->mModifiers == aEvent.mModifiers && mCoalescedInputEvent->inputSource == aEvent.inputSource && mCoalescedInputEvent->pointerId == aEvent.pointerId && + mCoalescedInputEvent->button == aEvent.button && + mCoalescedInputEvent->buttons == aEvent.buttons && mGuid == aGuid && mInputBlockId == aInputBlockId); } void CoalescedMouseMoveFlusher::WillRefresh(mozilla::TimeStamp aTime) {
--- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -2305,16 +2305,18 @@ ContentChild::ActorDestroy(ActorDestroyR } if (AbnormalShutdown == why) { NS_WARNING("shutting down early because of crash!"); ProcessChild::QuickExit(); } #ifndef NS_FREE_PERMANENT_DATA + CompositorManagerChild::Shutdown(); + // In release builds, there's no point in the content process // going through the full XPCOM shutdown path, because it doesn't // keep persistent state. ProcessChild::QuickExit(); #else if (gFirstIdleTask) { gFirstIdleTask->Cancel(); }
--- a/dom/media/mediasink/DecodedStream.cpp +++ b/dom/media/mediasink/DecodedStream.cpp @@ -407,17 +407,17 @@ DecodedStream::DestroyData(UniquePtr<Dec } mOutputListener.Disconnect(); DecodedStreamData* data = aData.release(); data->Forget(); nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("DecodedStream::DestroyData", [=]() { delete data; }); - mAbstractMainThread->Dispatch(r.forget()); + NS_DispatchToMainThread(r.forget()); } void DecodedStream::SetPlaying(bool aPlaying) { AssertOwnerThread(); // Resume/pause matters only when playback started.
new file mode 100644 --- /dev/null +++ b/dom/media/test/crashtests/1378826.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<title>Bug 1378826 : Removing last video track from recorder stream crashes.</title> +</head> +<body> +<canvas id="canvas"></canvas> +<script type="text/javascript"> + +function wait(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +function boom() { + let canvas = document.getElementById("canvas"); + let ctx = canvas.getContext('2d'); + ctx.fillRect(10, 10, 100, 100); + let stream = canvas.captureStream(); + let rec = new MediaRecorder(stream); + // At the time of fixing this bug onstop would fire, but this may change in + // future. As such defensively listen for onerror too to prevent this test + // timing out. + let stoppedPromise = new Promise(y => (rec.onstop = y, + rec.onerror = e => y)); + rec.onstart = () => { + // Remove the video track from the stream we're recording + stream.removeTrack(stream.getTracks()[0]); + // Recorder should stop or error in response to the above + return stoppedPromise + .then(() => { + // Little wait to help get bad writes if they're going to happen + wait(100) + .then(() => { + // Didn't crash, finish + document.documentElement.removeAttribute("class"); + }); + }); + }; + rec.start(); +} + +window.onload = boom; + +</script> +</body> +</html>
--- a/dom/media/test/crashtests/crashtests.list +++ b/dom/media/test/crashtests/crashtests.list @@ -80,16 +80,17 @@ load 1127188.html load 1157994.html load 1158427.html load 1185176.html load 1185192.html load 1304948.html load 1319486.html load 1368490.html load 1291702.html +load 1378826.html load 1384248.html load disconnect-wrong-destination.html load analyser-channels-1.html load audiocontext-double-suspend.html load buffer-source-duration-1.html load buffer-source-ended-1.html load buffer-source-resampling-start-1.html load buffer-source-slow-resampling-1.html
--- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -1114,16 +1114,17 @@ GeckoChildProcessHost::PerformAsyncLaunc // Process type cmdLine.AppendLooseValue(UTF8ToWide(childProcessType)); #if defined(XP_WIN) && defined(MOZ_SANDBOX) if (shouldSandboxCurrentProcess) { if (mSandboxBroker.LaunchApp(cmdLine.program().c_str(), cmdLine.command_line_string().c_str(), + mProcessType, mEnableSandboxLogging, &process)) { EnvironmentLog("MOZ_PROCESS_LOG").print( "==> process %d launched child process %d (%S)\n", base::GetCurrentProcId(), base::GetProcId(process), cmdLine.command_line_string().c_str()); } } else
--- a/ipc/mscom/oop/Handler.cpp +++ b/ipc/mscom/oop/Handler.cpp @@ -350,19 +350,22 @@ Handler::Register(REFCLSID aClsid) mozilla::ArrayLength(absLibPath)); if (!size || (size == mozilla::ArrayLength(absLibPath) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { DWORD lastError = GetLastError(); Unregister(aClsid); return HRESULT_FROM_WIN32(lastError); } + // The result of GetModuleFileName excludes the null terminator + DWORD valueSizeWithNullInBytes = (size + 1) * sizeof(wchar_t); + result = RegSetValueEx(inprocHandlerKey, L"", 0, REG_EXPAND_SZ, reinterpret_cast<const BYTE*>(absLibPath), - sizeof(absLibPath)); + valueSizeWithNullInBytes); if (result != ERROR_SUCCESS) { Unregister(aClsid); return HRESULT_FROM_WIN32(result); } const wchar_t kApartment[] = L"Apartment"; result = RegSetValueEx(inprocHandlerKey, L"ThreadingModel", 0, REG_SZ, reinterpret_cast<const BYTE*>(kApartment),
--- a/js/src/jit/JitcodeMap.cpp +++ b/js/src/jit/JitcodeMap.cpp @@ -1653,19 +1653,22 @@ JS::ForEachProfiledFrameOp::FrameHandle: return JS::ProfilingFrameIterator::Frame_Baseline; return JS::ProfilingFrameIterator::Frame_Ion; } JS_PUBLIC_API(void) JS::ForEachProfiledFrame(JSContext* cx, void* addr, ForEachProfiledFrameOp& op) { js::jit::JitcodeGlobalTable* table = cx->runtime()->jitRuntime()->getJitcodeGlobalTable(); - js::jit::JitcodeGlobalEntry& entry = table->lookupInfallible(addr); + js::jit::JitcodeGlobalEntry* entry = table->lookup(addr); + + if (!entry) + return; // Extract the stack for the entry. Assume maximum inlining depth is <64 const char* labels[64]; - uint32_t depth = entry.callStackAtAddr(cx->runtime(), addr, labels, 64); + uint32_t depth = entry->callStackAtAddr(cx->runtime(), addr, labels, 64); MOZ_ASSERT(depth < 64); for (uint32_t i = depth; i != 0; i--) { - JS::ForEachProfiledFrameOp::FrameHandle handle(cx->runtime(), entry, addr, labels[i - 1], i - 1); + JS::ForEachProfiledFrameOp::FrameHandle handle(cx->runtime(), *entry, addr, labels[i - 1], i - 1); op(handle); } }
--- a/js/src/jit/JitcodeMap.h +++ b/js/src/jit/JitcodeMap.h @@ -1037,17 +1037,17 @@ class JitcodeGlobalTable freeTowers_[i] = nullptr; } ~JitcodeGlobalTable() {} bool empty() const { return skiplistSize_ == 0; } - const JitcodeGlobalEntry* lookup(void* ptr) { + JitcodeGlobalEntry* lookup(void* ptr) { return lookupInternal(ptr); } JitcodeGlobalEntry& lookupInfallible(void* ptr) { JitcodeGlobalEntry* entry = lookupInternal(ptr); MOZ_ASSERT(entry); return *entry; }
--- a/js/xpconnect/loader/ScriptPreloader.cpp +++ b/js/xpconnect/loader/ScriptPreloader.cpp @@ -596,18 +596,16 @@ ScriptPreloader::PrepareCacheWrite() // // - A block of XDR data for the encoded scripts, with each script's data at // an offset from the start of the block, as specified above. Result<Ok, nsresult> ScriptPreloader::WriteCache() { MOZ_ASSERT(!NS_IsMainThread()); - Unused << URLPreloader::GetSingleton().WriteCache(); - if (!mDataPrepared && !mSaveComplete) { MOZ_ASSERT(!mBlockedOnSyncDispatch); mBlockedOnSyncDispatch = true; MonitorAutoUnlock mau(mSaveMonitor); NS_DispatchToMainThread( NewRunnableMethod("ScriptPreloader::PrepareCacheWrite", @@ -690,17 +688,20 @@ ScriptPreloader::Run() // Ideally wait about 10 seconds before saving, to avoid unnecessary IO // during early startup. But only if the cache hasn't been invalidated, // since that can trigger a new write during shutdown, and we don't want to // cause shutdown hangs. if (!mCacheInvalidated) { mal.Wait(10000); } - auto result = WriteCache(); + auto result = URLPreloader::GetSingleton().WriteCache(); + Unused << NS_WARN_IF(result.isErr()); + + result = WriteCache(); Unused << NS_WARN_IF(result.isErr()); result = mChildCache->WriteCache(); Unused << NS_WARN_IF(result.isErr()); mSaveComplete = true; NS_ReleaseOnMainThreadSystemGroup("ScriptPreloader::mSaveThread", mSaveThread.forget());
--- a/js/xpconnect/loader/URLPreloader.cpp +++ b/js/xpconnect/loader/URLPreloader.cpp @@ -197,16 +197,30 @@ URLPreloader::FindCacheFile() return Move(cacheFile); } Result<Ok, nsresult> URLPreloader::WriteCache() { MOZ_ASSERT(!NS_IsMainThread()); + // The script preloader might call us a second time, if it has to re-write + // its cache after a cache flush. We don't care about cache flushes, since + // our cache doesn't store any file data, only paths. And we currently clear + // our cached file list after the first write, which means that a second + // write would (aside from breaking the invariant that we never touch + // mCachedURLs off-main-thread after the first write, and trigger a data + // race) mean we get no pre-loading on the next startup. + if (mCacheWritten) { + return Ok(); + } + mCacheWritten = true; + + LOG(Debug, "Writing cache..."); + nsCOMPtr<nsIFile> cacheFile; MOZ_TRY_VAR(cacheFile, GetCacheFile(NS_LITERAL_STRING("-new.bin"))); bool exists; MOZ_TRY(cacheFile->Exists(&exists)); if (exists) { MOZ_TRY(cacheFile->Remove(false)); } @@ -251,16 +265,18 @@ void URLPreloader::Cleanup() { mCachedURLs.Clear(); } Result<Ok, nsresult> URLPreloader::ReadCache(LinkedList<URLEntry>& pendingURLs) { + LOG(Debug, "Reading cache..."); + nsCOMPtr<nsIFile> cacheFile; MOZ_TRY_VAR(cacheFile, FindCacheFile()); AutoMemMap cache; MOZ_TRY(cache.init(cacheFile)); auto size = cache.size(); @@ -296,16 +312,18 @@ URLPreloader::ReadCache(LinkedList<URLEn Range<uint8_t> header(data, data + headerSize); data += headerSize; InputBuffer buf(header); while (!buf.finished()) { CacheKey key(buf); + LOG(Debug, "Cached file: %s %s", key.TypeString(), key.mPath.get()); + auto entry = mCachedURLs.LookupOrAdd(key, key); entry->mResultCode = NS_ERROR_NOT_INITIALIZED; pendingURLs.insertBack(entry); } if (buf.error()) { return Err(NS_ERROR_UNEXPECTED); @@ -380,16 +398,18 @@ URLPreloader::BackgroundReadFiles() // If there is any other error code, the entry has already failed at // this point, so don't bother trying to read it again. if (entry->mResultCode != NS_ERROR_NOT_INITIALIZED) { continue; } nsresult rv = NS_OK; + LOG(Debug, "Background reading %s file %s", entry->TypeString(), entry->mPath.get()); + if (entry->mType == entry->TypeFile) { auto result = entry->Read(); if (result.isErr()) { rv = result.unwrapErr(); } } else { auto& cursor = cursors[i++];
--- a/js/xpconnect/loader/URLPreloader.h +++ b/js/xpconnect/loader/URLPreloader.h @@ -298,16 +298,19 @@ private: using HashType = nsClassHashtable<nsGenericHashKey<CacheKey>, URLEntry>; size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); bool mStartupFinished = false; bool mReaderInitialized = false; + // Only to be accessed from the cache write thread. + bool mCacheWritten = false; + // The prefix URLs for files in the GRE and App omni jar archives. nsCString mGREPrefix; nsCString mAppPrefix; nsCOMPtr<nsIResProtocolHandler> mResProto; nsCOMPtr<nsIChromeRegistry> mChromeReg; nsCOMPtr<nsIFile> mProfD;
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1826,16 +1826,17 @@ pref("network.http.max_response_header_s // If we should attempt to race the cache and network pref("network.http.rcwn.enabled", false); pref("network.http.rcwn.cache_queue_normal_threshold", 8); pref("network.http.rcwn.cache_queue_priority_threshold", 2); // We might attempt to race the cache with the network only if a resource // is smaller than this size. pref("network.http.rcwn.small_resource_size_kb", 256); +pref("network.http.rcwn.min_wait_before_racing_ms", 0); pref("network.http.rcwn.max_wait_before_racing_ms", 500); // The ratio of the transaction count for the focused window and the count of // all available active connections. pref("network.http.focused_window_transaction_ratio", "0.9"); // Whether or not we give more priority to active tab. // Note that this requires restart for changes to take effect. @@ -3333,17 +3334,17 @@ pref("dom.ipc.processCount.file", 1); // WebExtensions only support a single extension process. pref("dom.ipc.processCount.extension", 1); // Don't use a native event loop in the content process. pref("dom.ipc.useNativeEventProcessing.content", false); // Quantum DOM scheduling: pref("dom.ipc.scheduler", false); -pref("dom.ipc.scheduler.useMultipleQueues", false); +pref("dom.ipc.scheduler.useMultipleQueues", true); pref("dom.ipc.scheduler.preemption", false); pref("dom.ipc.scheduler.threadCount", 2); pref("dom.ipc.scheduler.chaoticScheduling", false); // Disable support for SVG pref("svg.disabled", false); // Override default dom.ipc.processCount for some remote content process types.
--- a/netwerk/cookie/CookieServiceChild.cpp +++ b/netwerk/cookie/CookieServiceChild.cpp @@ -70,20 +70,16 @@ CookieServiceChild::CookieServiceChild() return; } // This corresponds to Release() in DeallocPCookieService. NS_ADDREF_THIS(); NeckoChild::InitNeckoChild(); - gNeckoChild->SetEventTargetForActor( - this, - SystemGroup::EventTargetFor(TaskCategory::Other)); - // Create a child PCookieService actor. gNeckoChild->SendPCookieServiceConstructor(this); mIPCOpen = true; mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); NS_ASSERTION(mTLDService, "couldn't get TLDService");
--- a/netwerk/dns/nsEffectiveTLDService.cpp +++ b/netwerk/dns/nsEffectiveTLDService.cpp @@ -207,19 +207,20 @@ nsEffectiveTLDService::GetBaseDomainInte return NS_ERROR_INVALID_ARG; // Check if we're dealing with an IPv4/IPv6 hostname, and return PRNetAddr addr; PRStatus result = PR_StringToNetAddr(aHostname.get(), &addr); if (result == PR_SUCCESS) return NS_ERROR_HOST_IS_IP_ADDRESS; - // Lookup in the cache if this is a normal query. + // Lookup in the cache if this is a normal query. This is restricted to + // main thread-only as the cache is not thread-safe. TLDCacheEntry* entry = nullptr; - if (aAdditionalParts == 1) { + if (aAdditionalParts == 1 && NS_IsMainThread()) { if (LookupForAdd(aHostname, &entry)) { // There was a match, just return the cached value. aBaseDomain = entry->mBaseDomain; if (trailingDot) { aBaseDomain.Append('.'); } return NS_OK;
--- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -123,16 +123,17 @@ namespace { // Monotonically increasing ID for generating unique cache entries per // intercepted channel. static uint64_t gNumIntercepted = 0; static bool sRCWNEnabled = false; static uint32_t sRCWNQueueSizeNormal = 50; static uint32_t sRCWNQueueSizePriority = 10; static uint32_t sRCWNSmallResourceSizeKB = 256; +static uint32_t sRCWNMinWaitMs = 0; static uint32_t sRCWNMaxWaitMs = 500; // True if the local cache should be bypassed when processing a request. #define BYPASS_LOCAL_CACHE(loadFlags) \ (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \ nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE)) #define RECOVER_FROM_CACHE_FILE_ERROR(result) \ @@ -620,17 +621,17 @@ nsHttpChannel::ConnectOnTailUnblock() // When racing, if OnCacheEntryAvailable is called before AsyncOpenURI // returns, then we may not have started reading from the cache. // If the content is valid, we should attempt to do so, as technically the // cache has won the race. if (mRaceCacheWithNetwork && mCachedContentIsValid) { Unused << ReadFromCache(true); } - return TriggerNetwork(0); + return TriggerNetwork(); } nsresult nsHttpChannel::TryHSTSPriming() { bool isHttpScheme; nsresult rv = mURI->SchemeIs("http", &isHttpScheme); NS_ENSURE_SUCCESS(rv, rv); @@ -4508,17 +4509,17 @@ nsHttpChannel::OnCacheEntryAvailableInte // request was already sent (see bug 1377223). AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent); } if (mRaceCacheWithNetwork && mCachedContentIsValid) { Unused << ReadFromCache(true); } - return TriggerNetwork(0); + return TriggerNetwork(); } nsresult nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry *aEntry, bool aNew, nsresult aEntryStatus) { mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY; @@ -6129,16 +6130,17 @@ nsHttpChannel::AsyncOpen(nsIStreamListen static bool sRCWNInited = false; if (!sRCWNInited) { sRCWNInited = true; Preferences::AddBoolVarCache(&sRCWNEnabled, "network.http.rcwn.enabled"); Preferences::AddUintVarCache(&sRCWNQueueSizeNormal, "network.http.rcwn.cache_queue_normal_threshold"); Preferences::AddUintVarCache(&sRCWNQueueSizePriority, "network.http.rcwn.cache_queue_priority_threshold"); Preferences::AddUintVarCache(&sRCWNSmallResourceSizeKB, "network.http.rcwn.small_resource_size_kb"); + Preferences::AddUintVarCache(&sRCWNMinWaitMs, "network.http.rcwn.min_wait_before_racing_ms"); Preferences::AddUintVarCache(&sRCWNMaxWaitMs, "network.http.rcwn.max_wait_before_racing_ms"); } rv = NS_CheckPortSafety(mURI); if (NS_FAILED(rv)) { ReleaseListeners(); return rv; } @@ -9297,17 +9299,53 @@ nsHttpChannel::Test_triggerDelayedOpenCa std::function<void(nsHttpChannel*)> cacheOpenFunc = nullptr; std::swap(cacheOpenFunc, mCacheOpenFunc); cacheOpenFunc(this); return NS_OK; } nsresult -nsHttpChannel::TriggerNetwork(int32_t aTimeout) +nsHttpChannel::TriggerNetworkWithDelay(uint32_t aDelay) +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread"); + + LOG(("nsHttpChannel::TriggerNetworkWithDelay [this=%p, delay=%u]\n", + this, aDelay)); + + if (mCanceled) { + LOG((" channel was canceled.\n")); + return mStatus; + } + + // If a network request has already gone out, there is no point in + // doing this again. + if (mNetworkTriggered) { + LOG((" network already triggered. Returning.\n")); + return NS_OK; + } + + if (!aDelay) { + // We cannot call TriggerNetwork() directly here, because it would + // cause performance regression in tp6 tests, see bug 1398847. + return NS_DispatchToMainThread( + NewRunnableMethod("net::nsHttpChannel::TriggerNetworkWithDelay", + this, &nsHttpChannel::TriggerNetwork), + NS_DISPATCH_NORMAL); + } + + if (!mNetworkTriggerTimer) { + mNetworkTriggerTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } + mNetworkTriggerTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT); + return NS_OK; +} + +nsresult +nsHttpChannel::TriggerNetwork() { MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread"); LOG(("nsHttpChannel::TriggerNetwork [this=%p]\n", this)); if (mCanceled) { LOG((" channel was canceled.\n")); return mStatus; @@ -9315,46 +9353,39 @@ nsHttpChannel::TriggerNetwork(int32_t aT // If a network request has already gone out, there is no point in // doing this again. if (mNetworkTriggered) { LOG((" network already triggered. Returning.\n")); return NS_OK; } - if (!aTimeout) { - mNetworkTriggered = true; - if (mNetworkTriggerTimer) { - mNetworkTriggerTimer->Cancel(); - mNetworkTriggerTimer = nullptr; - } - - // If we are waiting for a proxy request, that means we can't trigger - // the next step just yet. We need for mConnectionInfo to be non-null - // before we call TryHSTSPriming. OnProxyAvailable will trigger - // BeginConnect, and Connect will call TryHSTSPriming even if it's - // for the cache callbacks. - if (mProxyRequest) { - LOG((" proxy request in progress. Delaying network trigger.\n")); - mWaitingForProxy = true; - return NS_OK; - } - - if (mCacheAsyncOpenCalled && !mOnCacheAvailableCalled) { - mRaceCacheWithNetwork = true; - } - - LOG((" triggering network\n")); - return TryHSTSPriming(); - } - - LOG((" setting timer to trigger network: %d ms\n", aTimeout)); - mNetworkTriggerTimer = do_CreateInstance(NS_TIMER_CONTRACTID); - mNetworkTriggerTimer->InitWithCallback(this, aTimeout, nsITimer::TYPE_ONE_SHOT); - return NS_OK; + mNetworkTriggered = true; + if (mNetworkTriggerTimer) { + mNetworkTriggerTimer->Cancel(); + mNetworkTriggerTimer = nullptr; + } + + // If we are waiting for a proxy request, that means we can't trigger + // the next step just yet. We need for mConnectionInfo to be non-null + // before we call TryHSTSPriming. OnProxyAvailable will trigger + // BeginConnect, and Connect will call TryHSTSPriming even if it's + // for the cache callbacks. + if (mProxyRequest) { + LOG((" proxy request in progress. Delaying network trigger.\n")); + mWaitingForProxy = true; + return NS_OK; + } + + if (mCacheAsyncOpenCalled && !mOnCacheAvailableCalled) { + mRaceCacheWithNetwork = true; + } + + LOG((" triggering network\n")); + return TryHSTSPriming(); } nsresult nsHttpChannel::MaybeRaceCacheWithNetwork() { // Don't trigger the network if the load flags say so. if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_NO_NETWORK_IO)) { return NS_OK; @@ -9375,43 +9406,42 @@ nsHttpChannel::MaybeRaceCacheWithNetwork mRaceDelay = 0; } else { // Give cache a headstart of 3 times the average cache entry open time. mRaceDelay = CacheFileUtils::CachePerfStats::GetAverage( CacheFileUtils::CachePerfStats::ENTRY_OPEN, true) * 3; // We use microseconds in CachePerfStats but we need milliseconds // for TriggerNetwork. mRaceDelay /= 1000; - if (mRaceDelay > sRCWNMaxWaitMs) { - mRaceDelay = sRCWNMaxWaitMs; - } - } - - MOZ_ASSERT(sRCWNEnabled, "The pref must be truned on."); + } + + mRaceDelay = clamped<uint32_t>(mRaceDelay, sRCWNMinWaitMs, sRCWNMaxWaitMs); + + MOZ_ASSERT(sRCWNEnabled, "The pref must be turned on."); LOG(("nsHttpChannel::MaybeRaceCacheWithNetwork [this=%p, delay=%u]\n", this, mRaceDelay)); - return TriggerNetwork(mRaceDelay); + return TriggerNetworkWithDelay(mRaceDelay); } NS_IMETHODIMP nsHttpChannel::Test_triggerNetwork(int32_t aTimeout) { MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread"); - return TriggerNetwork(aTimeout); + return TriggerNetworkWithDelay(aTimeout); } NS_IMETHODIMP nsHttpChannel::Notify(nsITimer *aTimer) { RefPtr<nsHttpChannel> self(this); if (aTimer == mCacheOpenTimer) { return Test_triggerDelayedOpenCacheEntry(); } else if (aTimer == mNetworkTriggerTimer) { - return TriggerNetwork(0); + return TriggerNetwork(); } else { MOZ_CRASH("Unknown timer"); } return NS_OK; } bool
--- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -717,17 +717,18 @@ private: RESPONSE_FROM_NETWORK = 2 // response coming from the network }; Atomic<ResponseSource, Relaxed> mFirstResponseSource; // Determines if it's possible and advisable to race the network request // with the cache fetch, and proceeds to do so. nsresult MaybeRaceCacheWithNetwork(); - nsresult TriggerNetwork(int32_t aTimeout); + nsresult TriggerNetworkWithDelay(uint32_t aDelay); + nsresult TriggerNetwork(); void CancelNetworkRequest(nsresult aStatus); // Timer used to delay the network request, or to trigger the network // request if retrieving the cache entry takes too long. nsCOMPtr<nsITimer> mNetworkTriggerTimer; // Is true if the network request has been triggered. bool mNetworkTriggered = false; bool mWaitingForProxy = false; // Is true if the onCacheEntryAvailable callback has been called.
--- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -385,16 +385,18 @@ skip-if = os == "android" [test_bug1279246.js] [test_throttlequeue.js] [test_throttlechannel.js] [test_throttling.js] [test_separate_connections.js] [test_rusturl.js] [test_trackingProtection_annotateChannels.js] [test_race_cache_with_network.js] +# temporarily disabled because it is failing after landing bug 1398847 +skip-if = true [test_channel_priority.js] [test_bug1312774_http1.js] [test_1351443-missing-NewChannel2.js] [test_bug1312782_http1.js] [test_bug1355539_http1.js] [test_bug1378385_http1.js] [test_tls_flags_separate_connections.js] [test_tls_flags.js]
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp +++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp @@ -18,16 +18,17 @@ #include "mozilla/WindowsVersion.h" #include "nsAppDirectoryServiceDefs.h" #include "nsCOMPtr.h" #include "nsDirectoryServiceDefs.h" #include "nsIFile.h" #include "nsIProperties.h" #include "nsServiceManagerUtils.h" #include "nsString.h" +#include "nsTHashtable.h" #include "sandbox/win/src/sandbox.h" #include "sandbox/win/src/security_level.h" #include "WinUtils.h" namespace mozilla { sandbox::BrokerServices *SandboxBroker::sBrokerService = nullptr; @@ -45,16 +46,19 @@ static UniquePtr<nsString> sContentTempD static UniquePtr<nsString> sRoamingAppDataDir; static UniquePtr<nsString> sLocalAppDataDir; static LazyLogModule sSandboxBrokerLog("SandboxBroker"); #define LOG_E(...) MOZ_LOG(sSandboxBrokerLog, LogLevel::Error, (__VA_ARGS__)) #define LOG_W(...) MOZ_LOG(sSandboxBrokerLog, LogLevel::Warning, (__VA_ARGS__)) +// Used to store whether we have accumulated an error combination for this session. +static UniquePtr<nsTHashtable<nsCStringHashKey>> sLaunchErrors; + /* static */ void SandboxBroker::Initialize(sandbox::BrokerServices* aBrokerServices) { sBrokerService = aBrokerServices; wchar_t exePath[MAX_PATH]; if (!::GetModuleFileNameW(nullptr, exePath, MAX_PATH)) { @@ -130,16 +134,17 @@ SandboxBroker::SandboxBroker() } else { mPolicy = nullptr; } } bool SandboxBroker::LaunchApp(const wchar_t *aPath, const wchar_t *aArguments, + GeckoProcessType aProcessType, const bool aEnableLogging, void **aProcessHandle) { if (!sBrokerService || !mPolicy) { return false; } // Set stdout and stderr, to allow inheritance for logging. @@ -201,19 +206,35 @@ SandboxBroker::LaunchApp(const wchar_t * // Ceate the sandboxed process PROCESS_INFORMATION targetInfo = {0}; sandbox::ResultCode result; sandbox::ResultCode last_warning = sandbox::SBOX_ALL_OK; DWORD last_error = ERROR_SUCCESS; result = sBrokerService->SpawnTarget(aPath, aArguments, mPolicy, &last_warning, &last_error, &targetInfo); if (sandbox::SBOX_ALL_OK != result) { - Telemetry::Accumulate(Telemetry::SANDBOX_FAILED_LAUNCH, result); + nsAutoCString key; + key.AppendASCII(XRE_ChildProcessTypeToString(aProcessType)); + key.AppendLiteral("/0x"); + key.AppendInt(static_cast<uint32_t>(last_error), 16); + + if (!sLaunchErrors) { + sLaunchErrors = MakeUnique<nsTHashtable<nsCStringHashKey>>(); + ClearOnShutdown(&sLaunchErrors); + } + + // Only accumulate for each combination once per session. + if (!sLaunchErrors->Contains(key)) { + Telemetry::Accumulate(Telemetry::SANDBOX_FAILED_LAUNCH_KEYED, key, result); + sLaunchErrors->PutEntry(key); + } + LOG_E("Failed (ResultCode %d) to SpawnTarget with last_error=%d, last_warning=%d", result, last_error, last_warning); + return false; } else if (sandbox::SBOX_ALL_OK != last_warning) { // If there was a warning (but the result was still ok), log it and proceed. LOG_W("Warning on SpawnTarget with last_error=%d, last_warning=%d", last_error, last_warning); } // The sandboxed process is started in a suspended state, resume it now that
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.h +++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.h @@ -6,16 +6,17 @@ #ifndef __SECURITY_SANDBOX_SANDBOXBROKER_H__ #define __SECURITY_SANDBOX_SANDBOXBROKER_H__ #include <stdint.h> #include <windows.h> #include "base/child_privileges.h" +#include "nsXULAppAPI.h" namespace sandbox { class BrokerServices; class TargetPolicy; } namespace mozilla { @@ -29,16 +30,17 @@ public: /** * Cache directory paths for use in policy rules. Must be called on main * thread. */ static void CacheRulesDirectories(); bool LaunchApp(const wchar_t *aPath, const wchar_t *aArguments, + GeckoProcessType aProcessType, const bool aEnableLogging, void **aProcessHandle); virtual ~SandboxBroker(); // Security levels for different types of processes #if defined(MOZ_CONTENT_SANDBOX) void SetSecurityLevelForContentProcess(int32_t aSandboxLevel, base::ChildPrivileges aPrivs);
--- a/taskcluster/ci/test/tests.yml +++ b/taskcluster/ci/test/tests.yml @@ -1142,32 +1142,41 @@ reftest: linux64-qr/.*: 1 windows10-64-asan.*: 3 default: default reftest-gpu: description: "Reftest GPU run" suite: reftest/reftest-gpu treeherder-symbol: tc-R(Rg) + chunks: + by-test-platform: + # Remove special casing when debug isn't using BBB anymore + windows7-32.*/debug: 1 + default: 8 run-on-projects: by-test-platform: windows10.*: [] windows8-64.*: [] default: built-projects worker-type: by-test-platform: windows7-32.*/debug: buildbot-bridge/buildbot-bridge default: null instance-size: default virtualization: virtual-with-gpu max-run-time: 3600 mozharness: script: desktop_unittest.py no-read-buildbot-config: true - chunked: false + chunked: + # Remove special casing when debug isn't using BBB anymore + by-test-platform: + windows7-32.*/debug: false + default: true config: by-test-platform: windows.*: - unittests/win_taskcluster_unittest.py macosx.*: - unittests/mac_unittest.py linux.*: - unittests/linux_unittest.py
--- a/toolkit/components/extensions/Extension.jsm +++ b/toolkit/components/extensions/Extension.jsm @@ -377,17 +377,17 @@ this.ExtensionData = class { if (!this.uuid) { this.uuid = UUIDMap.get(this.id); } return `moz-extension://${this.uuid}/${path}`; } async readDirectory(path) { if (this.rootURI instanceof Ci.nsIFileURL) { - let uri = Services.io.newURI(this.rootURI.resolve("./" + path)); + let uri = Services.io.newURI("./" + path, null, this.rootURI); let fullPath = uri.QueryInterface(Ci.nsIFileURL).file.path; let iter = new OS.File.DirectoryIterator(fullPath); let results = []; try { await iter.forEach(entry => { results.push(entry); @@ -1713,14 +1713,13 @@ this.Langpack = class extends ExtensionD } else { // If the path is not a string, it's an object with path per platform // where the keys are taken from AppConstants.platform const platform = AppConstants.platform; if (platform in path) { chromeEntries.push(["locale", alias, language, path[platform]]); } } - } } return chromeEntries; } };
--- a/toolkit/components/extensions/ExtensionChild.jsm +++ b/toolkit/components/extensions/ExtensionChild.jsm @@ -39,16 +39,17 @@ Cu.import("resource://gre/modules/Extens const { DefaultMap, EventEmitter, LimitedSet, defineLazyGetter, getMessageManager, getUniqueId, + getWinUtils, withHandlingUserInput, } = ExtensionUtils; const { EventManager, LocalAPIImplementation, LocaleData, NoCloneSpreadArgs, @@ -679,18 +680,17 @@ class ProxyAPIImplementation extends Sch callFunctionNoReturn(args) { this.childApiManager.callParentFunctionNoReturn(this.path, args); } callAsyncFunction(args, callback, requireUserInput) { if (requireUserInput) { let context = this.childApiManager.context; - let winUtils = context.contentWindow.getInterface(Ci.nsIDOMWindowUtils); - if (!winUtils.isHandlingUserInput) { + if (!getWinUtils(context.contentWindow).isHandlingUserInput) { let err = new context.cloneScope.Error(`${this.path} may only be called from a user input handler`); return context.wrapPromise(Promise.reject(err), callback); } } return this.childApiManager.callParentAsyncFunction(this.path, args, callback); } addListener(listener, args) {
--- a/toolkit/components/extensions/ExtensionCommon.jsm +++ b/toolkit/components/extensions/ExtensionCommon.jsm @@ -18,17 +18,16 @@ this.EXPORTED_SYMBOLS = ["ExtensionCommo Cu.importGlobalProperties(["fetch"]); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetters(this, { ConsoleAPI: "resource://gre/modules/Console.jsm", MessageChannel: "resource://gre/modules/MessageChannel.jsm", - Preferences: "resource://gre/modules/Preferences.jsm", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", Schemas: "resource://gre/modules/Schemas.jsm", }); XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService", "@mozilla.org/content/style-sheet-service;1", "nsIStyleSheetService"); @@ -41,17 +40,17 @@ var { DefaultWeakMap, EventEmitter, ExtensionError, defineLazyGetter, filterStack, getConsole, getInnerWindowID, getUniqueId, - instanceOf, + getWinUtils, } = ExtensionUtils; XPCOMUtils.defineLazyGetter(this, "console", getConsole); var ExtensionCommon; /** * A sentinel class to indicate that an array of values should be @@ -191,18 +190,17 @@ class BaseContext { this.messageManager = null; this.docShell = null; this.contentWindow = null; this.innerWindowID = 0; } setContentWindow(contentWindow) { let {document} = contentWindow; - let docShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell); + let {docShell} = document; this.innerWindowID = getInnerWindowID(contentWindow); this.messageManager = docShell.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIContentFrameMessageManager); if (this.incognito == null) { this.incognito = PrivateBrowsingUtils.isContentWindowPrivate(contentWindow); } @@ -372,18 +370,20 @@ class BaseContext { * @param {Error|object} error * @returns {Error} */ normalizeError(error) { if (error instanceof this.cloneScope.Error) { return error; } let message, fileName; - if (instanceOf(error, "Object") || error instanceof ExtensionError || - (typeof error == "object" && this.principal.subsumes(Cu.getObjectPrincipal(error)))) { + if (error && typeof error === "object" && + (ChromeUtils.getClassName(error) === "Object" || + error instanceof ExtensionError || + this.principal.subsumes(Cu.getObjectPrincipal(error)))) { message = error.message; fileName = error.fileName; } else { Cu.reportError(error); } message = message || "An unexpected error occurred"; return new this.cloneScope.Error(message, fileName); } @@ -663,19 +663,17 @@ class LocalAPIImplementation extends Sch callFunctionNoReturn(args) { this.pathObj[this.name](...args); } callAsyncFunction(args, callback, requireUserInput) { let promise; try { if (requireUserInput) { - let winUtils = this.context.contentWindow - .getInterface(Ci.nsIDOMWindowUtils); - if (!winUtils.isHandlingUserInput) { + if (!getWinUtils(this.context.contentWindow).isHandlingUserInput) { throw new ExtensionError(`${this.name} may only be called from a user input handler`); } } promise = this.pathObj[this.name](...args) || Promise.resolve(); } catch (e) { promise = Promise.reject(e); } return this.context.wrapPromise(promise, callback); @@ -1445,69 +1443,72 @@ LocaleData.prototype = { }); }, // Validates the contents of a locale JSON file, normalizes the // messages into a Map of message key -> localized string pairs. addLocale(locale, messages, extension) { let result = new Map(); + let isPlainObject = obj => (obj && typeof obj === "object" && + ChromeUtils.getClassName(obj) === "Object"); + // Chrome does not document the semantics of its localization // system very well. It handles replacements by pre-processing // messages, replacing |$[a-zA-Z0-9@_]+$| tokens with the value of their // replacements. Later, it processes the resulting string for // |$[0-9]| replacements. // // Again, it does not document this, but it accepts any number // of sequential |$|s, and replaces them with that number minus // 1. It also accepts |$| followed by any number of sequential // digits, but refuses to process a localized string which // provides more than 9 substitutions. - if (!instanceOf(messages, "Object")) { + if (!isPlainObject(messages)) { extension.packagingError(`Invalid locale data for ${locale}`); return result; } for (let key of Object.keys(messages)) { let msg = messages[key]; - if (!instanceOf(msg, "Object") || typeof(msg.message) != "string") { + if (!isPlainObject(msg) || typeof(msg.message) != "string") { extension.packagingError(`Invalid locale message data for ${locale}, message ${JSON.stringify(key)}`); continue; } // Substitutions are case-insensitive, so normalize all of their names // to lower-case. let placeholders = new Map(); - if (instanceOf(msg.placeholders, "Object")) { + if (isPlainObject(msg.placeholders)) { for (let key of Object.keys(msg.placeholders)) { placeholders.set(key.toLowerCase(), msg.placeholders[key]); } } let replacer = (match, name) => { let replacement = placeholders.get(name.toLowerCase()); - if (instanceOf(replacement, "Object") && "content" in replacement) { + if (isPlainObject(replacement) && "content" in replacement) { return replacement.content; } return ""; }; let value = msg.message.replace(/\$([A-Za-z0-9@_]+)\$/g, replacer); // Message names are also case-insensitive, so normalize them to lower-case. result.set(key.toLowerCase(), value); } this.messages.set(locale, result); return result; }, get acceptLanguages() { - let result = Preferences.get("intl.accept_languages", "", Ci.nsIPrefLocalizedString); + let result = Services.prefs.getComplexValue("intl.accept_languages", Ci.nsIPrefLocalizedString).data; return result.split(/\s*,\s*/g); }, get uiLocale() { return Services.locale.getAppLocaleAsBCP47(); }, };
--- a/toolkit/components/extensions/ExtensionPageChild.jsm +++ b/toolkit/components/extensions/ExtensionPageChild.jsm @@ -358,18 +358,17 @@ ExtensionPageChild = { let context = this.extensionContexts.get(windowId); if (context) { if (context.extension !== extension) { throw new Error("A different extension context already exists for this frame"); } throw new Error("An extension context was already initialized for this frame"); } - let mm = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell) + let mm = contentWindow.document.docShell .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIContentFrameMessageManager); let {viewType, tabId, devtoolsToolboxInfo} = getFrameData(mm) || {}; let uri = contentWindow.document.documentURIObject; if (devtoolsToolboxInfo) {
--- a/toolkit/components/extensions/ExtensionParent.jsm +++ b/toolkit/components/extensions/ExtensionParent.jsm @@ -47,17 +47,16 @@ var { var { DefaultMap, DefaultWeakMap, ExtensionError, MessageManagerProxy, defineLazyGetter, promiseDocumentLoaded, promiseEvent, - promiseFileContents, promiseObserved, } = ExtensionUtils; const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json"; const CATEGORY_EXTENSION_MODULES = "webextension-modules"; const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas"; const CATEGORY_EXTENSION_SCRIPTS = "webextension-scripts"; @@ -480,18 +479,17 @@ class ExtensionPageContextParent extends this.extension.views.add(this); extension.emit("extension-proxy-context-load", this); } // The window that contains this context. This may change due to moving tabs. get xulWindow() { let win = this.xulBrowser.ownerGlobal; - return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem + return win.document.docShell.rootTreeItem .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); } get currentWindow() { if (this.viewType !== "background") { return this.xulWindow; } } @@ -1430,19 +1428,19 @@ StartupCache = { getBlob() { return new Uint8Array(aomStartup.encodeBlob(this._data)); }, _data: null, async _readData() { let result = new Map(); try { - let data = await promiseFileContents(this.file); + let {buffer} = await OS.File.read(this.file); - result = aomStartup.decodeBlob(data); + result = aomStartup.decodeBlob(buffer); } catch (e) { if (!e.becauseNoSuchFile) { Cu.reportError(e); } } this._data = result; return result;
--- a/toolkit/components/extensions/ExtensionPermissions.jsm +++ b/toolkit/components/extensions/ExtensionPermissions.jsm @@ -22,18 +22,18 @@ let _initPromise; async function _lazyInit() { let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_NAME); prefs = new JSONFile({path}); prefs.data = {}; try { - let blob = await ExtensionUtils.promiseFileContents(path); - prefs.data = JSON.parse(new TextDecoder().decode(blob)); + let {buffer} = await OS.File.read(path); + prefs.data = JSON.parse(new TextDecoder().decode(buffer)); } catch (e) { if (!e.becauseNoSuchFile) { Cu.reportError(e); } } } function lazyInit() {
--- a/toolkit/components/extensions/ExtensionUtils.jsm +++ b/toolkit/components/extensions/ExtensionUtils.jsm @@ -1,20 +1,17 @@ /* 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"; this.EXPORTED_SYMBOLS = ["ExtensionUtils"]; -const Ci = Components.interfaces; -const Cc = Components.classes; -const Cu = Components.utils; -const Cr = Components.results; +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI", "resource://gre/modules/Console.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); @@ -32,21 +29,16 @@ const appinfo = Cc["@mozilla.org/xre/app let nextId = 0; const uniqueProcessID = String(appinfo.uniqueProcessID); function getUniqueId() { return `${nextId++}-${uniqueProcessID}`; } -async function promiseFileContents(path) { - let res = await OS.File.read(path); - return res.buffer; -} - /** * An Error subclass for which complete error messages are always passed * to extensions, rather than being interpreted as an unknown error. */ class ExtensionError extends Error {} function filterStack(error) { @@ -58,96 +50,56 @@ function runSafeSyncWithoutClone(f, ...a try { return f(...args); } catch (e) { dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n[[Exception stack\n${filterStack(e)}Current stack\n${filterStack(Error())}]]\n`); Cu.reportError(e); } } -// Run a function and report exceptions. -function runSafeWithoutClone(f, ...args) { - if (typeof(f) != "function") { - dump(`Extension error: expected function\n${filterStack(Error())}`); - return; - } - - Promise.resolve().then(() => { - runSafeSyncWithoutClone(f, ...args); - }); -} - -// Run a function, cloning arguments into context.cloneScope, and -// report exceptions. |f| is expected to be in context.cloneScope. -function runSafeSync(context, f, ...args) { - if (context.unloaded) { - Cu.reportError("runSafeSync called after context unloaded"); - return; - } - - try { - args = Cu.cloneInto(args, context.cloneScope); - } catch (e) { - Cu.reportError(e); - dump(`runSafe failure: cloning into ${context.cloneScope}: ${e}\n\n${filterStack(Error())}`); - } - return runSafeSyncWithoutClone(f, ...args); -} - -// Run a function, cloning arguments into context.cloneScope, and -// report exceptions. |f| is expected to be in context.cloneScope. -function runSafe(context, f, ...args) { - try { - args = Cu.cloneInto(args, context.cloneScope); - } catch (e) { - Cu.reportError(e); - dump(`runSafe failure: cloning into ${context.cloneScope}: ${e}\n\n${filterStack(Error())}`); - } - if (context.unloaded) { - dump(`runSafe failure: context is already unloaded ${filterStack(new Error())}\n`); - return undefined; - } - return runSafeWithoutClone(f, ...args); -} - // Return true if the given value is an instance of the given // native type. function instanceOf(value, type) { - return {}.toString.call(value) == `[object ${type}]`; + return (value && typeof value === "object" && + ChromeUtils.getClassName(value) === type); } /** * Similar to a WeakMap, but creates a new key with the given * constructor if one is not present. */ class DefaultWeakMap extends WeakMap { constructor(defaultConstructor, init) { super(init); this.defaultConstructor = defaultConstructor; } get(key) { - if (!this.has(key)) { - this.set(key, this.defaultConstructor(key)); + let value = super.get(key); + if (value === undefined && !this.has(key)) { + value = this.defaultConstructor(key); + this.set(key, value); } - return super.get(key); + return value; } } class DefaultMap extends Map { constructor(defaultConstructor, init) { super(init); this.defaultConstructor = defaultConstructor; } get(key) { - if (!this.has(key)) { - this.set(key, this.defaultConstructor(key)); + let value = super.get(key); + if (value === undefined && !this.has(key)) { + value = this.defaultConstructor(key); + this.set(key, value); } - return super.get(key); + return value; } } const _winUtils = new DefaultWeakMap(win => { return win.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); }); const getWinUtils = win => _winUtils.get(win); @@ -182,35 +134,36 @@ class EventEmitter { * dispatchers may need to block on. * * @param {string} event * The name of the event to listen for. * @param {function(string, ...any)} listener * The listener to call when events are emitted. */ on(event, listener) { - if (!this[LISTENERS].has(event)) { - this[LISTENERS].set(event, new Set()); + let listeners = this[LISTENERS].get(event); + if (!listeners) { + listeners = new Set(); + this[LISTENERS].set(event, listeners); } - this[LISTENERS].get(event).add(listener); + listeners.add(listener); } /** * Removes the given function as a listener for the given event. * * @param {string} event * The name of the event to stop listening for. * @param {function(string, ...any)} listener * The listener function to remove. */ off(event, listener) { - if (this[LISTENERS].has(event)) { - let set = this[LISTENERS].get(event); - + let set = this[LISTENERS].get(event); + if (set) { set.delete(listener); set.delete(this[ONCE_MAP].get(listener)); if (!set.size) { this[LISTENERS].delete(event); } } } @@ -299,17 +252,17 @@ class LimitedSet extends Set { // the entire loop even after we're done truncating. if (this.size > limit) { this.delete(item); } } } add(item) { - if (!this.has(item) && this.size >= this.limit + this.slop) { + if (this.size >= this.limit + this.slop && !this.has(item)) { this.truncate(this.limit - 1); } super.add(item); } } /** * Returns a Promise which resolves when the given document's DOM has @@ -341,19 +294,17 @@ function promiseDocumentReady(doc) { * @returns {Promise<Document>} */ function promiseDocumentLoaded(doc) { if (doc.readyState == "complete") { return Promise.resolve(doc); } return new Promise(resolve => { - doc.defaultView.addEventListener("load", function(event) { - resolve(doc); - }, {once: true}); + doc.defaultView.addEventListener("load", () => resolve(doc), {once: true}); }); } /** * Returns a Promise which resolves when the given event is dispatched to the * given element. * * @param {Element} element @@ -679,22 +630,18 @@ this.ExtensionUtils = { getUniqueId, filterStack, getWinUtils, instanceOf, normalizeTime, promiseDocumentLoaded, promiseDocumentReady, promiseEvent, - promiseFileContents, promiseObserved, - runSafe, - runSafeSync, runSafeSyncWithoutClone, - runSafeWithoutClone, withHandlingUserInput, DefaultMap, DefaultWeakMap, EventEmitter, ExtensionError, LimitedSet, MessageManagerProxy, };
--- a/toolkit/components/extensions/MessageChannel.jsm +++ b/toolkit/components/extensions/MessageChannel.jsm @@ -95,20 +95,17 @@ * }, * */ this.EXPORTED_SYMBOLS = ["MessageChannel"]; /* globals MessageChannel */ -const Ci = Components.interfaces; -const Cc = Components.classes; -const Cu = Components.utils; -const Cr = Components.results; +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; Cu.import("resource://gre/modules/AppConstants.jsm"); Cu.import("resource://gre/modules/ExtensionUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); const { MessageManagerProxy, } = ExtensionUtils;
--- a/toolkit/components/extensions/Schemas.jsm +++ b/toolkit/components/extensions/Schemas.jsm @@ -16,17 +16,16 @@ Cu.importGlobalProperties(["URL"]); Cu.import("resource://gre/modules/AppConstants.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/ExtensionUtils.jsm"); var { DefaultMap, DefaultWeakMap, - instanceOf, } = ExtensionUtils; XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent", "resource://gre/modules/ExtensionParent.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); XPCOMUtils.defineLazyServiceGetter(this, "contentPolicyService", "@mozilla.org/addons/content-policy;1", @@ -1622,17 +1621,17 @@ class ObjectType extends Type { if (Object.keys(this.properties).length || this.patternProperties.length || !(this.additionalProperties instanceof AnyType)) { throw new Error("InternalError: isInstanceOf can only be used " + "with objects that are otherwise unrestricted"); } } - if (!instanceOf(value, this.isInstanceOf)) { + if (ChromeUtils.getClassName(value) !== this.isInstanceOf) { return context.error(`Object must be an instance of ${this.isInstanceOf}`, `be an instance of ${this.isInstanceOf}`); } // This is kind of a hack, but we can't normalize things that // aren't JSON, so we just return them. return this.postprocess({value}, context); }
--- a/toolkit/components/extensions/ext-cookies.js +++ b/toolkit/components/extensions/ext-cookies.js @@ -167,25 +167,25 @@ const query = function* (detailsIn, prop if (isPrivate) { storeId = PRIVATE_STORE; } else if ("storeId" in details) { storeId = details.storeId; } // We can use getCookiesFromHost for faster searching. let enumerator; - let uri; + let url; let originAttributes = { userContextId, privateBrowsingId: isPrivate ? 1 : 0, }; if ("url" in details) { try { - uri = Services.io.newURI(details.url).QueryInterface(Ci.nsIURL); - enumerator = Services.cookies.getCookiesFromHost(uri.host, originAttributes); + url = new URL(details.url); + enumerator = Services.cookies.getCookiesFromHost(url.host, originAttributes); } catch (ex) { // This often happens for about: URLs return; } } else if ("domain" in details) { enumerator = Services.cookies.getCookiesFromHost(details.domain, originAttributes); } else { enumerator = Services.cookies.getCookiesWithOriginAttributes(JSON.stringify(originAttributes)); @@ -206,31 +206,30 @@ const query = function* (detailsIn, prop // path == cookiePath, but without the redundant string compare. if (path.length == cookiePath.length) { return true; } // URL path is a substring of the cookie path, so it matches if, and // only if, the next character is a path delimiter. - let pathDelimiters = ["/", "?", "#", ";"]; - return pathDelimiters.includes(path[cookiePath.length]); + return path[cookiePath.length] === "/"; } // "Restricts the retrieved cookies to those that would match the given URL." - if (uri) { - if (!domainMatches(uri.host)) { + if (url) { + if (!domainMatches(url.host)) { return false; } - if (cookie.isSecure && uri.scheme != "https") { + if (cookie.isSecure && url.protocol != "https:") { return false; } - if (!pathMatches(uri.pathQueryRef)) { + if (!pathMatches(url.pathname)) { return false; } } if ("name" in details && details.name != cookie.name) { return false; } @@ -255,18 +254,17 @@ const query = function* (detailsIn, prop // Check that the extension has permissions for this host. if (!context.extension.whiteListedHosts.matchesCookie(cookie)) { return false; } return true; } - while (enumerator.hasMoreElements()) { - let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2); + for (const cookie of XPCOMUtils.IterSimpleEnumerator(enumerator, Ci.nsICookie2)) { if (matches(cookie)) { yield {cookie, isPrivate, storeId}; } } }; this.cookies = class extends ExtensionAPI { getAPI(context) { @@ -286,27 +284,27 @@ this.cookies = class extends ExtensionAP getAll: function(details) { let allowed = ["url", "name", "domain", "path", "secure", "session", "storeId"]; let result = Array.from(query(details, allowed, context), convertCookie); return Promise.resolve(result); }, set: function(details) { - let uri = Services.io.newURI(details.url).QueryInterface(Ci.nsIURL); + let uri = Services.io.newURI(details.url); let path; if (details.path !== null) { path = details.path; } else { // This interface essentially emulates the behavior of the // Set-Cookie header. In the case of an omitted path, the cookie // service uses the directory path of the requesting URL, ignoring // any filename or query parameters. - path = uri.directory; + path = uri.QueryInterface(Ci.nsIURL).directory; } let name = details.name !== null ? details.name : ""; let value = details.value !== null ? details.value : ""; let secure = details.secure !== null ? details.secure : false; let httpOnly = details.httpOnly !== null ? details.httpOnly : false; let isSession = details.expirationDate === null; let expiry = isSession ? Number.MAX_SAFE_INTEGER : details.expirationDate;
--- a/toolkit/components/extensions/ext-runtime.js +++ b/toolkit/components/extensions/ext-runtime.js @@ -119,22 +119,22 @@ this.runtime = class extends ExtensionAP setUninstallURL: function(url) { if (url.length == 0) { return Promise.resolve(); } let uri; try { - uri = Services.io.newURI(url); + uri = new URL(url); } catch (e) { return Promise.reject({message: `Invalid URL: ${JSON.stringify(url)}`}); } - if (uri.scheme != "http" && uri.scheme != "https") { + if (uri.protocol != "http:" && uri.protocol != "https:") { return Promise.reject({message: "url must have the scheme http or https"}); } extension.uninstallURL = url; return Promise.resolve(); }, }, };
--- a/toolkit/components/extensions/ext-tabs-base.js +++ b/toolkit/components/extensions/ext-tabs-base.js @@ -689,19 +689,18 @@ class WindowBase { } /** * @property {nsIXULWindow} xulWindow * The nsIXULWindow object for this browser window. * @readonly */ get xulWindow() { - return this.window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell) - .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor) + return this.window.document.docShell.treeOwner + .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIXULWindow); } /** * Returns true if this window is the current window for the given extension * context, false otherwise. * * @param {BaseContext} context @@ -1202,18 +1201,16 @@ class WindowTrackerBase extends EventEmi this._listeners = new DefaultMap(() => new Set()); this._statusListeners = new DefaultWeakMap(listener => { return new StatusListener(listener); }); this._windowIds = new DefaultWeakMap(window => { - window.QueryInterface(Ci.nsIInterfaceRequestor); - return getWinUtils(window).outerWindowID; }); } isBrowserWindow(window) { let {documentElement} = window.document; return documentElement.getAttribute("windowtype") === "navigator:browser";
--- a/toolkit/components/extensions/ext-theme.js +++ b/toolkit/components/extensions/ext-theme.js @@ -6,16 +6,20 @@ Cu.import("resource://gre/modules/Servic XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"); XPCOMUtils.defineLazyGetter(this, "gThemesEnabled", () => { return Services.prefs.getBoolPref("extensions.webextensions.themes.enabled"); }); +var { + getWinUtils, +} = ExtensionUtils; + const ICONS = Services.prefs.getStringPref("extensions.webextensions.themes.icons.buttons", "").split(","); /** Class representing a theme. */ class Theme { /** * Creates a theme instance. * * @param {string} baseURI The base URI of the extension, used to @@ -38,19 +42,17 @@ class Theme { * * @param {Object} details Theme part of the manifest. Supported * properties can be found in the schema under ThemeType. * @param {Object} targetWindow The window to apply the theme to. Omitting * this parameter will apply the theme globally. */ load(details, targetWindow) { if (targetWindow) { - this.lwtStyles.window = targetWindow - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils).outerWindowID; + this.lwtStyles.window = getWinUtils(targetWindow).outerWindowID; } if (details.colors) { this.loadColors(details.colors); } if (details.images) { this.loadImages(details.images);
--- a/toolkit/components/extensions/ext-toolkit.js +++ b/toolkit/components/extensions/ext-toolkit.js @@ -10,16 +10,18 @@ getContainerForCookieStoreId: false, isValidCookieStoreId:false, isContainerCookieStoreId:false, isDefaultCookieStoreId: false, isPrivateCookieStoreId:false, EventManager: false, InputEventManager: false */ XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService", "resource://gre/modules/ContextualIdentityService.jsm"); +Cu.importGlobalProperties(["URL"]); + Cu.import("resource://gre/modules/ExtensionCommon.jsm"); global.EventEmitter = ExtensionUtils.EventEmitter; global.EventManager = ExtensionCommon.EventManager; global.InputEventManager = class extends EventManager { constructor(...args) { super(...args); this.inputHandling = true;
--- a/toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js +++ b/toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js @@ -1,15 +1,22 @@ "use strict"; /* global addMessageListener, sendAsyncMessage */ Components.utils.import("resource://gre/modules/AppConstants.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); +let listener = msg => { + void (msg instanceof Components.interfaces.nsIConsoleMessage); + dump(`Console message: ${msg}\n`); +}; + +Services.console.registerListener(listener); + let getBrowserApp, getTabBrowser; if (AppConstants.MOZ_BUILD_APP === "mobile/android") { getBrowserApp = win => win.BrowserApp; getTabBrowser = tab => tab.browser; } else { getBrowserApp = win => win.gBrowser; getTabBrowser = tab => tab.linkedBrowser; } @@ -25,16 +32,18 @@ function* iterBrowserWindows() { } let initialTabs = new Map(); for (let win of iterBrowserWindows()) { initialTabs.set(win, new Set(getBrowserApp(win).tabs)); } addMessageListener("check-cleanup", extensionId => { + Services.console.unregisterListener(listener); + let results = { extraWindows: [], extraTabs: [], }; for (let win of iterBrowserWindows()) { if (initialTabs.has(win)) { let tabs = initialTabs.get(win);
--- a/toolkit/components/passwordmgr/content/passwordManager.js +++ b/toolkit/components/passwordmgr/content/passwordManager.js @@ -122,17 +122,17 @@ function setFilter(aFilterString) { let signonsTreeView = { // Keep track of which favicons we've fetched or started fetching. // Maps a login origin to a favicon URL. _faviconMap: new Map(), _filterSet: [], // Coalesce invalidations to avoid repeated flickering. _invalidateTask: new DeferredTask(() => { signonsTree.treeBoxObject.invalidateColumn(signonsTree.columns.siteCol); - }, 10), + }, 10, 0), _lastSelectedRanges: [], selection: null, rowCount: 0, setTree(tree) {}, getImageSrc(row, column) { if (column.element.getAttribute("id") !== "siteCol") { return "";
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -12575,25 +12575,26 @@ "bug_numbers": [1286865], "expires_in_version": "never", "releaseChannelCollection": "opt-out", "kind": "count", "keyed": true, "cpp_guard": "XP_LINUX", "description": "System calls blocked by a seccomp-bpf sandbox policy; limited to syscalls where we would crash on Nightly. The key is generally the architecture and syscall ID but in some cases we include non-personally-identifying information from the syscall arguments; see the function SubmitToTelemetry in security/sandbox/linux/reporter/SandboxReporter.cpp for details." }, - "SANDBOX_FAILED_LAUNCH": { + "SANDBOX_FAILED_LAUNCH_KEYED": { "record_in_processes": ["main"], "alert_emails": ["bowen@mozilla.com"], - "expires_in_version": "60", - "kind": "enumerated", + "expires_in_version": "never", + "kind": "enumerated", + "keyed": true, "n_values": 50, "bug_numbers": [1368600], "cpp_guard": "XP_WIN", - "description": "Error code when a Windows sandboxed process fails to launch. See https://dxr.mozilla.org/mozilla-central/search?q=ResultCode++path%3Asandbox_types.h&redirect=true for definitions of the error codes." + "description": "Error code when a Windows sandboxed process fails to launch, keyed by process type and Windows error code. See https://dxr.mozilla.org/mozilla-central/search?q=ResultCode++path%3Asandbox_types.h&redirect=true for definitions of the error codes." }, "SYNC_WORKER_OPERATION": { "record_in_processes": ["main", "content"], "alert_emails": ["amarchesini@mozilla.com", "khuey@mozilla.com" ], "bug_numbers": [1267904], "expires_in_version": "never", "kind": "exponential", "high": 5000,
--- a/toolkit/components/telemetry/TelemetryController.jsm +++ b/toolkit/components/telemetry/TelemetryController.jsm @@ -730,17 +730,18 @@ var Impl = { TelemetryModules.start(); this._delayedInitTaskDeferred.resolve(); } catch (e) { this._delayedInitTaskDeferred.reject(e); } finally { this._delayedInitTask = null; } - }, this._testMode ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY); + }, this._testMode ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY, + this._testMode ? 0 : undefined); AsyncShutdown.sendTelemetry.addBlocker("TelemetryController: shutting down", () => this.shutdown(), () => this._getState()); this._delayedInitTask.arm(); return this._delayedInitTaskDeferred.promise; },
--- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -1536,17 +1536,18 @@ var Impl = { this._initialized = true; this.attachObservers(); this.gatherMemory(); if (Telemetry.canRecordExtended) { GCTelemetry.init(); } - }, testing ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY); + }, testing ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY, + testing ? 0 : undefined); delayedTask.arm(); }, getFlashVersion: function getFlashVersion() { let host = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); let tags = host.getPluginTags();
--- a/toolkit/modules/DeferredTask.jsm +++ b/toolkit/modules/DeferredTask.jsm @@ -85,41 +85,48 @@ this.EXPORTED_SYMBOLS = [ // Globals const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils", "resource://gre/modules/PromiseUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer", "initWithCallback"); // DeferredTask /** * Sets up a task whose execution can be triggered after a delay. * * @param aTaskFn - * Function or generator function to execute. This argument is passed to - * the "Task.spawn" method every time the task should be executed. This + * Function to execute. If the function returns a promise, the task is + * not considered complete until that promise resolves. This * task is never re-entered while running. * @param aDelayMs * Time between executions, in milliseconds. Multiple attempts to run * the task before the delay has passed are coalesced. This time of * inactivity is guaranteed to pass between multiple executions of the * task, except on finalization, when the task may restart immediately * after the previous execution finished. + * @param aIdleTimeoutMs + * The maximum time to wait for an idle slot on the main thread after + * aDelayMs have elapsed. If omitted, waits indefinitely for an idle + * callback. */ -this.DeferredTask = function(aTaskFn, aDelayMs) { +this.DeferredTask = function(aTaskFn, aDelayMs, aIdleTimeoutMs) { this._taskFn = aTaskFn; this._delayMs = aDelayMs; + this._timeoutMs = aIdleTimeoutMs; + + if (aTaskFn.isGenerator()) { + Cu.reportError(new Error("Unexpected generator function passed to DeferredTask")); + } } this.DeferredTask.prototype = { /** * Function or generator function to execute. */ _taskFn: null, @@ -292,23 +299,24 @@ this.DeferredTask.prototype = { // Indicate that the execution of the task has finished. This happens // synchronously with the previous state changes in the function. this._runningPromise = null; })().catch(Cu.reportError)); }, /** - * Executes the associated task and catches exceptions. + * Executes the associated task in an idle callback and catches exceptions. */ async _runTask() { try { - let result = this._taskFn(); - if (Object.prototype.toString.call(result) == "[object Generator]") { - await Task.spawn(result); // eslint-disable-line mozilla/no-task + // If we're being finalized, execute the task immediately, so we don't + // risk blocking async shutdown longer than necessary. + if (this._finalized || this._timeoutMs === 0) { + await this._taskFn(); } else { - await result; + await PromiseUtils.idleDispatch(this._taskFn, this._timeoutMs); } } catch (ex) { Cu.reportError(ex); } }, };
--- a/toolkit/modules/JSONFile.jsm +++ b/toolkit/modules/JSONFile.jsm @@ -32,17 +32,16 @@ this.EXPORTED_SYMBOLS = [ "JSONFile", ]; // Globals const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown", "resource://gre/modules/AsyncShutdown.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "OS",
--- a/toolkit/modules/PromiseUtils.jsm +++ b/toolkit/modules/PromiseUtils.jsm @@ -1,28 +1,55 @@ /* 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" this.EXPORTED_SYMBOLS = ["PromiseUtils"]; +Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Timer.jsm"); this.PromiseUtils = { /* * Creates a new pending Promise and provide methods to resolve and reject this Promise. * * @return {Deferred} an object consisting of a pending Promise "promise" * and methods "resolve" and "reject" to change its state. */ defer() { return new Deferred(); }, + + /** + * Requests idle dispatch to the main thread for the given callback, + * and returns a promise which resolves to the callback's return value + * when it has been executed. + * + * @param {function} callback + * @param {integer} [timeout] + * An optional timeout, after which the callback will be + * executed immediately if idle dispatch has not yet occurred. + * + * @returns {Promise} + */ + idleDispatch(callback, timeout = 0) { + return new Promise((resolve, reject) => { + Services.tm.idleDispatchToMainThread( + () => { + try { + resolve(callback()); + } catch (e) { + reject(e); + } + }, + timeout); + }); + }, } /** * The definition of Deferred object which is returned by PromiseUtils.defer(), * It contains a Promise and methods to resolve/reject it. */ function Deferred() { /* A method to resolve the associated Promise with the value passed.
--- a/toolkit/modules/addons/WebRequest.jsm +++ b/toolkit/modules/addons/WebRequest.jsm @@ -81,18 +81,17 @@ class HeaderChanger { this.originalHeaders = new Map(); for (let [name, value] of this.iterHeaders()) { this.originalHeaders.set(name.toLowerCase(), {name, value}); } } toArray() { - return Array.from(this.originalHeaders, - ([key, {name, value}]) => ({name, value})); + return Array.from(this.originalHeaders.values()); } validateHeaders(headers) { // We should probably use schema validation for this. if (!Array.isArray(headers)) { return false; } @@ -668,39 +667,40 @@ HttpObserverManager = { if (policyType == "websocket" && ["http", "https"].includes(uri.scheme)) { uri = Services.io.newURI(`ws${uri.spec.substring(4)}`); } if (filter.types && !filter.types.includes(policyType)) { return false; } - return WebRequestCommon.urlMatches(uri, filter.urls); + return !filter.urls || filter.urls.matches(uri); }, get resultsMap() { delete this.resultsMap; this.resultsMap = new Map(Object.keys(Cr).map(name => [Cr[name], name])); return this.resultsMap; }, - maybeError({channel}, extraData = null) { - if (!(extraData && extraData.error) && channel.securityInfo) { - let securityInfo = channel.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); + maybeError({channel}) { + // FIXME: Move to ChannelWrapper. + + let {securityInfo} = channel; + if (securityInfo instanceof Ci.nsITransportSecurityInfo) { if (NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) { let nsresult = NSSErrorsService.getXPCOMFromNSSError(securityInfo.errorCode); - extraData = {error: NSSErrorsService.getErrorMessage(nsresult)}; + return {error: NSSErrorsService.getErrorMessage(nsresult)}; } } - if (!(extraData && extraData.error)) { - if (!Components.isSuccessCode(channel.status)) { - extraData = {error: this.resultsMap.get(channel.status) || "NS_ERROR_NET_UNKNOWN"}; - } + + if (!Components.isSuccessCode(channel.status)) { + return {error: this.resultsMap.get(channel.status) || "NS_ERROR_NET_UNKNOWN"}; } - return extraData; + return null; }, errorCheck(channel) { let errorData = this.maybeError(channel); if (errorData) { this.runChannelListener(channel, "onError", errorData); } return errorData; @@ -801,17 +801,17 @@ HttpObserverManager = { commonData = this.getRequestData(channel, extraData); if (includeStatus) { commonData.statusCode = channel.statusCode; commonData.statusLine = channel.statusLine; } } let data = Object.assign({}, commonData); - if (registerFilter) { + if (registerFilter && opts.blocking) { this.registerChannel(channel, opts); } if (opts.requestHeaders) { requestHeaders = requestHeaders || new RequestHeaderChanger(channel); data.requestHeaders = requestHeaders.toArray(); } @@ -838,38 +838,31 @@ HttpObserverManager = { } catch (e) { Cu.reportError(e); } return this.applyChanges(kind, channel, handlerResults, requestHeaders, responseHeaders); }, async applyChanges(kind, channel, handlerResults, requestHeaders, responseHeaders) { - let asyncHandlers = handlerResults.filter(({result}) => isThenable(result)); - let isAsync = asyncHandlers.length > 0; - let shouldResume = false; + let shouldResume = !channel.suspended; try { - if (isAsync) { - shouldResume = !channel.suspended; - channel.suspended = true; - - for (let value of asyncHandlers) { + for (let {opts, result} of handlerResults) { + if (isThenable(result)) { + channel.suspended = true; try { - value.result = await value.result; + result = await result; } catch (e) { Cu.reportError(e); - value.result = {}; + continue; } - } - } - - for (let {opts, result} of handlerResults) { - if (!result || typeof result !== "object") { - continue; + if (!result || typeof result !== "object") { + continue; + } } if (result.cancel) { channel.suspended = false; channel.cancel(Cr.NS_ERROR_ABORT); this.errorCheck(channel); return; @@ -888,32 +881,30 @@ HttpObserverManager = { if (opts.requestHeaders && result.requestHeaders && requestHeaders) { requestHeaders.applyChanges(result.requestHeaders); } if (opts.responseHeaders && result.responseHeaders && responseHeaders) { responseHeaders.applyChanges(result.responseHeaders); } - if (kind === "authRequired" && opts.blocking && result.authCredentials) { - if (channel.authPromptCallback) { - channel.authPromptCallback(result.authCredentials); - } + if (kind === "authRequired" && result.authCredentials && channel.authPromptCallback) { + channel.authPromptCallback(result.authCredentials); } } // If a listener did not cancel the request or provide credentials, we // forward the auth request to the base handler. - if (kind === "authRequired") { - if (channel.authPromptForward) { - channel.authPromptForward(); - } + if (kind === "authRequired" && channel.authPromptForward) { + channel.authPromptForward(); } - if (kind === "modify") { + if (kind === "modify" && this.listeners.afterModify.size) { await this.runChannelListener(channel, "afterModify"); + } else if (kind !== "onError") { + this.errorCheck(channel); } } catch (e) { Cu.reportError(e); } // Only resume the channel if it was suspended by this call. if (shouldResume) { channel.suspended = false;
--- a/toolkit/modules/tests/xpcshell/test_DeferredTask.js +++ b/toolkit/modules/tests/xpcshell/test_DeferredTask.js @@ -187,28 +187,16 @@ add_test(function test_arm_async_functio await Promise.resolve(); run_next_test(); }, 50); deferredTask.arm(); }); /** - * Checks that "arm" accepts a Task.jsm generator function. - */ -add_test(function test_arm_async_generator() { - let deferredTask = new DeferredTask(function* () { - yield Promise.resolve(); - run_next_test(); - }, 50); - - deferredTask.arm(); -}); - -/** * Checks that an armed task can be disarmed. */ add_test(function test_disarm() { // Create a task that will run later. let deferredTask = new DeferredTask(function() { do_throw("This task should not run."); }, 2 * T); deferredTask.arm();
--- a/toolkit/mozapps/extensions/DeferredSave.jsm +++ b/toolkit/mozapps/extensions/DeferredSave.jsm @@ -183,17 +183,17 @@ this.DeferredSave.prototype = { _startTimer() { if (!this._pending) { return; } this.logger.debug("Starting timer"); if (!this._timer) this._timer = MakeTimer(); - this._timer.initWithCallback(() => this._deferredSave(), + this._timer.initWithCallback(() => this._timerCallback(), this._delay, Ci.nsITimer.TYPE_ONE_SHOT); }, /** * Mark the current stored data dirty, and schedule a flush to disk * @return A Promise<integer> that will be resolved after the data is written to disk; * the promise is resolved with the number of bytes written. */ @@ -207,16 +207,20 @@ this.DeferredSave.prototype = { this._pending = PromiseUtils.defer(); // Wait until the most recent write completes or fails (if it hasn't already) // and then restart our timer this._writing.then(count => this._startTimer(), error => this._startTimer()); } return this._pending.promise; }, + _timerCallback() { + Services.tm.idleDispatchToMainThread(() => this._deferredSave()); + }, + _deferredSave() { let pending = this._pending; this._pending = null; let writing = this._writing; this._writing = pending.promise; // In either the success or the exception handling case, we don't need to handle // the error from _writing here; it's already being handled in another then()
--- a/xpcom/base/nsCycleCollector.cpp +++ b/xpcom/base/nsCycleCollector.cpp @@ -2285,17 +2285,17 @@ CCGraphBuilder::DoneAddingRoots() mGraph.mRootCount = mGraph.MapCount(); mCurrNode = new NodePool::Enumerator(mGraph.mNodes); } MOZ_NEVER_INLINE bool CCGraphBuilder::BuildGraph(SliceBudget& aBudget) { - const intptr_t kNumNodesBetweenTimeChecks = 1000; + const intptr_t kNumNodesBetweenTimeChecks = 500; const intptr_t kStep = SliceBudget::CounterReset / kNumNodesBetweenTimeChecks; MOZ_ASSERT(mCurrNode); while (!aBudget.isOverBudget() && !mCurrNode->IsDone()) { PtrInfo* pi = mCurrNode->GetNext(); if (!pi) { MOZ_CRASH();
--- a/xpcom/threads/LabeledEventQueue.cpp +++ b/xpcom/threads/LabeledEventQueue.cpp @@ -8,18 +8,38 @@ #include "mozilla/dom/TabChild.h" #include "mozilla/dom/TabGroup.h" #include "mozilla/Scheduler.h" #include "mozilla/SchedulerGroup.h" #include "nsQueryObject.h" using namespace mozilla::dom; +LinkedList<SchedulerGroup>* LabeledEventQueue::sSchedulerGroups; +size_t LabeledEventQueue::sLabeledEventQueueCount; +SchedulerGroup* LabeledEventQueue::sCurrentSchedulerGroup; + LabeledEventQueue::LabeledEventQueue() { + // LabeledEventQueue should only be used by one consumer since it uses a + // single static sSchedulerGroups field. It's hard to assert this, though, so + // we assert NS_IsMainThread(), which is a reasonable proxy. + MOZ_ASSERT(NS_IsMainThread()); + + if (sLabeledEventQueueCount++ == 0) { + sSchedulerGroups = new LinkedList<SchedulerGroup>(); + } +} + +LabeledEventQueue::~LabeledEventQueue() +{ + if (--sLabeledEventQueueCount == 0) { + delete sSchedulerGroups; + sSchedulerGroups = nullptr; + } } static SchedulerGroup* GetSchedulerGroup(nsIRunnable* aEvent) { RefPtr<SchedulerGroup::Runnable> groupRunnable = do_QueryObject(aEvent); if (!groupRunnable) { // It's not labeled. @@ -89,18 +109,24 @@ LabeledEventQueue::PutEvent(already_AddR } mNumEvents++; epoch->mNumEvents++; RunnableEpochQueue* queue = isLabeled ? mLabeled.LookupOrAdd(group) : &mUnlabeled; queue->Push(QueueEntry(event.forget(), epoch->mEpochNumber)); - if (group && !group->isInList()) { - mSchedulerGroups.insertBack(group); + if (group && group->EnqueueEvent() == SchedulerGroup::NewlyQueued) { + // This group didn't have any events before. Add it to the + // sSchedulerGroups list. + MOZ_ASSERT(!group->isInList()); + sSchedulerGroups->insertBack(group); + if (!sCurrentSchedulerGroup) { + sCurrentSchedulerGroup = group; + } } } void LabeledEventQueue::PopEpoch() { Epoch& epoch = mEpochs.FirstElement(); MOZ_ASSERT(epoch.mNumEvents > 0); @@ -108,85 +134,119 @@ LabeledEventQueue::PopEpoch() mEpochs.Pop(); } else { epoch.mNumEvents--; } mNumEvents--; } +// Returns the next SchedulerGroup after |aGroup| in sSchedulerGroups. Wraps +// around to the beginning of the list when we hit the end. +/* static */ SchedulerGroup* +LabeledEventQueue::NextSchedulerGroup(SchedulerGroup* aGroup) +{ + SchedulerGroup* result = aGroup->getNext(); + if (!result) { + result = sSchedulerGroups->getFirst(); + } + return result; +} + already_AddRefed<nsIRunnable> LabeledEventQueue::GetEvent(EventPriority* aPriority, const MutexAutoLock& aProofOfLock) { if (mEpochs.IsEmpty()) { return nullptr; } Epoch epoch = mEpochs.FirstElement(); if (!epoch.IsLabeled()) { QueueEntry entry = mUnlabeled.FirstElement(); - if (IsReadyToRun(entry.mRunnable, nullptr)) { - PopEpoch(); - mUnlabeled.Pop(); - MOZ_ASSERT(entry.mEpochNumber == epoch.mEpochNumber); - MOZ_ASSERT(entry.mRunnable.get()); - return entry.mRunnable.forget(); + if (!IsReadyToRun(entry.mRunnable, nullptr)) { + return nullptr; } + + PopEpoch(); + mUnlabeled.Pop(); + MOZ_ASSERT(entry.mEpochNumber == epoch.mEpochNumber); + MOZ_ASSERT(entry.mRunnable.get()); + return entry.mRunnable.forget(); + } + + if (!sCurrentSchedulerGroup) { + return nullptr; } // Move active tabs to the front of the queue. The mAvoidActiveTabCount field // prevents us from preferentially processing events from active tabs twice in // a row. This scheme is designed to prevent starvation. if (TabChild::HasActiveTabs() && mAvoidActiveTabCount <= 0) { for (TabChild* tabChild : TabChild::GetActiveTabs()) { SchedulerGroup* group = tabChild->TabGroup(); - if (!group->isInList() || group == mSchedulerGroups.getFirst()) { + if (!group->isInList() || group == sCurrentSchedulerGroup) { continue; } // For each active tab we move to the front of the queue, we have to // process two SchedulerGroups (the active tab and another one, presumably // a background group) before we prioritize active tabs again. mAvoidActiveTabCount += 2; - group->removeFrom(mSchedulerGroups); - mSchedulerGroups.insertFront(group); + // We move |group| right before sCurrentSchedulerGroup and then set + // sCurrentSchedulerGroup to group. + MOZ_ASSERT(group != sCurrentSchedulerGroup); + group->removeFrom(*sSchedulerGroups); + sCurrentSchedulerGroup->setPrevious(group); + sCurrentSchedulerGroup = group; } } - // Iterate over SchedulerGroups in order. Each time we pass by a - // SchedulerGroup, we move it to the back of the list. This ensures that we - // process SchedulerGroups in a round-robin order (ignoring active tab - // prioritization). - SchedulerGroup* firstGroup = mSchedulerGroups.getFirst(); + // Iterate over each SchedulerGroup once, starting at sCurrentSchedulerGroup. + SchedulerGroup* firstGroup = sCurrentSchedulerGroup; SchedulerGroup* group = firstGroup; do { - RunnableEpochQueue* queue = mLabeled.Get(group); - MOZ_ASSERT(queue); - MOZ_ASSERT(!queue->IsEmpty()); + mAvoidActiveTabCount--; - mAvoidActiveTabCount--; - SchedulerGroup* next = group->removeAndGetNext(); - mSchedulerGroups.insertBack(group); + RunnableEpochQueue* queue = mLabeled.Get(group); + if (!queue) { + // This can happen if |group| is in a different LabeledEventQueue than |this|. + group = NextSchedulerGroup(group); + continue; + } + MOZ_ASSERT(!queue->IsEmpty()); QueueEntry entry = queue->FirstElement(); if (entry.mEpochNumber == epoch.mEpochNumber && IsReadyToRun(entry.mRunnable, group)) { + sCurrentSchedulerGroup = NextSchedulerGroup(group); + PopEpoch(); + if (group->DequeueEvent() == SchedulerGroup::NoLongerQueued) { + // Now we can take group out of sSchedulerGroups. + if (sCurrentSchedulerGroup == group) { + // Since we changed sCurrentSchedulerGroup above, we'll only get here + // if |group| was the only element in sSchedulerGroups. In that case + // set sCurrentSchedulerGroup to null. + MOZ_ASSERT(group->getNext() == nullptr); + MOZ_ASSERT(group->getPrevious() == nullptr); + sCurrentSchedulerGroup = nullptr; + } + group->removeFrom(*sSchedulerGroups); + } queue->Pop(); if (queue->IsEmpty()) { mLabeled.Remove(group); - group->removeFrom(mSchedulerGroups); } return entry.mRunnable.forget(); } - group = next; + group = NextSchedulerGroup(group); } while (group != firstGroup); return nullptr; } bool LabeledEventQueue::IsEmpty(const MutexAutoLock& aProofOfLock) {
--- a/xpcom/threads/LabeledEventQueue.h +++ b/xpcom/threads/LabeledEventQueue.h @@ -25,16 +25,17 @@ class SchedulerGroup; // from its queue. Ideally the heuristic should give precedence to // SchedulerGroups corresponding to the foreground tabs. The correctness of this // data structure relies on the invariant that events from different // SchedulerGroups cannot affect each other. class LabeledEventQueue final : public AbstractEventQueue { public: LabeledEventQueue(); + ~LabeledEventQueue(); void PutEvent(already_AddRefed<nsIRunnable>&& aEvent, EventPriority aPriority, const MutexAutoLock& aProofOfLock) final; already_AddRefed<nsIRunnable> GetEvent(EventPriority* aPriority, const MutexAutoLock& aProofOfLock) final; bool IsEmpty(const MutexAutoLock& aProofOfLock) final; @@ -122,23 +123,30 @@ private: Epoch NextEpoch(bool aIsLabeled) const { MOZ_ASSERT(aIsLabeled == !IsLabeled()); return Epoch(mEpochNumber + 1, aIsLabeled); } }; void PopEpoch(); + static SchedulerGroup* NextSchedulerGroup(SchedulerGroup* aGroup); using RunnableEpochQueue = Queue<QueueEntry, 32>; using LabeledMap = nsClassHashtable<nsRefPtrHashKey<SchedulerGroup>, RunnableEpochQueue>; using EpochQueue = Queue<Epoch, 8>; - // List of SchedulerGroups that have events in the queue. - LinkedList<SchedulerGroup> mSchedulerGroups; + // List of SchedulerGroups that might have events. This is static, so it + // covers all LabeledEventQueues. If a SchedulerGroup is in this list, it may + // not have an event in *this* LabeledEventQueue (although it will have an + // event in *some* LabeledEventQueue). sCurrentSchedulerGroup cycles through + // the elements of sSchedulerGroups in order. + static LinkedList<SchedulerGroup>* sSchedulerGroups; + static size_t sLabeledEventQueueCount; + static SchedulerGroup* sCurrentSchedulerGroup; LabeledMap mLabeled; RunnableEpochQueue mUnlabeled; EpochQueue mEpochs; size_t mNumEvents = 0; // Number of SchedulerGroups that must be processed before we prioritize an // active tab. This field is designed to guarantee a 1:1 interleaving between
--- a/xpcom/threads/PrioritizedEventQueue.cpp +++ b/xpcom/threads/PrioritizedEventQueue.cpp @@ -117,17 +117,17 @@ template<class InnerQueueT> EventPriority PrioritizedEventQueue<InnerQueueT>::SelectQueue(bool aUpdateState, const MutexAutoLock& aProofOfLock) { bool highPending = !mHighQueue->IsEmpty(aProofOfLock); bool normalPending = !mNormalQueue->IsEmpty(aProofOfLock); size_t inputCount = mInputQueue->Count(aProofOfLock); - if (aUpdateState && mInputQueueState == STATE_ENABLED && + if (mInputQueueState == STATE_ENABLED && mInputHandlingStartTime.IsNull() && inputCount > 0) { mInputHandlingStartTime = InputEventStatistics::Get() .GetInputHandlingStartTime(inputCount); } // We check the different queues in the following order. The conditions we use // are meant to avoid starvation and to ensure that we don't process an event @@ -269,17 +269,17 @@ PrioritizedEventQueue<InnerQueueT>::HasR { mHasPendingEventsPromisedIdleEvent = false; EventPriority queue = SelectQueue(false, aProofOfLock); if (queue == EventPriority::High) { return mHighQueue->HasReadyEvent(aProofOfLock); } else if (queue == EventPriority::Input) { - return mIdleQueue->HasReadyEvent(aProofOfLock); + return mInputQueue->HasReadyEvent(aProofOfLock); } else if (queue == EventPriority::Normal) { return mNormalQueue->HasReadyEvent(aProofOfLock); } MOZ_ASSERT(queue == EventPriority::Idle); // If we get here, then both the high and normal queues are empty.
--- a/xpcom/threads/SchedulerGroup.h +++ b/xpcom/threads/SchedulerGroup.h @@ -70,16 +70,46 @@ public: } // Ensure that it's valid to access the TabGroup at this time. void ValidateAccess() const { MOZ_ASSERT(IsSafeToRun()); } + enum EnqueueStatus + { + NewlyQueued, + AlreadyQueued, + }; + + // Records that this SchedulerGroup had an event enqueued in some + // queue. Returns whether the SchedulerGroup was already in a queue before + // EnqueueEvent() was called. + EnqueueStatus EnqueueEvent() + { + mEventCount++; + return mEventCount == 1 ? NewlyQueued : AlreadyQueued; + } + + enum DequeueStatus + { + StillQueued, + NoLongerQueued, + }; + + // Records that this SchedulerGroup had an event dequeued from some + // queue. Returns whether the SchedulerGroup is still in a queue after + // DequeueEvent() returns. + DequeueStatus DequeueEvent() + { + mEventCount--; + return mEventCount == 0 ? NoLongerQueued : StillQueued; + } + class Runnable final : public mozilla::Runnable , public nsIRunnablePriority , public nsILabelableRunnable { public: Runnable(already_AddRefed<nsIRunnable>&& aRunnable, SchedulerGroup* aGroup); @@ -162,16 +192,20 @@ protected: // Shuts down this dispatcher. If aXPCOMShutdown is true, invalidates this // dispatcher. void Shutdown(bool aXPCOMShutdown); static MOZ_THREAD_LOCAL(bool) sTlsValidatingAccess; bool mIsRunning; + // Number of events that are currently enqueued for this SchedulerGroup + // (across all queues). + size_t mEventCount = 0; + nsCOMPtr<nsISerialEventTarget> mEventTargets[size_t(TaskCategory::Count)]; RefPtr<AbstractThread> mAbstractThreads[size_t(TaskCategory::Count)]; }; NS_DEFINE_STATIC_IID_ACCESSOR(SchedulerGroup::Runnable, NS_SCHEDULERGROUPRUNNABLE_IID); } // namespace mozilla