diff --git a/content/base/src/nsGenericElement.cpp b/content/base/src/nsGenericElement.cpp index d74e266ded4d..ca1f3cfb4f91 100644 --- a/content/base/src/nsGenericElement.cpp +++ b/content/base/src/nsGenericElement.cpp @@ -157,6 +157,8 @@ #include "nsCycleCollector.h" #include "xpcpublic.h" #include "xpcprivate.h" +#include "nsLayoutStatics.h" +#include "mozilla/Telemetry.h" using namespace mozilla; using namespace mozilla::dom; @@ -4374,6 +4376,102 @@ nsINode::IsEqualNode(nsIDOMNode* aOther, bool* aReturn) NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericElement) +#define SUBTREE_UNBINDINGS_PER_RUNNABLE 500 + +class ContentUnbinder : public nsRunnable +{ +public: + ContentUnbinder() + { + nsLayoutStatics::AddRef(); + mLast = this; + } + + ~ContentUnbinder() + { + Run(); + nsLayoutStatics::Release(); + } + + void UnbindSubtree(nsIContent* aNode) + { + if (aNode->NodeType() != nsIDOMNode::ELEMENT_NODE && + aNode->NodeType() != nsIDOMNode::DOCUMENT_FRAGMENT_NODE) { + return; + } + nsGenericElement* container = static_cast(aNode); + PRUint32 childCount = container->mAttrsAndChildren.ChildCount(); + if (childCount) { + while (childCount-- > 0) { + // Hold a strong ref to the node when we remove it, because we may be + // the last reference to it. We need to call TakeChildAt() and + // update mFirstChild before calling UnbindFromTree, since this last + // can notify various observers and they should really see consistent + // tree state. + nsCOMPtr child = + container->mAttrsAndChildren.TakeChildAt(childCount); + if (childCount == 0) { + container->mFirstChild = nsnull; + } + UnbindSubtree(child); + child->UnbindFromTree(); + } + } + } + + NS_IMETHOD Run() + { + nsAutoScriptBlocker scriptBlocker; + PRUint32 len = mSubtreeRoots.Length(); + if (len) { + PRTime start = PR_Now(); + for (PRUint32 i = 0; i < len; ++i) { + UnbindSubtree(mSubtreeRoots[i]); + } + mSubtreeRoots.Clear(); + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_CONTENT_UNBIND, + PRUint32(PR_Now() - start) / PR_USEC_PER_MSEC); + } + if (this == sContentUnbinder) { + sContentUnbinder = nsnull; + if (mNext) { + nsRefPtr next; + next.swap(mNext); + sContentUnbinder = next; + next->mLast = mLast; + mLast = nsnull; + NS_DispatchToMainThread(next); + } + } + return NS_OK; + } + + static void Append(nsIContent* aSubtreeRoot) + { + if (!sContentUnbinder) { + sContentUnbinder = new ContentUnbinder(); + nsCOMPtr e = sContentUnbinder; + NS_DispatchToMainThread(e); + } + + if (sContentUnbinder->mLast->mSubtreeRoots.Length() >= + SUBTREE_UNBINDINGS_PER_RUNNABLE) { + sContentUnbinder->mLast->mNext = new ContentUnbinder(); + sContentUnbinder->mLast = sContentUnbinder->mLast->mNext; + } + sContentUnbinder->mLast->mSubtreeRoots.AppendElement(aSubtreeRoot); + } + +private: + nsAutoTArray, + SUBTREE_UNBINDINGS_PER_RUNNABLE> mSubtreeRoots; + nsRefPtr mNext; + ContentUnbinder* mLast; + static ContentUnbinder* sContentUnbinder; +}; + +ContentUnbinder* ContentUnbinder::sContentUnbinder = nsnull; + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGenericElement) nsINode::Unlink(tmp); @@ -4383,16 +4481,12 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGenericElement) } // Unlink child content (and unbind our subtree). - { + if (UnoptimizableCCNode(tmp) || !nsCCUncollectableMarker::sGeneration) { PRUint32 childCount = tmp->mAttrsAndChildren.ChildCount(); if (childCount) { // Don't allow script to run while we're unbinding everything. nsAutoScriptBlocker scriptBlocker; while (childCount-- > 0) { - // Once we have XPCOMGC we shouldn't need to call UnbindFromTree. - // We could probably do a non-deep unbind here when IsInDoc is false - // for better performance. - // Hold a strong ref to the node when we remove it, because we may be // the last reference to it. We need to call TakeChildAt() and // update mFirstChild before calling UnbindFromTree, since this last @@ -4405,7 +4499,12 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGenericElement) child->UnbindFromTree(); } } - } + } else if (!tmp->GetParent() && tmp->mAttrsAndChildren.ChildCount()) { + ContentUnbinder::Append(tmp); + } /* else { + The subtree root will end up to a ContentUnbinder, and that will + unbind the child nodes. + } */ // Unlink any DOM slots of interest. { diff --git a/content/base/src/nsGenericElement.h b/content/base/src/nsGenericElement.h index ab71557c9da7..428ccfcd8400 100644 --- a/content/base/src/nsGenericElement.h +++ b/content/base/src/nsGenericElement.h @@ -82,6 +82,7 @@ class nsIScrollableFrame; class nsAttrValueOrString; class nsContentList; class nsDOMTokenList; +class ContentUnbinder; struct nsRect; typedef PRUptrdiff PtrBits; @@ -956,6 +957,7 @@ protected: */ virtual void GetLinkTarget(nsAString& aTarget); + friend class ContentUnbinder; /** * Array containing all attributes and children for this element */ diff --git a/content/canvas/src/WebGLContext.h b/content/canvas/src/WebGLContext.h index 04487477b252..652fc89e7630 100644 --- a/content/canvas/src/WebGLContext.h +++ b/content/canvas/src/WebGLContext.h @@ -64,6 +64,10 @@ #include "CheckedInt.h" +#ifdef XP_MACOSX +#include "ForceDiscreteGPUHelperCGL.h" +#endif + /* * Minimum value constants defined in 6.2 State Tables of OpenGL ES - 2.0.25 * https://bugzilla.mozilla.org/show_bug.cgi?id=686732 @@ -943,6 +947,16 @@ protected: bool mContextLostErrorSet; bool mContextLostDueToTest; +#ifdef XP_MACOSX + // see bug 713305. This RAII helper guarantees that we're on the discrete GPU, during its lifetime + // Debouncing note: we don't want to switch GPUs too frequently, so try to not create and destroy + // these objects at high frequency. Having WebGLContext's hold one such object seems fine, + // because WebGLContext objects only go away during GC, which shouldn't happen too frequently. + // If in the future GC becomes much more frequent, we may have to revisit then (maybe use a timer). + ForceDiscreteGPUHelperCGL mForceDiscreteGPUHelper; +#endif + + public: // console logging helpers static void LogMessage(const char *fmt, ...); diff --git a/content/canvas/test/webgl/test_webgl_conformance_test_suite.html b/content/canvas/test/webgl/test_webgl_conformance_test_suite.html index 530e2b84eb16..8de194fa5115 100644 --- a/content/canvas/test/webgl/test_webgl_conformance_test_suite.html +++ b/content/canvas/test/webgl/test_webgl_conformance_test_suite.html @@ -94,6 +94,26 @@ function start() { } } + // we currently disable this test on version of Mac OSX older than 10.6, + // due to various weird failures, including one making getRenderbufferParameter tests + // on DEPTH_STENCIL fail + var kDarwinVersion = 0; + if (kIsMac) { + // code borrowed from browser/modules/test/browser_taskbar_preview.js + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + kDarwinVersion = parseFloat(Components.classes["@mozilla.org/system-info;1"] + .getService(Components.interfaces.nsIPropertyBag2) + .getProperty("version")); + // the next line is correct: Mac OSX 10.6 corresponds to Darwin version 10 ! + // Mac OSX 10.5 would be Darwin version 9. the |version| string we've got here + // is the Darwin version. + if (kDarwinVersion < 10.0) { + todo(false, "Test disabled on Mac OSX versions older than 10.6."); + SimpleTest.finish(); + return; + } + } + function getEnv(env) { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); var envsvc = Components.classes["@mozilla.org/process/environment;1"].getService(Components.interfaces.nsIEnvironment); @@ -130,14 +150,32 @@ function start() { this.elem = li; }; + /** + * Indicates whether this test page results are not to be ignored. + */ + Page.prototype.shouldBeAccountedFor = function() { + return testsToIgnore.indexOf(this.url) == -1; + } + + /** + * Indicates whether all this test page results are expected not to fail, + * if not ignored. + */ Page.prototype.isExpectedToFullyPass = function() { - return testsExpectedToFail.indexOf(this.url) == -1 && testsToIgnore.indexOf(this.url) == -1; + return this.shouldBeAccountedFor() && + testsExpectedToFail.indexOf(this.url) == -1; } - Page.prototype.errormsg = function(msg) { - return msg + ' (URL: ' + this.url + ')'; + /** + * Returns log message with added test page url. + */ + Page.prototype.logMsg = function(msg) { + return '[' + this.url + '] ' + msg; } + /** + * Reports an individual test result of test page. + */ Page.prototype.addResult = function(msg, success) { ++this.totalTests; if (success === undefined) { @@ -146,19 +184,28 @@ function start() { var css = "timeout"; // only few timeouts are actually caught here --- most are caught in finishPage(). if (this.isExpectedToFullyPass()) { - ok(false, this.errormsg('Test timed out, "' + msg + '"')); + ok(false, this.logMsg('Test timed out'), msg); + } else { + todo(false, this.logMsg('Test timed out'), msg); } } else if (success) { ++this.totalSuccessful; var result = "success"; var css = "success"; - // don't report success. + if (this.shouldBeAccountedFor()) { + ok(true, this.logMsg('Test passed'), msg); + } else { + todo(false, this.logMsg('Test passed, but is ignored'), msg); + } + // Don't report individual success to UI, to keep it light. return; } else { var result = "failed"; var css = "fail"; if (this.isExpectedToFullyPass()) { - ok(false, this.errormsg('Test failed, "' + msg + '"')); + ok(false, this.logMsg('Test failed'), msg); + } else { + todo(false, this.logMsg('Test failed'), msg); } } @@ -181,6 +228,9 @@ function start() { return true; }; + /** + * Reports test page result summary. + */ Page.prototype.finishPage = function(success) { var msg = ' (' + this.totalSuccessful + ' of ' + this.totalTests + ' passed)'; @@ -189,23 +239,31 @@ function start() { msg = '(*timeout*)'; ++this.totalTests; ++this.totalTimeouts; + // Most timeouts are only caught here --- though a few are (already) caught in addResult(). if (this.isExpectedToFullyPass()) { - ok(false, this.errormsg('Unexpected timeout in this test page')); - window.dump('WebGL test error: test page timeout: ' + this.url + '\n'); + ok(false, this.logMsg('Timeout in this test page')); + } else { + todo(false, this.logMsg('Timeout in this test page')); } } else if (this.totalSuccessful != this.totalTests) { var css = 'testpagefail'; + var totalFailed = this.totalTests - this.totalTimeouts - this.totalSuccessful; if (this.isExpectedToFullyPass()) { - window.dump('WebGL test error: test page failure: ' + this.url + '\n'); + ok(false, this.logMsg("(WebGL test error) " + totalFailed + ' failure(s) and ' + this.totalTimeouts + ' timeout(s)')); + } else { + todo(false, this.logMsg("(WebGL test error) " + totalFailed + ' failure(s) and ' + this.totalTimeouts + ' timeout(s)')); } - // failures have already been reported for the sub-tests } else { var css = 'testpagesuccess'; if (this.isExpectedToFullyPass()) { - ok(true, this.errormsg('Successful test page')); + ok(true, this.logMsg('All ' + this.totalSuccessful + ' test(s) passed')); + } else { + if (this.shouldBeAccountedFor()) { + todo(true, this.logMsg('Test page expected to fail, but all ' + this.totalSuccessful + ' tests passed')); + } else { + todo(false, this.logMsg('All ' + this.totalSuccessful + ' test(s) passed, but test page is ignored')); + } } - window.dump('WebGL test page successful: ' + this.url + '\n'); - testsSuccessful.push(this.url); } this.elem.setAttribute('class', css); this.totalsElem.textContent = msg; @@ -246,7 +304,7 @@ function start() { }; Reporter.prototype.startPage = function(url) { - dump('WebGL mochitest: starting page ' + url + '\n'); + info("[" + url + "] (WebGL mochitest) Starting test page"); // Calling garbageCollect before each test page fixes intermittent failures with // out-of-memory errors, often failing to create a WebGL context. @@ -266,14 +324,11 @@ function start() { return page.startPage(); }; - Reporter.prototype.totalFailed = function() { - return this.totalTests - this.totalSuccessful; - }; - Reporter.prototype.displayStats = function() { + var totalFailed = this.totalTests - this.totalTimeouts - this.totalSuccessful; this.fullResultsNode.textContent = this.totalSuccessful + ' passed, ' + - this.totalFailed() + ' failed, ' + + totalFailed + ' failed, ' + this.totalTimeouts + ' timed out'; }; @@ -295,9 +350,6 @@ function start() { }; Reporter.prototype.finishedTestSuite = function() { - for (var i = 0; i < testsExpectedToFail.length; ++i) - if (testsSuccessful.indexOf(testsExpectedToFail[i]) != -1) - todo(true, 'Test expected to fail, but passed: ' + testsExpectedToFail[i]); statusTextNode.textContent = 'Finished'; SimpleTest.finish(); } @@ -353,10 +405,12 @@ function start() { // try to create a dummy WebGL context, just to catch context creation failures once here, // rather than having them result in 100's of failures (one in each test page) var canvas = document.getElementById("webglcheck-default"); - var ctx = null; + var ctx; try { ctx = canvas.getContext("experimental-webgl"); - } catch(e) {} + } catch(e) { + ok(false, "canvas.getContext() failed", e); + } if (ctx) { statusTextNode.textContent = 'Loading test lists...'; @@ -435,8 +489,6 @@ function start() { var testsToIgnore = []; - var testsSuccessful = []; - runTestSuite(); } diff --git a/gfx/gl/ForceDiscreteGPUHelperCGL.h b/gfx/gl/ForceDiscreteGPUHelperCGL.h new file mode 100644 index 000000000000..0b58c6cbcd7e --- /dev/null +++ b/gfx/gl/ForceDiscreteGPUHelperCGL.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef ForceDiscreteGPUHelperCGL_h_ +#define ForceDiscreteGPUHelperCGL_h_ + +#include + +/** This RAII helper guarantees that we're on the discrete GPU during its lifetime. + * + * As long as any ForceDiscreteGPUHelperCGL object is alive, we're on the discrete GPU. + */ +class ForceDiscreteGPUHelperCGL +{ + CGLPixelFormatObj mPixelFormatObj; + +public: + ForceDiscreteGPUHelperCGL() + { + // the code in this function is taken from Chromium, src/ui/gfx/gl/gl_context_cgl.cc, r122013 + // BSD-style license, (c) The Chromium Authors + CGLPixelFormatAttribute attribs[1]; + attribs[0] = static_cast(0); + GLint num_pixel_formats = 0; + CGLChoosePixelFormat(attribs, &mPixelFormatObj, &num_pixel_formats); + } + + ~ForceDiscreteGPUHelperCGL() + { + CGLReleasePixelFormat(mPixelFormatObj); + } +}; + +#endif // ForceDiscreteGPUHelperCGL_h_ diff --git a/gfx/gl/Makefile.in b/gfx/gl/Makefile.in index f7c787029d45..ce7a0b76e578 100644 --- a/gfx/gl/Makefile.in +++ b/gfx/gl/Makefile.in @@ -52,6 +52,7 @@ EXPORTS = \ GLContextProvider.h \ GLContextProviderImpl.h \ EGLUtils.h \ + ForceDiscreteGPUHelperCGL.h \ $(NULL) ifdef MOZ_X11 diff --git a/toolkit/components/telemetry/TelemetryHistograms.h b/toolkit/components/telemetry/TelemetryHistograms.h index 787bfd9667bd..736493a4b480 100644 --- a/toolkit/components/telemetry/TelemetryHistograms.h +++ b/toolkit/components/telemetry/TelemetryHistograms.h @@ -70,7 +70,7 @@ HISTOGRAM(CYCLE_COLLECTOR_VISITED_GCED, 1, 300000, 50, EXPONENTIAL, "Number of J HISTOGRAM(CYCLE_COLLECTOR_COLLECTED, 1, 100000, 50, EXPONENTIAL, "Number of objects collected by the cycle collector") HISTOGRAM_BOOLEAN(CYCLE_COLLECTOR_NEED_GC, "Needed garbage collection before cycle collection.") HISTOGRAM(CYCLE_COLLECTOR_TIME_BETWEEN, 1, 120, 50, EXPONENTIAL, "Time spent in between cycle collections (seconds)") - +HISTOGRAM(CYCLE_COLLECTOR_CONTENT_UNBIND, 1, 10000, 50, EXPONENTIAL, "Time spent on one ContentUnbinder (ms)") HISTOGRAM(FORGET_SKIPPABLE_MAX, 1, 10000, 50, EXPONENTIAL, "Max time spent on one forget skippable (ms)") /**