--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -78,63 +78,33 @@ addEventListener("pageshow", function(ev
});
addEventListener("DOMAutoComplete", function(event) {
LoginManagerContent.onUsernameInput(event);
});
addEventListener("blur", function(event) {
LoginManagerContent.onUsernameInput(event);
});
-var gLastContextMenuEvent = null; // null or a WeakReference to a contextmenu event
var handleContentContextMenu = function (event) {
- gLastContextMenuEvent = null;
let defaultPrevented = event.defaultPrevented;
if (!Services.prefs.getBoolPref("dom.event.contextmenu.enabled")) {
let plugin = null;
try {
plugin = event.target.QueryInterface(Ci.nsIObjectLoadingContent);
} catch (e) {}
if (plugin && plugin.displayedType == Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
// Don't open a context menu for plugins.
return;
}
defaultPrevented = false;
}
- if (defaultPrevented) {
- return;
- }
-
- if (event.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) {
- // If this was triggered by touch, then we don't want to show the actual
- // context menu until we get the APZ:LongTapUp notification. However, we
- // will need the |event| object when we get that notification, so we save
- // it in a WeakReference. That way it won't leak things if we never get
- // the APZ:LongTapUp notification (which is quite possible).
- gLastContextMenuEvent = Cu.getWeakReference(event);
+ if (defaultPrevented)
return;
- }
-
- // For non-touch-derived contextmenu events, we can handle it right away.
- showContentContextMenu(event);
-}
-
-var showContentContextMenu = function (event) {
- if (event == null) {
- // If we weren't given an event, then this is being invoked from the
- // APZ:LongTapUp observer, and the contextmenu event is stashed in
- // gLastContextMenuEvent.
- event = (gLastContextMenuEvent ? gLastContextMenuEvent.get() : null);
- gLastContextMenuEvent = null;
- if (event == null) {
- // Still no event? We can't do anything, bail out.
- return;
- }
- }
let addonInfo = {};
let subject = {
event: event,
addonInfo: addonInfo,
};
subject.wrappedJSObject = subject;
Services.obs.notifyObservers(subject, "content-contextmenu", null);
@@ -242,21 +212,16 @@ var showContentContextMenu = function (e
};
}
}
Cc["@mozilla.org/eventlistenerservice;1"]
.getService(Ci.nsIEventListenerService)
.addSystemEventListener(global, "contextmenu", handleContentContextMenu, false);
-Services.obs.addObserver(showContentContextMenu, "APZ:LongTapUp", false);
-addEventListener("unload", () => {
- Services.obs.removeObserver(showContentContextMenu, "APZ:LongTapUp")
-}, false);
-
// Values for telemtery bins: see TLS_ERROR_REPORT_UI in Histograms.json
const TLS_ERROR_REPORT_TELEMETRY_UI_SHOWN = 0;
const TLS_ERROR_REPORT_TELEMETRY_EXPANDED = 1;
const TLS_ERROR_REPORT_TELEMETRY_SUCCESS = 6;
const TLS_ERROR_REPORT_TELEMETRY_FAILURE = 7;
const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE;
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -34,16 +34,31 @@ const PAGECONTENT_SMALL =
"</select><select id='two'>" +
" <option value='Three'>Three</option>" +
" <option value='Four'>Four</option>" +
"</select><select id='three'>" +
" <option value='Five'>Five</option>" +
" <option value='Six'>Six</option>" +
"</select></body></html>";
+const PAGECONTENT_SOMEHIDDEN =
+ "<html>" +
+ "<body><select id='one'>" +
+ " <option value='One' style='display: none;'>OneHidden</option>" +
+ " <option value='Two' style='display: none;'>TwoHidden</option>" +
+ " <option value='Three'>ThreeVisible</option>" +
+ " <option value='Four'style='display: table;'>FourVisible</option>" +
+ " <option value='Five'>FiveVisible</option>" +
+ " <optgroup label='GroupHidden' style='display: none;'>" +
+ " <option value='Four'>Six.OneHidden</option>" +
+ " <option value='Five' style='display: block;'>Six.TwoHidden</option>" +
+ " </optgroup>" +
+ " <option value='Six'>SevenVisible</option>" +
+ "</select></body></html>";
+
const PAGECONTENT_TRANSLATED =
"<html><body>" +
"<div id='div'>" +
"<iframe id='frame' width='320' height='295' style='border: none;'" +
" src='data:text/html,<select id=select autofocus><option>he he he</option><option>boo boo</option><option>baz baz</option></select>'" +
"</iframe>" +
"</div></body></html>";
@@ -480,8 +495,38 @@ add_task(function* test_mousemove_correc
let sizeModeChanged = BrowserTestUtils.waitForEvent(window, "sizemodechange");
BrowserFullScreen();
yield sizeModeChanged;
yield popupHiddenPromise;
}
yield BrowserTestUtils.removeTab(tab);
});
+
+// This test checks when a <select> element has some options with altered display values.
+add_task(function* test_somehidden() {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_SOMEHIDDEN);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ let selectPopup = document.getElementById("ContentSelectDropdown").menupopup;
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ // The exact number is not needed; just ensure the height is larger than 4 items to accomodate any popup borders.
+ ok(selectPopup.getBoundingClientRect().height >= selectPopup.lastChild.getBoundingClientRect().height * 4, "Height contains at least 4 items");
+ ok(selectPopup.getBoundingClientRect().height < selectPopup.lastChild.getBoundingClientRect().height * 5, "Height doesn't contain 5 items");
+
+ // The label contains the substring 'Visible' for items that are visible.
+ // Otherwise, it is expected to be display: none.
+ is(selectPopup.parentNode.itemCount, 9, "Correct number of items");
+ let child = selectPopup.firstChild;
+ let idx = 1;
+ while (child) {
+ is(getComputedStyle(child).display, child.label.indexOf("Visible") > 0 ? "-moz-box" : "none",
+ "Item " + (idx++) + " is visible");
+ child = child.nextSibling;
+ }
+
+ yield hideSelectPopup(selectPopup, "escape");
+ yield BrowserTestUtils.removeTab(tab);
+});
--- a/browser/base/content/test/plugins/browser_bug812562.js
+++ b/browser/base/content/test/plugins/browser_bug812562.js
@@ -54,17 +54,17 @@ add_task(function* () {
ok(!popupNotification, "test part 2: Should not have a click-to-play notification");
yield ContentTask.spawn(gTestBrowser, null, function* () {
Assert.ok(!content.document.getElementById("test"),
"test part 2: plugin should not be activated");
});
let obsPromise = TestUtils.topicObserved("PopupNotifications-updateNotShowing");
let overlayPromise = promisePopupNotification("click-to-play-plugins");
- gTestBrowser.contentWindow.history.back();
+ gTestBrowser.goBack();
yield obsPromise;
yield overlayPromise;
});
add_task(function* () {
yield promiseUpdatePluginBindings(gTestBrowser);
let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -172,11 +172,20 @@ featureDisableRequiresRestart=%S must re
shouldRestartTitle=Restart %S
okToRestartButton=Restart %S now
revertNoRestartButton=Revert
restartNow=Restart Now
restartLater=Restart Later
disableContainersAlertTitle=Close All Container Tabs?
+
+# LOCALIZATION NOTE (disableContainersMsg): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #S is the number of container tabs
disableContainersMsg=If you disable Container Tabs now, #S container tab will be closed. Are you sure you want to disable Container Tabs?;If you disable Containers Tabs now, #S container tabs will be closed. Are you sure you want to disable Containers Tabs?
+
+# LOCALIZATION NOTE (disableContainersOkButton): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #S is the number of container tabs
disableContainersOkButton=Close #S Container Tab;Close #S Container Tabs
+
disableContainersButton2=Keep enabled
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1049,17 +1049,17 @@ toolbaritem[cui-areatype="menu-panel"] >
}
#identity-box:-moz-locale-dir(rtl) {
border-top-right-radius: 1.5px;
border-bottom-right-radius: 1.5px;
}
#identity-box:-moz-focusring {
- outline: 1px dotted #000;
+ outline: 1px dotted;
outline-offset: -3px;
}
%include ../shared/identity-block/identity-block.inc.css
%include ../shared/notification-icons.inc.css
.popup-notification-body[popupid="addon-progress"],
--- a/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css
+++ b/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css
@@ -1,10 +1,10 @@
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
- border-bottom: 1px solid hsla(210, 4%, 10%, 0.14);
+ border-bottom: 1px solid var(--panel-separator-color);
background-color: hsla(210, 4%, 10%, 0.07);
padding: 6px 0;
padding-inline-start: 44px;
padding-inline-end: 6px;
background-image: url("chrome://browser/skin/info.svg");
background-clip: padding-box;
background-position: 20px center;
background-repeat: no-repeat;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1484,17 +1484,17 @@ html|*.urlbar-input:-moz-lwtheme::-moz-p
#search-container {
min-width: calc(54px + 11ch);
}
/* identity box */
#identity-box:-moz-focusring {
- outline: 1px dotted #000;
+ outline: 1px dotted;
outline-offset: -3px;
}
/* Location bar dropmarker */
.urlbar-history-dropmarker {
-moz-appearance: none;
padding: 0 3px;
--- a/caps/nsPrincipal.cpp
+++ b/caps/nsPrincipal.cpp
@@ -215,19 +215,19 @@ nsPrincipal::SubsumesInternal(nsIPrincip
// If either has .domain set, we have equality i.f.f. the domains match.
// Otherwise, we fall through to the non-document-domain-considering case.
if (thisDomain || otherDomain) {
return nsScriptSecurityManager::SecurityCompareURIs(thisDomain, otherDomain);
}
}
- nsCOMPtr<nsIURI> otherURI;
- rv = aOther->GetURI(getter_AddRefs(otherURI));
- NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIURI> otherURI;
+ rv = aOther->GetURI(getter_AddRefs(otherURI));
+ NS_ENSURE_SUCCESS(rv, false);
// Compare codebases.
return nsScriptSecurityManager::SecurityCompareURIs(mCodebase, otherURI);
}
NS_IMETHODIMP
nsPrincipal::GetURI(nsIURI** aURI)
{
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -4839,18 +4839,16 @@ nsDocument::GetScriptHandlingObjectInter
return scriptHandlingObject;
}
void
nsDocument::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject)
{
NS_ASSERTION(!mScriptGlobalObject ||
mScriptGlobalObject == aScriptObject,
"Wrong script object!");
- // XXXkhuey why bother?
- nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aScriptObject);
if (aScriptObject) {
mScopeObject = do_GetWeakReference(aScriptObject);
mHasHadScriptHandlingObject = true;
mHasHadDefaultView = false;
}
}
bool
@@ -7705,18 +7703,16 @@ nsIDocument::GetCompatMode(nsString& aCo
void
nsDOMAttributeMap::BlastSubtreeToPieces(nsINode *aNode)
{
if (aNode->IsElement()) {
Element *element = aNode->AsElement();
const nsDOMAttributeMap *map = element->GetAttributeMap();
if (map) {
- nsCOMPtr<nsIAttribute> attr;
-
// This non-standard style of iteration is presumably used because some
// of the code in the loop body can trigger element removal, which
// invalidates the iterator.
while (true) {
auto iter = map->mAttributeCache.ConstIter();
if (iter.Done()) {
break;
}
@@ -8759,17 +8755,16 @@ nsDocument::Sanitize()
// in case there is ever an exploit that allows a cached document to be
// accessed from a different document.
// First locate all input elements, regardless of whether they are
// in a form, and reset the password and autocomplete=off elements.
RefPtr<nsContentList> nodes = GetElementsByTagName(NS_LITERAL_STRING("input"));
- nsCOMPtr<nsIContent> item;
nsAutoString value;
uint32_t length = nodes->Length(true);
for (uint32_t i = 0; i < length; ++i) {
NS_ASSERTION(nodes->Item(i), "null item in node list!");
RefPtr<HTMLInputElement> input = HTMLInputElement::FromContentOrNull(nodes->Item(i));
if (!input)
@@ -10434,17 +10429,16 @@ nsresult
nsDocument::GetStateObject(nsIVariant** aState)
{
// Get the document's current state object. This is the object backing both
// history.state and popStateEvent.state.
//
// mStateObjectContainer may be null; this just means that there's no
// current state object.
- nsCOMPtr<nsIVariant> stateObj;
if (!mStateObjectCached && mStateObjectContainer) {
AutoJSContext cx;
nsIGlobalObject* sgo = GetScopeObject();
NS_ENSURE_TRUE(sgo, NS_ERROR_UNEXPECTED);
JS::Rooted<JSObject*> global(cx, sgo->GetGlobalJSObject());
NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
JSAutoCompartment ac(cx, global);
@@ -10875,17 +10869,16 @@ nsIDocument::CaretPositionFromPoint(floa
nsCOMPtr<nsIContent> node = offsets.content;
uint32_t offset = offsets.offset;
nsCOMPtr<nsIContent> anonNode = node;
bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree();
if (nodeIsAnonymous) {
node = ptFrame->GetContent();
nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
- nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(nonanon);
nsCOMPtr<nsIDOMHTMLTextAreaElement> textArea = do_QueryInterface(nonanon);
nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
nsNumberControlFrame* numberFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
if (textFrame || numberFrame) {
// If the anonymous content node has a child, then we need to make sure
// that we get the appropriate child, as otherwise the offset may not be
// correct when we construct a range for it.
nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild();
--- a/dom/base/nsDocumentEncoder.cpp
+++ b/dom/base/nsDocumentEncoder.cpp
@@ -3,17 +3,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/. */
/*
* Object that can be used to serialize selections, ranges, or nodes
* to strings in a gazillion different ways.
*/
-
+
#include "nsIDocumentEncoder.h"
#include "nscore.h"
#include "nsIFactory.h"
#include "nsISupports.h"
#include "nsIDocument.h"
#include "nsIHTMLDocument.h"
#include "nsCOMPtr.h"
@@ -84,18 +84,18 @@ protected:
bool aDontSerializeRoot,
uint32_t aMaxLength = 0);
nsresult SerializeNodeEnd(nsINode* aNode, nsAString& aStr);
// This serializes the content of aNode.
nsresult SerializeToStringIterative(nsINode* aNode,
nsAString& aStr);
nsresult SerializeRangeToString(nsRange *aRange,
nsAString& aOutputString);
- nsresult SerializeRangeNodes(nsRange* aRange,
- nsINode* aNode,
+ nsresult SerializeRangeNodes(nsRange* aRange,
+ nsINode* aNode,
nsAString& aString,
int32_t aDepth);
nsresult SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray,
nsAString& aString);
nsresult SerializeRangeContextEnd(const nsTArray<nsINode*>& aAncestorArray,
nsAString& aString);
virtual int32_t
GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray)
@@ -162,17 +162,17 @@ protected:
uint32_t mEndDepth;
int32_t mStartRootIndex;
int32_t mEndRootIndex;
AutoTArray<nsINode*, 8> mCommonAncestors;
AutoTArray<nsIContent*, 8> mStartNodes;
AutoTArray<int32_t, 8> mStartOffsets;
AutoTArray<nsIContent*, 8> mEndNodes;
AutoTArray<int32_t, 8> mEndOffsets;
- bool mHaltRangeHint;
+ bool mHaltRangeHint;
// Used when context has already been serialized for
// table cell selections (where parent is <tr>)
bool mDisableContextSerialize;
bool mIsCopying; // Set to true only while copying
bool mNodeIsContainer;
nsStringBuffer* mCachedBuffer;
};
@@ -407,38 +407,38 @@ nsresult
nsDocumentEncoder::SerializeNodeStart(nsINode* aNode,
int32_t aStartOffset,
int32_t aEndOffset,
nsAString& aStr,
nsINode* aOriginalNode)
{
if (!IsVisibleNode(aNode))
return NS_OK;
-
+
nsINode* node = nullptr;
nsCOMPtr<nsINode> fixedNodeKungfuDeathGrip;
// Caller didn't do fixup, so we'll do it ourselves
if (!aOriginalNode) {
aOriginalNode = aNode;
- if (mNodeFixup) {
+ if (mNodeFixup) {
bool dummy;
nsCOMPtr<nsIDOMNode> domNodeIn = do_QueryInterface(aNode);
nsCOMPtr<nsIDOMNode> domNodeOut;
mNodeFixup->FixupNode(domNodeIn, &dummy, getter_AddRefs(domNodeOut));
fixedNodeKungfuDeathGrip = do_QueryInterface(domNodeOut);
node = fixedNodeKungfuDeathGrip;
}
}
// Either there was no fixed-up node,
// or the caller did fixup themselves and aNode is already fixed
if (!node)
node = aNode;
-
+
if (node->IsElement()) {
if ((mFlags & (nsIDocumentEncoder::OutputPreformatted |
nsIDocumentEncoder::OutputDropInvisibleBreak)) &&
IsInvisibleBreak(node)) {
return NS_OK;
}
Element* originalElement =
aOriginalNode && aOriginalNode->IsElement() ?
@@ -650,33 +650,33 @@ ConvertAndWrite(const nsAString& aString
uint32_t written;
rv = aStream->Write(charXferBuf, charLength, &written);
NS_ENSURE_SUCCESS(rv, rv);
// If the converter couldn't convert a chraacer we replace the
// character with a characre entity.
if (convert_rv == NS_ERROR_UENC_NOMAPPING) {
- // Finishes the conversion.
+ // Finishes the conversion.
// The converter has the possibility to write some extra data and flush its final state.
char finish_buf[33];
charLength = sizeof(finish_buf) - 1;
rv = aEncoder->Finish(finish_buf, &charLength);
NS_ENSURE_SUCCESS(rv, rv);
// Make sure finish_buf is null-terminated before we call
// Write().
finish_buf[charLength] = '\0';
rv = aStream->Write(finish_buf, charLength, &written);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString entString("&#");
- if (NS_IS_HIGH_SURROGATE(unicodeBuf[unicodeLength - 1]) &&
+ if (NS_IS_HIGH_SURROGATE(unicodeBuf[unicodeLength - 1]) &&
unicodeLength < startLength && NS_IS_LOW_SURROGATE(unicodeBuf[unicodeLength])) {
entString.AppendInt(SURROGATE_TO_UCS4(unicodeBuf[unicodeLength - 1],
unicodeBuf[unicodeLength]));
unicodeLength += 1;
}
else
entString.AppendInt(unicodeBuf[unicodeLength - 1]);
entString.Append(';');
@@ -854,17 +854,17 @@ nsDocumentEncoder::SerializeRangeNodes(n
{
// node is completely contained in range. Serialize the whole subtree
// rooted by this node.
rv = SerializeToStringRecursive(aNode, aString, false);
NS_ENSURE_SUCCESS(rv, rv);
}
else
{
- // due to implementation it is impossible for text node to be both start and end of
+ // due to implementation it is impossible for text node to be both start and end of
// range. We would have handled that case without getting here.
//XXXsmaug What does this all mean?
if (IsTextNode(aNode))
{
if (startNode == content)
{
int32_t startOffset = aRange->StartOffset();
rv = SerializeNodeStart(aNode, startOffset, -1, aString);
@@ -884,42 +884,42 @@ nsDocumentEncoder::SerializeRangeNodes(n
if (IncludeInContext(aNode))
{
// halt the incrementing of mStartDepth/mEndDepth. This is
// so paste client will include this node in paste.
mHaltRangeHint = true;
}
if ((startNode == content) && !mHaltRangeHint) mStartDepth++;
if ((endNode == content) && !mHaltRangeHint) mEndDepth++;
-
+
// serialize the start of this node
rv = SerializeNodeStart(aNode, 0, -1, aString);
NS_ENSURE_SUCCESS(rv, rv);
}
-
+
// do some calculations that will tell us which children of this
// node are in the range.
nsIContent* childAsNode = nullptr;
int32_t startOffset = 0, endOffset = -1;
if (startNode == content && mStartRootIndex >= aDepth)
startOffset = mStartOffsets[mStartRootIndex - aDepth];
if (endNode == content && mEndRootIndex >= aDepth)
endOffset = mEndOffsets[mEndRootIndex - aDepth];
- // generated content will cause offset values of -1 to be returned.
+ // generated content will cause offset values of -1 to be returned.
int32_t j;
uint32_t childCount = content->GetChildCount();
if (startOffset == -1) startOffset = 0;
if (endOffset == -1) endOffset = childCount;
else
{
// if we are at the "tip" of the selection, endOffset is fine.
// otherwise, we need to add one. This is because of the semantics
// of the offset list created by GetAncestorsAndOffsets(). The
- // intermediate points on the list use the endOffset of the
+ // intermediate points on the list use the endOffset of the
// location of the ancestor, rather than just past it. So we need
// to add one here in order to include it in the children we serialize.
if (aNode != aRange->GetEndParent())
{
endOffset++;
}
}
// serialize the children of this node that are in the range
@@ -934,17 +934,17 @@ nsDocumentEncoder::SerializeRangeNodes(n
NS_ENSURE_SUCCESS(rv, rv);
}
// serialize the end of this node
if (aNode != mCommonParent)
{
rv = SerializeNodeEnd(aNode, aString);
- NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv, rv);
}
}
}
return NS_OK;
}
nsresult
nsDocumentEncoder::SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray,
@@ -1015,17 +1015,17 @@ nsDocumentEncoder::SerializeRangeToStrin
{
if (!aRange || aRange->Collapsed())
return NS_OK;
mCommonParent = aRange->GetCommonAncestor();
if (!mCommonParent)
return NS_OK;
-
+
nsINode* startParent = aRange->GetStartParent();
NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE);
int32_t startOffset = aRange->StartOffset();
nsINode* endParent = aRange->GetEndParent();
NS_ENSURE_TRUE(endParent, NS_ERROR_FAILURE);
int32_t endOffset = aRange->EndOffset();
@@ -1042,17 +1042,17 @@ nsDocumentEncoder::SerializeRangeToStrin
&mStartNodes, &mStartOffsets);
nsCOMPtr<nsIDOMNode> ep = do_QueryInterface(endParent);
nsContentUtils::GetAncestorsAndOffsets(ep, endOffset,
&mEndNodes, &mEndOffsets);
nsCOMPtr<nsIContent> commonContent = do_QueryInterface(mCommonParent);
mStartRootIndex = mStartNodes.IndexOf(commonContent);
mEndRootIndex = mEndNodes.IndexOf(commonContent);
-
+
nsresult rv = NS_OK;
rv = SerializeRangeContextStart(mCommonAncestors, aOutputString);
NS_ENSURE_SUCCESS(rv, rv);
if ((startParent == endParent) && IsTextNode(startParent))
{
if (mFlags & SkipInvisibleContent) {
@@ -1103,30 +1103,28 @@ nsDocumentEncoder::EncodeToStringWithMax
}
}
NS_ASSERTION(!mCachedBuffer->IsReadonly(),
"DocumentEncoder shouldn't keep reference to non-readonly buffer!");
static_cast<char16_t*>(mCachedBuffer->Data())[0] = char16_t(0);
mCachedBuffer->ToString(0, output, true);
// output owns the buffer now!
mCachedBuffer = nullptr;
-
+
if (!mSerializer) {
nsAutoCString progId(NS_CONTENTSERIALIZER_CONTRACTID_PREFIX);
AppendUTF16toUTF8(mMimeType, progId);
mSerializer = do_CreateInstance(progId.get());
NS_ENSURE_TRUE(mSerializer, NS_ERROR_NOT_IMPLEMENTED);
}
nsresult rv = NS_OK;
- nsCOMPtr<nsIAtom> charsetAtom;
-
bool rewriteEncodingDeclaration = !(mSelection || mRange || mNode) && !(mFlags & OutputDontRewriteEncodingDeclaration);
mSerializer->Init(mFlags, mWrapColumn, mCharset.get(), mIsCopying, rewriteEncodingDeclaration);
if (mSelection) {
nsCOMPtr<nsIDOMRange> range;
int32_t i, count = 0;
rv = mSelection->GetRangeCount(&count);
@@ -1190,23 +1188,23 @@ nsDocumentEncoder::EncodeToStringWithMax
mStartDepth = firstRangeStartDepth;
if (prevNode) {
nsCOMPtr<nsINode> p = do_QueryInterface(prevNode);
rv = SerializeNodeEnd(p, output);
NS_ENSURE_SUCCESS(rv, rv);
mCommonAncestors.Clear();
nsContentUtils::GetAncestors(p->GetParentNode(), mCommonAncestors);
- mDisableContextSerialize = false;
+ mDisableContextSerialize = false;
rv = SerializeRangeContextEnd(mCommonAncestors, output);
NS_ENSURE_SUCCESS(rv, rv);
}
// Just to be safe
- mDisableContextSerialize = false;
+ mDisableContextSerialize = false;
mSelection = nullptr;
} else if (mRange) {
rv = SerializeRangeToString(mRange, output);
mRange = nullptr;
} else if (mNode) {
if (!mNodeFixup && !(mFlags & SkipInvisibleContent) && !mStream &&
@@ -1221,17 +1219,17 @@ nsDocumentEncoder::EncodeToStringWithMax
if (NS_SUCCEEDED(rv)) {
rv = SerializeToStringRecursive(mDocument, output, false, aMaxLength);
}
}
NS_ENSURE_SUCCESS(rv, rv);
rv = mSerializer->Flush(output);
-
+
mCachedBuffer = nsStringBuffer::FromString(output);
// We have to be careful how we set aOutputString, because we don't
// want it to end up sharing mCachedBuffer if we plan to reuse it.
bool setOutput = false;
// Try to cache the buffer.
if (mCachedBuffer) {
if (mCachedBuffer->StorageSize() == bufferSize &&
!mCachedBuffer->IsReadonly()) {
@@ -1330,22 +1328,22 @@ public:
protected:
enum Endpoint
{
kStart,
kEnd
};
-
+
nsresult PromoteRange(nsIDOMRange *inRange);
- nsresult PromoteAncestorChain(nsCOMPtr<nsIDOMNode> *ioNode,
- int32_t *ioStartOffset,
+ nsresult PromoteAncestorChain(nsCOMPtr<nsIDOMNode> *ioNode,
+ int32_t *ioStartOffset,
int32_t *ioEndOffset);
- nsresult GetPromotedPoint(Endpoint aWhere, nsIDOMNode *aNode, int32_t aOffset,
+ nsresult GetPromotedPoint(Endpoint aWhere, nsIDOMNode *aNode, int32_t aOffset,
nsCOMPtr<nsIDOMNode> *outNode, int32_t *outOffset, nsIDOMNode *aCommon);
nsCOMPtr<nsIDOMNode> GetChildAt(nsIDOMNode *aParent, int32_t aOffset);
bool IsMozBR(nsIDOMNode* aNode);
nsresult GetNodeLocation(nsIDOMNode *inChild, nsCOMPtr<nsIDOMNode> *outParent, int32_t *outOffset);
bool IsRoot(nsIDOMNode* aNode);
bool IsFirstNode(nsIDOMNode *aNode);
bool IsLastNode(nsIDOMNode *aNode);
bool IsEmptyTextContent(nsIDOMNode* aNode);
@@ -1401,29 +1399,29 @@ nsHTMLCopyEncoder::Init(nsIDOMDocument*
}
NS_IMETHODIMP
nsHTMLCopyEncoder::SetSelection(nsISelection* aSelection)
{
// check for text widgets: we need to recognize these so that
// we don't tweak the selection to be outside of the magic
// div that ender-lite text widgets are embedded in.
-
- if (!aSelection)
+
+ if (!aSelection)
return NS_ERROR_NULL_POINTER;
-
+
nsCOMPtr<nsIDOMRange> range;
nsCOMPtr<nsIDOMNode> commonParent;
Selection* selection = aSelection->AsSelection();
uint32_t rangeCount = selection->RangeCount();
// if selection is uninitialized return
if (!rangeCount)
return NS_ERROR_FAILURE;
-
+
// we'll just use the common parent of the first range. Implicit assumption
// here that multi-range selections are table cell selections, in which case
// the common parent is somewhere in the table and we don't really care where.
nsresult rv = aSelection->GetRangeAt(0, getter_AddRefs(range));
NS_ENSURE_SUCCESS(rv, rv);
if (!range)
return NS_ERROR_NULL_POINTER;
range->GetCommonAncestorContainer(getter_AddRefs(commonParent));
@@ -1476,50 +1474,50 @@ nsHTMLCopyEncoder::SetSelection(nsISelec
mIsTextWidget = true;
break;
}
}
#endif
}
// normalize selection if we are not in a widget
- if (mIsTextWidget)
+ if (mIsTextWidget)
{
mSelection = aSelection;
mMimeType.AssignLiteral("text/plain");
return NS_OK;
}
// also consider ourselves in a text widget if we can't find an html document
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mDocument);
if (!(htmlDoc && mDocument->IsHTMLDocument())) {
mIsTextWidget = true;
mSelection = aSelection;
// mMimeType is set to text/plain when encoding starts.
return NS_OK;
}
-
+
// there's no Clone() for selection! fix...
//nsresult rv = aSelection->Clone(getter_AddRefs(mSelection);
//NS_ENSURE_SUCCESS(rv, rv);
NS_NewDomSelection(getter_AddRefs(mSelection));
NS_ENSURE_TRUE(mSelection, NS_ERROR_FAILURE);
-
+
// loop thru the ranges in the selection
for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
range = selection->GetRangeAt(rangeIdx);
NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
nsCOMPtr<nsIDOMRange> myRange;
range->CloneRange(getter_AddRefs(myRange));
NS_ENSURE_TRUE(myRange, NS_ERROR_FAILURE);
// adjust range to include any ancestors who's children are entirely selected
rv = PromoteRange(myRange);
NS_ENSURE_SUCCESS(rv, rv);
-
+
rv = mSelection->AddRange(myRange);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
NS_IMETHODIMP
@@ -1539,60 +1537,60 @@ nsHTMLCopyEncoder::EncodeToStringWithCon
nsresult rv = EncodeToString(aEncodedString);
NS_ENSURE_SUCCESS(rv, rv);
// do not encode any context info or range hints if we are in a text widget.
if (mIsTextWidget) return NS_OK;
// now encode common ancestors into aContextString. Note that the common ancestors
// will be for the last range in the selection in the case of multirange selections.
- // encoding ancestors every range in a multirange selection in a way that could be
+ // encoding ancestors every range in a multirange selection in a way that could be
// understood by the paste code would be a lot more work to do. As a practical matter,
// selections are single range, and the ones that aren't are table cell selections
// where all the cells are in the same table.
// leaf of ancestors might be text node. If so discard it.
int32_t count = mCommonAncestors.Length();
int32_t i;
nsCOMPtr<nsINode> node;
if (count > 0)
node = mCommonAncestors.ElementAt(0);
- if (node && IsTextNode(node))
+ if (node && IsTextNode(node))
{
mCommonAncestors.RemoveElementAt(0);
// don't forget to adjust range depth info
if (mStartDepth) mStartDepth--;
if (mEndDepth) mEndDepth--;
// and the count
count--;
}
-
+
i = count;
while (i > 0)
{
node = mCommonAncestors.ElementAt(--i);
SerializeNodeStart(node, 0, -1, aContextString);
}
//i = 0; guaranteed by above
while (i < count)
{
node = mCommonAncestors.ElementAt(i++);
SerializeNodeEnd(node, aContextString);
}
- // encode range info : the start and end depth of the selection, where the depth is
+ // encode range info : the start and end depth of the selection, where the depth is
// distance down in the parent hierarchy. Later we will need to add leading/trailing
// whitespace info to this.
nsAutoString infoString;
infoString.AppendInt(mStartDepth);
infoString.Append(char16_t(','));
infoString.AppendInt(mEndDepth);
aInfoString = infoString;
-
+
return NS_OK;
}
bool
nsHTMLCopyEncoder::IncludeInContext(nsINode *aNode)
{
nsCOMPtr<nsIContent> content(do_QueryInterface(aNode));
@@ -1624,82 +1622,81 @@ nsHTMLCopyEncoder::IncludeInContext(nsIN
nsGkAtoms::h2,
nsGkAtoms::h3,
nsGkAtoms::h4,
nsGkAtoms::h5,
nsGkAtoms::h6);
}
-nsresult
+nsresult
nsHTMLCopyEncoder::PromoteRange(nsIDOMRange *inRange)
{
if (!inRange) return NS_ERROR_NULL_POINTER;
nsresult rv;
nsCOMPtr<nsIDOMNode> startNode, endNode, common;
int32_t startOffset, endOffset;
-
+
rv = inRange->GetCommonAncestorContainer(getter_AddRefs(common));
NS_ENSURE_SUCCESS(rv, rv);
rv = inRange->GetStartContainer(getter_AddRefs(startNode));
NS_ENSURE_SUCCESS(rv, rv);
rv = inRange->GetStartOffset(&startOffset);
NS_ENSURE_SUCCESS(rv, rv);
rv = inRange->GetEndContainer(getter_AddRefs(endNode));
NS_ENSURE_SUCCESS(rv, rv);
rv = inRange->GetEndOffset(&endOffset);
NS_ENSURE_SUCCESS(rv, rv);
-
+
nsCOMPtr<nsIDOMNode> opStartNode;
nsCOMPtr<nsIDOMNode> opEndNode;
int32_t opStartOffset, opEndOffset;
- nsCOMPtr<nsIDOMRange> opRange;
-
- // examine range endpoints.
+
+ // examine range endpoints.
rv = GetPromotedPoint( kStart, startNode, startOffset, address_of(opStartNode), &opStartOffset, common);
NS_ENSURE_SUCCESS(rv, rv);
rv = GetPromotedPoint( kEnd, endNode, endOffset, address_of(opEndNode), &opEndOffset, common);
NS_ENSURE_SUCCESS(rv, rv);
-
+
// if both range endpoints are at the common ancestor, check for possible inclusion of ancestors
if ( (opStartNode == common) && (opEndNode == common) )
{
rv = PromoteAncestorChain(address_of(opStartNode), &opStartOffset, &opEndOffset);
NS_ENSURE_SUCCESS(rv, rv);
opEndNode = opStartNode;
}
-
+
// set the range to the new values
rv = inRange->SetStart(opStartNode, opStartOffset);
NS_ENSURE_SUCCESS(rv, rv);
rv = inRange->SetEnd(opEndNode, opEndOffset);
return rv;
-}
+}
// PromoteAncestorChain will promote a range represented by [{*ioNode,*ioStartOffset} , {*ioNode,*ioEndOffset}]
// The promotion is different from that found in getPromotedPoint: it will only promote one endpoint if it can
// promote the other. Thus, instead of having a startnode/endNode, there is just the one ioNode.
nsresult
-nsHTMLCopyEncoder::PromoteAncestorChain(nsCOMPtr<nsIDOMNode> *ioNode,
- int32_t *ioStartOffset,
- int32_t *ioEndOffset)
+nsHTMLCopyEncoder::PromoteAncestorChain(nsCOMPtr<nsIDOMNode> *ioNode,
+ int32_t *ioStartOffset,
+ int32_t *ioEndOffset)
{
if (!ioNode || !ioStartOffset || !ioEndOffset) return NS_ERROR_NULL_POINTER;
nsresult rv = NS_OK;
bool done = false;
nsCOMPtr<nsIDOMNode> frontNode, endNode, parent;
int32_t frontOffset, endOffset;
//save the editable state of the ioNode, so we don't promote an ancestor if it has different editable state
nsCOMPtr<nsINode> node = do_QueryInterface(*ioNode);
bool isEditable = node->IsEditable();
-
+
// loop for as long as we can promote both endpoints
while (!done)
{
rv = (*ioNode)->GetParentNode(getter_AddRefs(parent));
if ((NS_FAILED(rv)) || !parent)
done = true;
else
{
@@ -1707,56 +1704,56 @@ nsHTMLCopyEncoder::PromoteAncestorChain(
// up the hierarchy.
rv = GetPromotedPoint( kStart, *ioNode, *ioStartOffset, address_of(frontNode), &frontOffset, parent);
NS_ENSURE_SUCCESS(rv, rv);
// then we make the same attempt with the endpoint
rv = GetPromotedPoint( kEnd, *ioNode, *ioEndOffset, address_of(endNode), &endOffset, parent);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsINode> frontINode = do_QueryInterface(frontNode);
- // if both endpoints were promoted one level and isEditable is the same as the original node,
+ // if both endpoints were promoted one level and isEditable is the same as the original node,
// keep looping - otherwise we are done.
if ( (frontNode != parent) || (endNode != parent) || (frontINode->IsEditable() != isEditable) )
done = true;
else
{
- *ioNode = frontNode;
+ *ioNode = frontNode;
*ioStartOffset = frontOffset;
*ioEndOffset = endOffset;
}
}
}
return rv;
}
nsresult
-nsHTMLCopyEncoder::GetPromotedPoint(Endpoint aWhere, nsIDOMNode *aNode, int32_t aOffset,
+nsHTMLCopyEncoder::GetPromotedPoint(Endpoint aWhere, nsIDOMNode *aNode, int32_t aOffset,
nsCOMPtr<nsIDOMNode> *outNode, int32_t *outOffset, nsIDOMNode *common)
{
nsresult rv = NS_OK;
nsCOMPtr<nsIDOMNode> node = aNode;
nsCOMPtr<nsIDOMNode> parent = aNode;
int32_t offset = aOffset;
bool bResetPromotion = false;
-
+
// default values
*outNode = node;
*outOffset = offset;
- if (common == node)
+ if (common == node)
return NS_OK;
-
+
if (aWhere == kStart)
{
// some special casing for text nodes
nsCOMPtr<nsINode> t = do_QueryInterface(aNode);
if (IsTextNode(t))
{
// if not at beginning of text node, we are done
- if (offset > 0)
+ if (offset > 0)
{
// unless everything before us in just whitespace. NOTE: we need a more
// general solution that truly detects all cases of non-significant
// whitesace with no false alarms.
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
nsAutoString text;
nodeAsText->SubstringData(0, offset, text);
text.CompressWhitespace();
@@ -1769,17 +1766,17 @@ nsHTMLCopyEncoder::GetPromotedPoint(Endp
NS_ENSURE_SUCCESS(rv, rv);
}
else
{
node = GetChildAt(parent,offset);
}
if (!node) node = parent;
- // finding the real start for this point. look up the tree for as long as we are the
+ // finding the real start for this point. look up the tree for as long as we are the
// first node in the container, and as long as we haven't hit the body node.
if (!IsRoot(node) && (parent != common))
{
rv = GetNodeLocation(node, address_of(parent), &offset);
NS_ENSURE_SUCCESS(rv, rv);
if (offset == -1) return NS_OK; // we hit generated content; STOP
nsIParserService *parserService = nsContentUtils::GetParserService();
if (!parserService)
@@ -1793,44 +1790,44 @@ nsHTMLCopyEncoder::GetPromotedPoint(Endp
{
bool isBlock = false;
parserService->IsBlock(parserService->HTMLAtomTagToId(
content->NodeInfo()->NameAtom()), isBlock);
if (isBlock)
{
bResetPromotion = false;
}
- }
+ }
}
-
+
node = parent;
rv = GetNodeLocation(node, address_of(parent), &offset);
NS_ENSURE_SUCCESS(rv, rv);
if (offset == -1) // we hit generated content; STOP
{
// back up a bit
parent = node;
offset = 0;
break;
}
- }
+ }
if (bResetPromotion)
{
*outNode = aNode;
*outOffset = aOffset;
}
else
{
*outNode = parent;
*outOffset = offset;
}
return rv;
}
}
-
+
if (aWhere == kEnd)
{
// some special casing for text nodes
nsCOMPtr<nsINode> n = do_QueryInterface(aNode);
if (IsTextNode(n))
{
// if not at end of text node, we are done
uint32_t len = n->Length();
@@ -1851,18 +1848,18 @@ nsHTMLCopyEncoder::GetPromotedPoint(Endp
NS_ENSURE_SUCCESS(rv, rv);
}
else
{
if (offset) offset--; // we want node _before_ offset
node = GetChildAt(parent,offset);
}
if (!node) node = parent;
-
- // finding the real end for this point. look up the tree for as long as we are the
+
+ // finding the real end for this point. look up the tree for as long as we are the
// last node in the container, and as long as we haven't hit the body node.
if (!IsRoot(node) && (parent != common))
{
rv = GetNodeLocation(node, address_of(parent), &offset);
NS_ENSURE_SUCCESS(rv, rv);
if (offset == -1) return NS_OK; // we hit generated content; STOP
nsIParserService *parserService = nsContentUtils::GetParserService();
if (!parserService)
@@ -1876,76 +1873,76 @@ nsHTMLCopyEncoder::GetPromotedPoint(Endp
{
bool isBlock = false;
parserService->IsBlock(parserService->HTMLAtomTagToId(
content->NodeInfo()->NameAtom()), isBlock);
if (isBlock)
{
bResetPromotion = false;
}
- }
+ }
}
-
+
node = parent;
rv = GetNodeLocation(node, address_of(parent), &offset);
NS_ENSURE_SUCCESS(rv, rv);
if (offset == -1) // we hit generated content; STOP
{
// back up a bit
parent = node;
offset = 0;
break;
}
- }
+ }
if (bResetPromotion)
{
*outNode = aNode;
*outOffset = aOffset;
}
else
{
*outNode = parent;
offset++; // add one since this in an endpoint - want to be AFTER node.
*outOffset = offset;
}
return rv;
}
}
-
+
return rv;
}
-nsCOMPtr<nsIDOMNode>
+nsCOMPtr<nsIDOMNode>
nsHTMLCopyEncoder::GetChildAt(nsIDOMNode *aParent, int32_t aOffset)
{
nsCOMPtr<nsIDOMNode> resultNode;
-
- if (!aParent)
+
+ if (!aParent)
return resultNode;
-
+
nsCOMPtr<nsIContent> content = do_QueryInterface(aParent);
NS_PRECONDITION(content, "null content in nsHTMLCopyEncoder::GetChildAt");
resultNode = do_QueryInterface(content->GetChildAt(aOffset));
return resultNode;
}
-bool
+bool
nsHTMLCopyEncoder::IsMozBR(nsIDOMNode* aNode)
{
MOZ_ASSERT(aNode);
nsCOMPtr<Element> element = do_QueryInterface(aNode);
return element &&
element->IsHTMLElement(nsGkAtoms::br) &&
element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
NS_LITERAL_STRING("_moz"), eIgnoreCase);
}
-nsresult
+nsresult
nsHTMLCopyEncoder::GetNodeLocation(nsIDOMNode *inChild,
nsCOMPtr<nsIDOMNode> *outParent,
int32_t *outOffset)
{
NS_ASSERTION((inChild && outParent && outOffset), "bad args");
nsresult result = NS_ERROR_NULL_POINTER;
if (inChild && outParent && outOffset)
{
@@ -1981,91 +1978,91 @@ nsHTMLCopyEncoder::IsRoot(nsIDOMNode* aN
}
bool
nsHTMLCopyEncoder::IsFirstNode(nsIDOMNode *aNode)
{
nsCOMPtr<nsIDOMNode> parent;
int32_t offset, j=0;
nsresult rv = GetNodeLocation(aNode, address_of(parent), &offset);
- if (NS_FAILED(rv))
+ if (NS_FAILED(rv))
{
NS_NOTREACHED("failure in IsFirstNode");
return false;
}
if (offset == 0) // easy case, we are first dom child
return true;
- if (!parent)
+ if (!parent)
return true;
-
+
// need to check if any nodes before us are really visible.
// Mike wrote something for me along these lines in nsSelectionController,
// but I don't think it's ready for use yet - revisit.
- // HACK: for now, simply consider all whitespace text nodes to be
+ // HACK: for now, simply consider all whitespace text nodes to be
// invisible formatting nodes.
nsCOMPtr<nsIDOMNodeList> childList;
nsCOMPtr<nsIDOMNode> child;
rv = parent->GetChildNodes(getter_AddRefs(childList));
- if (NS_FAILED(rv) || !childList)
+ if (NS_FAILED(rv) || !childList)
{
NS_NOTREACHED("failure in IsFirstNode");
return true;
}
while (j < offset)
{
childList->Item(j, getter_AddRefs(child));
- if (!IsEmptyTextContent(child))
+ if (!IsEmptyTextContent(child))
return false;
j++;
}
return true;
}
bool
nsHTMLCopyEncoder::IsLastNode(nsIDOMNode *aNode)
{
nsCOMPtr<nsIDOMNode> parent;
int32_t offset,j;
nsresult rv = GetNodeLocation(aNode, address_of(parent), &offset);
- if (NS_FAILED(rv))
+ if (NS_FAILED(rv))
{
NS_NOTREACHED("failure in IsLastNode");
return false;
}
nsCOMPtr<nsINode> parentNode = do_QueryInterface(parent);
if (!parentNode) {
return true;
}
uint32_t numChildren = parentNode->Length();
if (offset+1 == (int32_t)numChildren) // easy case, we are last dom child
return true;
// need to check if any nodes after us are really visible.
// Mike wrote something for me along these lines in nsSelectionController,
// but I don't think it's ready for use yet - revisit.
- // HACK: for now, simply consider all whitespace text nodes to be
+ // HACK: for now, simply consider all whitespace text nodes to be
// invisible formatting nodes.
j = (int32_t)numChildren-1;
nsCOMPtr<nsIDOMNodeList>childList;
nsCOMPtr<nsIDOMNode> child;
rv = parent->GetChildNodes(getter_AddRefs(childList));
- if (NS_FAILED(rv) || !childList)
+ if (NS_FAILED(rv) || !childList)
{
NS_NOTREACHED("failure in IsLastNode");
return true;
}
while (j > offset)
{
childList->Item(j, getter_AddRefs(child));
j--;
- if (IsMozBR(child)) // we ignore trailing moz BRs.
+ if (IsMozBR(child)) // we ignore trailing moz BRs.
continue;
- if (!IsEmptyTextContent(child))
+ if (!IsEmptyTextContent(child))
return false;
}
return true;
}
bool
nsHTMLCopyEncoder::IsEmptyTextContent(nsIDOMNode* aNode)
{
@@ -2100,9 +2097,8 @@ nsHTMLCopyEncoder::GetImmediateContextCo
nsGkAtoms::tfoot,
nsGkAtoms::table)) {
break;
}
++j;
}
return j;
}
-
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -14015,17 +14015,16 @@ nsGlobalWindow::GetReturnValue(JSContext
aError, );
}
NS_IMETHODIMP
nsGlobalModalWindow::GetReturnValue(nsIVariant **aRetVal)
{
FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(GetReturnValue, (aRetVal), NS_OK);
- nsCOMPtr<nsIVariant> result;
if (!mReturnValue) {
nsCOMPtr<nsIVariant> variant = CreateVoidVariant();
variant.forget(aRetVal);
return NS_OK;
}
return mReturnValue->Get(nsContentUtils::SubjectPrincipal(), aRetVal);
}
--- a/dom/base/nsHostObjectProtocolHandler.cpp
+++ b/dom/base/nsHostObjectProtocolHandler.cpp
@@ -215,17 +215,16 @@ class BlobURLsReporter final : public ns
BlobImpl* blobImpl = iter.UserData()->mBlobImpl;
MOZ_ASSERT(blobImpl);
NS_NAMED_LITERAL_CSTRING(desc,
"A blob URL allocated with URL.createObjectURL; the referenced "
"blob cannot be freed until all URLs for it have been explicitly "
"invalidated with URL.revokeObjectURL.");
nsAutoCString path, url, owner, specialDesc;
- nsCOMPtr<nsIURI> principalURI;
uint64_t size = 0;
uint32_t refCount = 1;
DebugOnly<bool> blobImplWasCounted;
blobImplWasCounted = refCounts.Get(blobImpl, &refCount);
MOZ_ASSERT(blobImplWasCounted);
MOZ_ASSERT(refCount > 0);
--- a/dom/base/nsLocation.cpp
+++ b/dom/base/nsLocation.cpp
@@ -258,17 +258,16 @@ nsLocation::GetWritableURI(nsIURI** aURI
}
nsresult
nsLocation::SetURI(nsIURI* aURI, bool aReplace)
{
nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
if (docShell) {
nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
- nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
if(NS_FAILED(CheckURL(aURI, getter_AddRefs(loadInfo))))
return NS_ERROR_FAILURE;
if (aReplace) {
loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContentAndReplace);
} else {
loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContent);
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -2655,17 +2655,16 @@ nsScriptLoader::PrepareLoadedRequest(nsS
// acceptable.
nsAutoCString mimeType;
channel->GetContentType(mimeType);
NS_ConvertUTF8toUTF16 typeString(mimeType);
if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
return NS_ERROR_FAILURE;
}
- nsCOMPtr<nsIURI> baseURL;
channel->GetURI(getter_AddRefs(request->mBaseURL));
// Attempt to compile off main thread.
rv = AttemptAsyncScriptCompile(request);
if (NS_SUCCEEDED(rv)) {
return rv;
}
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -3113,18 +3113,16 @@ nsDOMDeviceStorage::AddOrAppendNamed(Blo
MOZ_ASSERT(IsOwningThread());
MOZ_ASSERT(aCreate || !aPath.IsEmpty());
// if the blob is null here, bail
if (!aBlob) {
return nullptr;
}
- nsCOMPtr<nsIRunnable> r;
-
if (IsFullPath(aPath)) {
nsString storagePath;
RefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
if (!ds) {
return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
}
return ds->AddOrAppendNamed(aBlob, storagePath, aCreate, aRv);
--- a/dom/html/HTMLSelectElement.cpp
+++ b/dom/html/HTMLSelectElement.cpp
@@ -609,17 +609,16 @@ HTMLSelectElement::Add(nsIDOMHTMLElement
if (dataType == nsIDataType::VTYPE_EMPTY ||
dataType == nsIDataType::VTYPE_VOID) {
ErrorResult error;
Add(*htmlElement, (nsGenericHTMLElement*)nullptr, error);
return error.StealNSResult();
}
nsCOMPtr<nsISupports> supports;
- nsCOMPtr<nsIDOMHTMLElement> beforeElement;
// whether aBefore is nsIDOMHTMLElement...
if (NS_SUCCEEDED(aBefore->GetAsISupports(getter_AddRefs(supports)))) {
nsCOMPtr<nsIContent> beforeElement = do_QueryInterface(supports);
nsGenericHTMLElement* beforeHTMLElement =
nsGenericHTMLElement::FromContentOrNull(beforeElement);
NS_ENSURE_TRUE(beforeHTMLElement, NS_ERROR_DOM_SYNTAX_ERR);
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -3118,19 +3118,16 @@ nsGenericHTMLElement::GetInnerText(mozil
}
nsRange::GetInnerTextNoFlush(aValue, aError, this, 0, this, GetChildCount());
}
void
nsGenericHTMLElement::SetInnerText(const nsAString& aValue)
{
- // Fire DOMNodeRemoved mutation events before we do anything else.
- nsCOMPtr<nsIContent> kungFuDeathGrip;
-
// Batch possible DOMSubtreeModified events.
mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
FireNodeRemovedForChildren();
// Might as well stick a batch around this since we're performing several
// mutations.
mozAutoDocUpdate updateBatch(GetComposedDoc(),
UPDATE_CONTENT_MODEL, true);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1298,23 +1298,32 @@ StartMacOSContentSandbox()
}
nsAutoCString tempDirPath;
rv = tempDir->GetNativePath(tempDirPath);
if (NS_FAILED(rv)) {
MOZ_CRASH("Failed to get NS_OS_TEMP_DIR path");
}
+ nsCOMPtr<nsIFile> profileDir;
+ ContentChild::GetSingleton()->GetProfileDir(getter_AddRefs(profileDir));
+ nsCString profileDirPath;
+ rv = profileDir->GetNativePath(profileDirPath);
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("Failed to get profile path");
+ }
+
MacSandboxInfo info;
info.type = MacSandboxType_Content;
info.level = info.level = sandboxLevel;
info.appPath.assign(appPath.get());
info.appBinaryPath.assign(appBinaryPath.get());
info.appDir.assign(appDir.get());
info.appTempDir.assign(tempDirPath.get());
+ info.profileDir.assign(profileDirPath.get());
std::string err;
if (!mozilla::StartMacSandbox(info, err)) {
NS_WARNING(err.c_str());
MOZ_CRASH("sandbox_init() failed");
}
return true;
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -16,16 +16,19 @@
#include "nsHashKeys.h"
#include "nsIObserver.h"
#include "nsTHashtable.h"
#include "nsRefPtrHashtable.h"
#include "nsWeakPtr.h"
#include "nsIWindowProvider.h"
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+#include "nsIFile.h"
+#endif
struct ChromePackage;
class nsIObserver;
struct SubstitutionMapping;
struct OverrideMapping;
class nsIDomainPolicy;
namespace mozilla {
@@ -109,16 +112,29 @@ public:
}
void SetProcessName(const nsAString& aName, bool aDontOverride = false);
void GetProcessName(nsAString& aName) const;
void GetProcessName(nsACString& aName) const;
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+ void GetProfileDir(nsIFile** aProfileDir) const
+ {
+ *aProfileDir = mProfileDir;
+ NS_IF_ADDREF(*aProfileDir);
+ }
+
+ void SetProfileDir(nsIFile* aProfileDir)
+ {
+ mProfileDir = aProfileDir;
+ }
+#endif
+
bool IsAlive() const;
static void AppendProcessId(nsACString& aName);
ContentBridgeParent* GetLastBridge()
{
MOZ_ASSERT(mLastBridge);
ContentBridgeParent* parent = mLastBridge;
@@ -676,16 +692,20 @@ private:
bool mIsAlive;
nsString mProcessName;
static ContentChild* sSingleton;
nsCOMPtr<nsIDomainPolicy> mPolicy;
nsCOMPtr<nsITimer> mForceKillTimer;
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+ nsCOMPtr<nsIFile> mProfileDir;
+#endif
+
// Hashtable to keep track of the pending GetFilesHelper objects.
// This GetFilesHelperChild objects are removed when RecvGetFilesResponse is
// received.
nsRefPtrHashtable<nsIDHashKey, GetFilesHelperChild> mGetFilesPendingRequests;
DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
};
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1080,17 +1080,16 @@ ContentParent::CreateBrowserOrApp(const
nsIDocShell* docShell = GetOpenerDocShellHelper(aFrameElement);
TabId openerTabId;
if (docShell) {
openerTabId = TabParent::GetTabIdFrom(docShell);
}
if (aContext.IsMozBrowserElement() || !aContext.HasOwnApp()) {
- RefPtr<TabParent> tp;
RefPtr<nsIContentParent> constructorSender;
if (isInContentProcess) {
MOZ_ASSERT(aContext.IsMozBrowserElement());
constructorSender = CreateContentBridgeParent(aContext, initialPriority,
openerTabId, &tabId);
} else {
if (aOpenerContentParent) {
constructorSender = aOpenerContentParent;
--- a/dom/ipc/ContentProcess.cpp
+++ b/dom/ipc/ContentProcess.cpp
@@ -109,26 +109,45 @@ SetUpSandboxEnvironment()
#endif
void
ContentProcess::SetAppDir(const nsACString& aPath)
{
mXREEmbed.SetAppDir(aPath);
}
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+void
+ContentProcess::SetProfile(const nsACString& aProfile)
+{
+ bool flag;
+ nsresult rv =
+ XRE_GetFileFromPath(aProfile.BeginReading(), getter_AddRefs(mProfileDir));
+ if (NS_FAILED(rv) ||
+ NS_FAILED(mProfileDir->Exists(&flag)) || !flag) {
+ NS_WARNING("Invalid profile directory passed to content process.");
+ mProfileDir = nullptr;
+ }
+}
+#endif
+
bool
ContentProcess::Init()
{
mContent.Init(IOThreadChild::message_loop(),
ParentPid(),
IOThreadChild::channel());
mXREEmbed.Start();
mContent.InitXPCOM();
mContent.InitGraphicsDeviceData();
+#if (defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
+ mContent.SetProfileDir(mProfileDir);
+#endif
+
#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
SetUpSandboxEnvironment();
#endif
return true;
}
// Note: CleanUp() never gets called in non-debug builds because we exit early
--- a/dom/ipc/ContentProcess.h
+++ b/dom/ipc/ContentProcess.h
@@ -34,19 +34,28 @@ public:
~ContentProcess()
{ }
virtual bool Init() override;
virtual void CleanUp() override;
void SetAppDir(const nsACString& aPath);
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+ void SetProfile(const nsACString& aProfile);
+#endif
+
private:
ContentChild mContent;
mozilla::ipc::ScopedXREEmbed mXREEmbed;
+
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+ nsCOMPtr<nsIFile> mProfileDir;
+#endif
+
#if defined(XP_WIN)
// This object initializes and configures COM.
mozilla::mscom::MainThreadRuntime mCOMRuntime;
#endif
DISALLOW_EVIL_CONSTRUCTORS(ContentProcess);
};
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -393,49 +393,65 @@ OfflineClockDriver::GetCurrentTimeStamp(
void
SystemClockDriver::WaitForNextIteration()
{
mGraphImpl->GetMonitor().AssertCurrentThreadOwns();
PRIntervalTime timeout = PR_INTERVAL_NO_TIMEOUT;
TimeStamp now = TimeStamp::Now();
- if (mGraphImpl->mNeedAnotherIteration) {
+
+ // This lets us avoid hitting the Atomic twice when we know we won't sleep
+ bool another = mGraphImpl->mNeedAnotherIteration; // atomic
+ if (!another) {
+ mGraphImpl->mGraphDriverAsleep = true; // atomic
+ mWaitState = WAITSTATE_WAITING_INDEFINITELY;
+ }
+ // NOTE: mNeedAnotherIteration while also atomic may have changed before
+ // we could set mGraphDriverAsleep, so we must re-test it.
+ // (EnsureNextIteration sets mNeedAnotherIteration, then tests
+ // mGraphDriverAsleep
+ if (another || mGraphImpl->mNeedAnotherIteration) { // atomic
int64_t timeoutMS = MEDIA_GRAPH_TARGET_PERIOD_MS -
int64_t((now - mCurrentTimeStamp).ToMilliseconds());
// Make sure timeoutMS doesn't overflow 32 bits by waking up at
// least once a minute, if we need to wake up at all
timeoutMS = std::max<int64_t>(0, std::min<int64_t>(timeoutMS, 60*1000));
timeout = PR_MillisecondsToInterval(uint32_t(timeoutMS));
- STREAM_LOG(LogLevel::Verbose, ("Waiting for next iteration; at %f, timeout=%f", (now - mInitialTimeStamp).ToSeconds(), timeoutMS/1000.0));
+ STREAM_LOG(LogLevel::Verbose,
+ ("Waiting for next iteration; at %f, timeout=%f",
+ (now - mInitialTimeStamp).ToSeconds(), timeoutMS/1000.0));
if (mWaitState == WAITSTATE_WAITING_INDEFINITELY) {
mGraphImpl->mGraphDriverAsleep = false; // atomic
}
mWaitState = WAITSTATE_WAITING_FOR_NEXT_ITERATION;
- } else {
- mGraphImpl->mGraphDriverAsleep = true; // atomic
- mWaitState = WAITSTATE_WAITING_INDEFINITELY;
}
if (timeout > 0) {
mGraphImpl->GetMonitor().Wait(timeout);
STREAM_LOG(LogLevel::Verbose, ("Resuming after timeout; at %f, elapsed=%f",
(TimeStamp::Now() - mInitialTimeStamp).ToSeconds(),
(TimeStamp::Now() - now).ToSeconds()));
}
if (mWaitState == WAITSTATE_WAITING_INDEFINITELY) {
mGraphImpl->mGraphDriverAsleep = false; // atomic
}
+ // Note: this can race against the EnsureNextIteration setting
+ // WAITSTATE_RUNNING and setting mGraphDriverAsleep to false, so you can
+ // have an iteration with WAITSTATE_WAKING_UP instead of RUNNING.
mWaitState = WAITSTATE_RUNNING;
- mGraphImpl->mNeedAnotherIteration = false;
+ mGraphImpl->mNeedAnotherIteration = false; // atomic
}
void SystemClockDriver::WakeUp()
{
mGraphImpl->GetMonitor().AssertCurrentThreadOwns();
+ // Note: this can race against the thread setting WAITSTATE_RUNNING and
+ // setting mGraphDriverAsleep to false, so you can have an iteration
+ // with WAITSTATE_WAKING_UP instead of RUNNING.
mWaitState = WAITSTATE_WAKING_UP;
mGraphImpl->mGraphDriverAsleep = false; // atomic
mGraphImpl->GetMonitor().Notify();
}
OfflineClockDriver::OfflineClockDriver(MediaStreamGraphImpl* aGraphImpl, GraphTime aSlice)
: ThreadedDriver(aGraphImpl),
mSlice(aSlice)
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -523,18 +523,16 @@ MediaDecoderReader::Shutdown()
ReleaseResources();
mDuration.DisconnectIfConnected();
mBuffered.DisconnectAll();
mIsSuspended.DisconnectAll();
// Shut down the watch manager before shutting down our task queue.
mWatchManager.Shutdown();
- RefPtr<ShutdownPromise> p;
-
mDecoder = nullptr;
ReaderQueue::Instance().Remove(this);
return mTaskQueue->BeginShutdown();
}
} // namespace mozilla
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -517,16 +517,18 @@ public:
Monitor& GetMonitor()
{
return mMonitor;
}
void EnsureNextIteration()
{
mNeedAnotherIteration = true; // atomic
+ // Note: GraphDriver must ensure that there's no race on setting
+ // mNeedAnotherIteration and mGraphDriverAsleep -- see WaitForNextIteration()
if (mGraphDriverAsleep) { // atomic
MonitorAutoLock mon(mMonitor);
CurrentDriver()->WakeUp(); // Might not be the same driver; might have woken already
}
}
void EnsureNextIterationLocked()
{
--- a/dom/mobilemessage/MobileMessageManager.cpp
+++ b/dom/mobilemessage/MobileMessageManager.cpp
@@ -31,17 +31,17 @@
#include "nsIMobileMessageService.h"
#include "nsIObserverService.h"
#include "nsISmsService.h"
#include "nsServiceManagerUtils.h" // For do_GetService()
// Service instantiation
#include "ipc/SmsIPCService.h"
#include "MobileMessageService.h"
-#ifdef MOZ_WIDGET_ANDROID
+#if defined(MOZ_WIDGET_ANDROID) && defined(MOZ_WEBSMS_BACKEND)
#include "android/MobileMessageDatabaseService.h"
#include "android/SmsService.h"
#elif defined(MOZ_WIDGET_GONK) && defined(MOZ_B2G_RIL)
#include "nsIGonkMobileMessageDatabaseService.h"
#include "nsIGonkSmsService.h"
#endif
#include "nsXULAppAPI.h" // For XRE_GetProcessType()
@@ -848,34 +848,34 @@ MobileMessageManager::SetSmscAddress(con
already_AddRefed<nsISmsService>
NS_CreateSmsService()
{
nsCOMPtr<nsISmsService> smsService;
if (XRE_IsContentProcess()) {
smsService = SmsIPCService::GetSingleton();
} else {
-#ifdef MOZ_WIDGET_ANDROID
+#if defined(MOZ_WIDGET_ANDROID) && defined(MOZ_WEBSMS_BACKEND)
smsService = new SmsService();
#elif defined(MOZ_WIDGET_GONK) && defined(MOZ_B2G_RIL)
smsService = do_GetService(GONK_SMSSERVICE_CONTRACTID);
#endif
}
return smsService.forget();
}
already_AddRefed<nsIMobileMessageDatabaseService>
NS_CreateMobileMessageDatabaseService()
{
nsCOMPtr<nsIMobileMessageDatabaseService> mobileMessageDBService;
if (XRE_IsContentProcess()) {
mobileMessageDBService = SmsIPCService::GetSingleton();
} else {
-#ifdef MOZ_WIDGET_ANDROID
+#if defined(MOZ_WIDGET_ANDROID) && defined(MOZ_WEBSMS_BACKEND)
mobileMessageDBService = new MobileMessageDatabaseService();
#elif defined(MOZ_WIDGET_GONK) && defined(MOZ_B2G_RIL)
mobileMessageDBService =
do_CreateInstance(GONK_MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID);
#endif
}
return mobileMessageDBService.forget();
--- a/dom/mobilemessage/moz.build
+++ b/dom/mobilemessage/moz.build
@@ -11,17 +11,17 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/xpcs
EXPORTS.mozilla.dom.mobilemessage += [
'Constants.h', # Required by almost all cpp files
'ipc/SmsChild.h',
'ipc/SmsParent.h',
'Types.h', # Required by IPDL SmsTypes.h
]
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android' and CONFIG['MOZ_WEBSMS_BACKEND']:
SOURCES += [
'android/MobileMessageDatabaseService.cpp',
'android/SmsManager.cpp',
'android/SmsService.cpp',
]
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['MOZ_B2G_RIL']:
EXTRA_JS_MODULES += [
'gonk/mms_consts.js',
new file mode 100644
--- /dev/null
+++ b/dom/permission/tests/file_empty.html
@@ -0,0 +1,2 @@
+<h1>I'm just a support file</h1>
+<p>I get loaded to do permission testing.</p>
\ No newline at end of file
--- a/dom/permission/tests/mochitest.ini
+++ b/dom/permission/tests/mochitest.ini
@@ -1,12 +1,13 @@
[DEFAULT]
support-files =
file_framework.js
file_shim.html
+ file_empty.html
[test_alarms.html]
skip-if = true
[test_browser.html]
skip-if = true
[test_embed-apps.html]
skip-if = true || e10s #Bug 931116, b2g desktop specific, initial triage ### Bug 1255339: blacklist because no more mozApps
[test_idle.html]
--- a/dom/permission/tests/test_permissions_api.html
+++ b/dom/permission/tests/test_permissions_api.html
@@ -1,146 +1,206 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
+
<head>
<meta charset="utf-8">
<title>Test for Permissions API</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
- <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
</head>
-<body onload='runTests()'>
-<pre id="test">
-<script type="application/javascript;version=1.8">
-'use strict';
+
+<body>
+ <pre id="test"></pre>
+ <script type="application/javascript;version=1.8">
+ /*globals SpecialPowers, SimpleTest, is, ok, */
+ 'use strict';
+
+ const {
+ UNKNOWN_ACTION,
+ PROMPT_ACTION,
+ ALLOW_ACTION,
+ DENY_ACTION
+ } = SpecialPowers.Ci.nsIPermissionManager;
+
+ SimpleTest.waitForExplicitFinish();
-let { UNKNOWN_ACTION, PROMPT_ACTION, ALLOW_ACTION, DENY_ACTION } =
- SpecialPowers.Ci.nsIPermissionManager;
+ const PERMISSIONS = [{
+ name: 'geolocation',
+ type: 'geo'
+ }, {
+ name: 'notifications',
+ type: 'desktop-notification'
+ }, {
+ name: 'push',
+ type: 'desktop-notification'
+ }, ];
-SimpleTest.waitForExplicitFinish();
+ const UNSUPPORTED_PERMISSIONS = [
+ 'foobarbaz', // Not in spec, for testing only.
+ 'midi',
+ ];
-const PERMISSIONS = [
- { name: 'geolocation', perm: 'geo' },
- { name: 'notifications', perm: 'desktop-notification' },
- { name: 'push', perm: 'desktop-notification' },
-];
+ // Create a closure, so that tests are run on the correct window object.
+ function createPermissionTester(aWindow) {
+ return {
+ setPermissions(allow) {
+ const permissions = PERMISSIONS.map(({ type }) => {
+ return {
+ type,
+ allow,
+ 'context': aWindow.document
+ };
+ });
+ return new Promise((resolve) => {
+ SpecialPowers.popPermissions(() => {
+ SpecialPowers.pushPermissions(permissions, resolve);
+ });
+ });
+ },
+ revokePermissions() {
+ const promisesToRevoke = PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .revoke({ name })
+ .then(
+ ({ state }) => is(state, 'prompt', `correct state for '${name}'`),
+ () => ok(false, `revoke should not have rejected for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToRevoke);
+ },
+ revokeUnsupportedPermissions() {
+ const promisesToRevoke = UNSUPPORTED_PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .revoke({ name })
+ .then(
+ () => ok(false, `revoke should not have resolved for '${name}'`),
+ error => is(error.name, 'TypeError', `revoke should have thrown TypeError for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToRevoke);
+ },
+ checkPermissions(state) {
+ const promisesToQuery = PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(
+ () => is(state, state, `correct state for '${name}'`),
+ () => ok(false, `query should not have rejected for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToQuery);
+ },
+ checkUnsupportedPermissions() {
+ const promisesToQuery = UNSUPPORTED_PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(
+ () => ok(false, `query should not have resolved for '${name}'`),
+ error => {
+ is(error.name, 'TypeError',
+ `query should have thrown TypeError for '${name}'`);
+ }
+ );
+ });
+ return Promise.all(promisesToQuery);
+ },
+ promiseStateChanged(name, state) {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(status => {
+ return new Promise( resolve => {
+ status.onchange = () => {
+ status.onchange = null;
+ is(status.state, state, `state changed for '${name}'`);
+ resolve();
+ };
+ });
+ },
+ () => ok(false, `query should not have rejected for '${name}'`));
+ },
+ testStatusOnChange() {
+ return new Promise((resolve) => {
+ SpecialPowers.popPermissions(() => {
+ const permission = 'geolocation';
+ const promiseGranted = this.promiseStateChanged(permission, 'granted');
+ this.setPermissions(ALLOW_ACTION);
+ promiseGranted.then(() => {
+ const promisePrompt = this.promiseStateChanged(permission, 'prompt');
+ SpecialPowers.popPermissions();
+ return promisePrompt;
+ }).then(resolve);
+ });
+ });
+ },
+ testInvalidQuery() {
+ return aWindow.navigator.permissions
+ .query({ name: 'invalid' })
+ .then(
+ () => ok(false, 'invalid query should not have resolved'),
+ () => ok(true, 'invalid query should have rejected')
+ );
+ },
+ testInvalidRevoke() {
+ return aWindow.navigator.permissions
+ .revoke({ name: 'invalid' })
+ .then(
+ () => ok(false, 'invalid revoke should not have resolved'),
+ () => ok(true, 'invalid revoke should have rejected')
+ );
+ },
+ };
+ }
-const UNSUPPORTED_PERMISSIONS = [
- 'foobarbaz', // Not in spec, for testing only.
- 'midi'
-];
+ function enablePrefs() {
+ const ops = {
+ 'set': [
+ ['dom.permissions.revoke.enable', true],
+ ],
+ };
+ return SpecialPowers.pushPrefEnv(ops);
+ }
-function setPermissions(action) {
- let permissions = PERMISSIONS.map(x => {
- return { 'type': x.perm, 'allow': action, 'context': document };
- });
- return new Promise((resolve, reject) => {
- SpecialPowers.popPermissions(() => {
- SpecialPowers.pushPermissions(permissions, resolve);
+ function createIframe() {
+ return new Promise((resolve) => {
+ const iframe = document.createElement('iframe');
+ iframe.src = 'file_empty.html';
+ iframe.onload = () => resolve(iframe.contentWindow);
+ document.body.appendChild(iframe);
});
- });
-}
-
-function revokePermissions(action) {
- return Promise.all(PERMISSIONS.map(x =>
- navigator.permissions.revoke({ name: x.name }).then(
- result => is(result.state, "prompt", `correct state for '${x.name}'`),
- error => ok(false, `revoke should not have rejected for '${x.name}'`))
- ));
-}
-
-function revokeUnsupportedPermissions() {
- return Promise.all(UNSUPPORTED_PERMISSIONS.map(name =>
- navigator.permissions.revoke({ name: name }).then(
- result => ok(false, `revoke should not have resolved for '${name}'`),
- error => is(error.name, 'TypeError', `revoke should have thrown TypeError for '${name}'`))
- ));
-}
-
-function checkPermissions(state) {
- return Promise.all(PERMISSIONS.map(x => {
- return navigator.permissions.query({ name: x.name }).then(
- result => is(result.state, state, `correct state for '${x.name}'`),
- error => ok(false, `query should not have rejected for '${x.name}'`));
- }));
-}
-
-function checkUnsupportedPermissions() {
- return Promise.all(UNSUPPORTED_PERMISSIONS.map(name => {
- return navigator.permissions.query({ name: name }).then(
- result => ok(false, `query should not have resolved for '${name}'`),
- error => {
- is(error.name, 'TypeError',
- `query should have thrown TypeError for '${name}'`);
+ }
+ debugger;
+ window.onload = () => {
+ enablePrefs()
+ .then(createIframe)
+ .then(createPermissionTester)
+ .then((tester) => {
+ return tester
+ .checkUnsupportedPermissions()
+ .then(() => tester.setPermissions(UNKNOWN_ACTION))
+ .then(() => tester.checkPermissions('prompt'))
+ .then(() => tester.setPermissions(PROMPT_ACTION))
+ .then(() => tester.checkPermissions('prompt'))
+ .then(() => tester.setPermissions(ALLOW_ACTION))
+ .then(() => tester.checkPermissions('granted'))
+ .then(() => tester.setPermissions(DENY_ACTION))
+ .then(() => tester.checkPermissions('denied'))
+ .then(() => tester.testStatusOnChange())
+ .then(() => tester.testInvalidQuery())
+ .then(() => tester.revokeUnsupportedPermissions())
+ .then(() => tester.revokePermissions())
+ .then(() => tester.checkPermissions('prompt'))
+ .then(() => tester.testInvalidRevoke());
+ })
+ .then(SimpleTest.finish)
+ .catch((e) => {
+ ok(false, `Unexpected error ${e}`);
+ SimpleTest.finish();
});
- }));
-}
-
-function promiseStateChanged(name, state) {
- return navigator.permissions.query({ name }).then(
- status => {
- return new Promise((resolve, reject) => {
- status.onchange = () => {
- status.onchange = null;
- is(status.state, state, `state changed for '${name}'`);
- resolve();
- };
- });
- },
- error => ok(false, `query should not have rejected for '${name}'`));
-}
-
-function testStatusOnChange() {
- return new Promise((resolve, reject) => {
- SpecialPowers.popPermissions(() => {
- let permission = 'geolocation';
- let promiseGranted = promiseStateChanged(permission, 'granted');
- setPermissions(ALLOW_ACTION);
- promiseGranted.then(() => {
- let promisePrompt = promiseStateChanged(permission, 'prompt');
- SpecialPowers.popPermissions();
- return promisePrompt;
- }).then(resolve);
- });
- });
-}
+ };
+ </script>
+</body>
-function testInvalidQuery() {
- navigator.permissions.query({ name: 'invalid' }).then(
- result => ok(false, 'invalid query should not have resolved'),
- error => ok(true, 'invalid query should have rejected'));
-}
-
-function testInvalidRevoke() {
- navigator.permissions.revoke({ name: 'invalid' }).then(
- result => ok(false, 'invalid revoke should not have resolved'),
- error => ok(true, 'invalid revoke should have rejected'));
-}
-
-function runTests() {
- checkUnsupportedPermissions()
- .then(() => setPermissions(UNKNOWN_ACTION))
- .then(() => checkPermissions('prompt'))
- .then(() => setPermissions(PROMPT_ACTION))
- .then(() => checkPermissions('prompt'))
- .then(() => setPermissions(ALLOW_ACTION))
- .then(() => checkPermissions('granted'))
- .then(() => setPermissions(DENY_ACTION))
- .then(() => checkPermissions('denied'))
- .then(testStatusOnChange)
- .then(testInvalidQuery)
- .then(revokeUnsupportedPermissions)
- .then(revokePermissions)
- .then(() => checkPermissions('prompt'))
- .then(testInvalidRevoke)
- .then(SimpleTest.finish)
- .catch ((e) => {
- ok(false, 'Unexpected error ' + e);
- SimpleTest.finish();
- });
-}
-</script>
-</pre>
-</body>
</html>
--- a/dom/plugins/base/nsPluginStreamListenerPeer.cpp
+++ b/dom/plugins/base/nsPluginStreamListenerPeer.cpp
@@ -406,17 +406,16 @@ nsPluginStreamListenerPeer::SetupPluginC
return rv;
// Yes, make it unique.
rv = pluginTmp->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
if (NS_FAILED(rv))
return rv;
// create a file output stream to write to...
- nsCOMPtr<nsIOutputStream> outstream;
rv = NS_NewLocalFileOutputStream(getter_AddRefs(mFileCacheOutputStream), pluginTmp, -1, 00600);
if (NS_FAILED(rv))
return rv;
// save the file.
mLocalCachedFileHolder = new CachedFileHolder(pluginTmp);
}
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -3350,17 +3350,16 @@ PluginInstanceChild::DoAsyncSetWindow(co
bool
PluginInstanceChild::CreateOptSurface(void)
{
MOZ_ASSERT(mSurfaceType != gfxSurfaceType::Max,
"Need a valid surface type here");
NS_ASSERTION(!mCurrentSurface, "mCurrentSurfaceActor can get out of sync.");
- RefPtr<gfxASurface> retsurf;
// Use an opaque surface unless we're transparent and *don't* have
// a background to source from.
gfxImageFormat format =
(mIsTransparent && !mBackground) ? SurfaceFormat::A8R8G8B8_UINT32 :
SurfaceFormat::X8R8G8B8_UINT32;
#ifdef MOZ_X11
Display* dpy = mWsInfo.display;
@@ -3576,17 +3575,19 @@ PluginInstanceChild::EnsureCurrentBuffer
}
#endif
return true;
}
void
PluginInstanceChild::UpdateWindowAttributes(bool aForceSetWindow)
{
+#if defined(MOZ_X11) || defined(XP_WIN)
RefPtr<gfxASurface> curSurface = mHelperSurface ? mHelperSurface : mCurrentSurface;
+#endif // Only used within MOZ_X11 or XP_WIN blocks. Unused variable otherwise
bool needWindowUpdate = aForceSetWindow;
#ifdef MOZ_X11
Visual* visual = nullptr;
Colormap colormap = 0;
if (curSurface && curSurface->GetType() == gfxSurfaceType::Xlib) {
static_cast<gfxXlibSurface*>(curSurface.get())->
GetColormapAndVisual(&colormap, &visual);
if (visual != mWsInfo.visual || colormap != mWsInfo.colormap) {
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -7483,18 +7483,16 @@ UpgradeDirectoryMetadataFrom1To2Helper::
return NS_OK;
}
nsresult
UpgradeDirectoryMetadataFrom1To2Helper::DoProcessOriginDirectories()
{
AssertIsOnIOThread();
- nsCOMPtr<nsIFile> permanentStorageDir;
-
for (uint32_t count = mOriginProps.Length(), index = 0;
index < count;
index++) {
OriginProps& originProps = mOriginProps[index];
nsresult rv;
if (originProps.mNeedsRestore) {
--- a/dom/storage/DOMStorageDBThread.cpp
+++ b/dom/storage/DOMStorageDBThread.cpp
@@ -452,17 +452,16 @@ DOMStorageDBThread::OpenDatabaseConnecti
nsresult rv;
MOZ_ASSERT(!NS_IsMainThread());
nsCOMPtr<mozIStorageService> service
= do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr<mozIStorageConnection> connection;
rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
if (rv == NS_ERROR_FILE_CORRUPTED) {
// delete the db and try opening again
rv = mDatabaseFile->Remove(false);
NS_ENSURE_SUCCESS(rv, rv);
rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
}
NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/webidl/Permissions.webidl
+++ b/dom/webidl/Permissions.webidl
@@ -20,11 +20,11 @@ dictionary PermissionDescriptor {
// We don't implement `PushPermissionDescriptor` because we use a background
// message quota instead of `userVisibleOnly`.
[Exposed=(Window)]
interface Permissions {
[Throws]
Promise<PermissionStatus> query(object permission);
- [Throws]
+ [Throws, Pref="dom.permissions.revoke.enable"]
Promise<PermissionStatus> revoke(object permission);
};
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1045,17 +1045,18 @@ public:
// (if any).
static void
ReportError(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
bool aFireAtScope, WorkerPrivate* aTarget,
const nsString& aMessage, const nsString& aFilename,
const nsString& aLine, uint32_t aLineNumber,
uint32_t aColumnNumber, uint32_t aFlags,
uint32_t aErrorNumber, JSExnType aExnType,
- bool aMutedError, uint64_t aInnerWindowId)
+ bool aMutedError, uint64_t aInnerWindowId,
+ JS::Handle<JS::Value> aException = JS::NullHandleValue)
{
if (aWorkerPrivate) {
aWorkerPrivate->AssertIsOnWorkerThread();
} else {
AssertIsOnMainThread();
}
// We should not fire error events for warnings but instead make sure that
@@ -1065,16 +1066,17 @@ public:
RootedDictionary<ErrorEventInit> init(aCx);
if (aMutedError) {
init.mMessage.AssignLiteral("Script error.");
} else {
init.mMessage = aMessage;
init.mFilename = aFilename;
init.mLineno = aLineNumber;
+ init.mError = aException;
}
init.mCancelable = true;
init.mBubbles = false;
if (aTarget) {
RefPtr<ErrorEvent> event =
ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
@@ -5914,16 +5916,22 @@ WorkerPrivate::ReportError(JSContext* aC
if (!MayContinueRunning() || mErrorHandlerRecursionCount == 2) {
return;
}
NS_ASSERTION(mErrorHandlerRecursionCount == 0 ||
mErrorHandlerRecursionCount == 1,
"Bad recursion logic!");
+ JS::Rooted<JS::Value> exn(aCx);
+ if (!JS_GetPendingException(aCx, &exn)) {
+ // Probably shouldn't actually happen? But let's go ahead and just use null
+ // for lack of anything better.
+ exn.setNull();
+ }
JS_ClearPendingException(aCx);
nsString message, filename, line;
uint32_t lineNumber, columnNumber, flags, errorNumber;
JSExnType exnType = JSEXN_ERR;
bool mutedError = aReport && aReport->isMuted;
if (aReport) {
@@ -5961,17 +5969,17 @@ WorkerPrivate::ReportError(JSContext* aC
bool fireAtScope = mErrorHandlerRecursionCount == 1 &&
!mCloseHandlerStarted &&
errorNumber != JSMSG_OUT_OF_MEMORY &&
JS::CurrentGlobalOrNull(aCx);
ReportErrorRunnable::ReportError(aCx, this, fireAtScope, nullptr, message,
filename, line, lineNumber,
columnNumber, flags, errorNumber, exnType,
- mutedError, 0);
+ mutedError, 0, exn);
mErrorHandlerRecursionCount--;
}
// static
void
WorkerPrivate::ReportErrorToConsole(const char* aMessage)
{
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -2608,17 +2608,17 @@ XMLHttpRequestMainThread::InitiateFetch(
// ref to us to be extra safe.
mChannel->SetNotificationCallbacks(mNotificationCallbacks);
mChannel = nullptr;
mErrorLoad = true;
// Per spec, we throw on sync errors, but not async.
if (mFlagSynchronous) {
- return rv;
+ return NS_ERROR_DOM_NETWORK_ERR;
}
}
return NS_OK;
}
NS_IMETHODIMP
XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
@@ -2869,17 +2869,17 @@ XMLHttpRequestMainThread::SendInternal(c
DispatchProgressEvent(mUpload, ProgressEventType::loadstart,
0, mUploadTotal);
}
}
if (!mChannel) {
// Per spec, silently fail on async request failures; throw for sync.
if (mFlagSynchronous) {
- return NS_ERROR_FAILURE;
+ return NS_ERROR_DOM_NETWORK_ERR;
} else {
// Defer the actual sending of async events just in case listeners
// are attached after the send() method is called.
NS_DispatchToCurrentThread(
NewRunnableMethod<ProgressEventType>(this,
&XMLHttpRequestMainThread::CloseRequestWithError,
ProgressEventType::error));
return NS_OK;
--- a/dom/xml/nsXMLFragmentContentSink.cpp
+++ b/dom/xml/nsXMLFragmentContentSink.cpp
@@ -162,18 +162,16 @@ nsXMLFragmentContentSink::WillBuildModel
mRoot = new DocumentFragment(mNodeInfoManager);
return NS_OK;
}
NS_IMETHODIMP
nsXMLFragmentContentSink::DidBuildModel(bool aTerminated)
{
- RefPtr<nsParserBase> kungFuDeathGrip(mParser);
-
// Drop our reference to the parser to get rid of a circular
// reference.
mParser = nullptr;
return NS_OK;
}
NS_IMETHODIMP
--- a/dom/xul/templates/nsXULTemplateQueryProcessorStorage.cpp
+++ b/dom/xul/templates/nsXULTemplateQueryProcessorStorage.cpp
@@ -220,17 +220,16 @@ nsXULTemplateQueryProcessorStorage::GetD
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(channel, &rv);
if (NS_FAILED(rv)) { // if it fails, not a file url
nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_STORAGE_BAD_URI);
return rv;
}
- nsCOMPtr<nsIFile> file;
rv = fileChannel->GetFile(getter_AddRefs(databaseFile));
NS_ENSURE_SUCCESS(rv, rv);
}
// ok now we have an URI of a sqlite file
nsCOMPtr<mozIStorageConnection> connection;
rv = storage->OpenDatabase(databaseFile, getter_AddRefs(connection));
if (NS_FAILED(rv)) {
--- a/gfx/2d/Rect.h
+++ b/gfx/2d/Rect.h
@@ -276,21 +276,20 @@ IntRectTyped<units> TruncatedToInt(const
}
template<class units>
RectTyped<units> IntRectToRect(const IntRectTyped<units>& aRect)
{
return RectTyped<units>(aRect.x, aRect.y, aRect.width, aRect.height);
}
-// Convenience function for intersecting two IntRects wrapped in Maybes.
-template <typename Units>
-Maybe<IntRectTyped<Units>>
-IntersectMaybeRects(const Maybe<IntRectTyped<Units>>& a,
- const Maybe<IntRectTyped<Units>>& b)
+// Convenience function for intersecting two rectangles wrapped in Maybes.
+template <typename T>
+Maybe<T>
+IntersectMaybeRects(const Maybe<T>& a, const Maybe<T>& b)
{
if (!a) {
return b;
} else if (!b) {
return a;
} else {
return Some(a->Intersect(*b));
}
--- a/gfx/ipc/CompositorSession.h
+++ b/gfx/ipc/CompositorSession.h
@@ -46,17 +46,17 @@ public:
// This returns a CompositorBridgeParent if the compositor resides in the same process.
virtual CompositorBridgeParent* GetInProcessBridge() const = 0;
// Set the GeckoContentController for the root of the layer tree.
virtual void SetContentController(GeckoContentController* aController) = 0;
// Return the Async Pan/Zoom Tree Manager for this compositor.
- virtual already_AddRefed<IAPZCTreeManager> GetAPZCTreeManager() const = 0;
+ virtual RefPtr<IAPZCTreeManager> GetAPZCTreeManager() const = 0;
// Return the child end of the compositor IPC bridge.
CompositorBridgeChild* GetCompositorBridgeChild();
// Return the proxy for accessing the compositor's widget.
CompositorWidgetDelegate* GetCompositorWidgetDelegate() {
return mCompositorWidgetDelegate;
}
--- a/gfx/ipc/GPUParent.cpp
+++ b/gfx/ipc/GPUParent.cpp
@@ -6,16 +6,17 @@
#include "GPUParent.h"
#include "gfxConfig.h"
#include "gfxPlatform.h"
#include "gfxPrefs.h"
#include "GPUProcessHost.h"
#include "mozilla/Assertions.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/ipc/ProcessChild.h"
+#include "mozilla/layers/APZThreadUtils.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/ImageBridgeParent.h"
#include "nsDebugImpl.h"
#include "mozilla/layers/LayerTreeOwnerTracker.h"
#include "ProcessUtils.h"
#include "VRManager.h"
#include "VRManagerParent.h"
@@ -58,16 +59,17 @@ GPUParent::Init(base::ProcessId aParentP
#if defined(XP_WIN)
DeviceManagerDx::Init();
DeviceManagerD3D9::Init();
#endif
if (NS_FAILED(NS_InitMinimalXPCOM())) {
return false;
}
CompositorThreadHolder::Start();
+ APZThreadUtils::SetControllerThread(CompositorThreadHolder::Loop());
VRManager::ManagerInit();
LayerTreeOwnerTracker::Initialize();
mozilla::ipc::SetThisProcessName("GPU Process");
return true;
}
bool
GPUParent::RecvInit(nsTArray<GfxPrefSetting>&& prefs,
@@ -165,18 +167,22 @@ bool
GPUParent::RecvGetDeviceStatus(GPUDeviceData* aOut)
{
CopyFeatureChange(Feature::D3D11_COMPOSITING, &aOut->d3d11Compositing());
CopyFeatureChange(Feature::D3D9_COMPOSITING, &aOut->d3d9Compositing());
CopyFeatureChange(Feature::OPENGL_COMPOSITING, &aOut->oglCompositing());
#if defined(XP_WIN)
if (DeviceManagerDx* dm = DeviceManagerDx::Get()) {
- dm->ExportDeviceInfo(&aOut->d3d11Device());
+ D3D11DeviceStatus deviceStatus;
+ dm->ExportDeviceInfo(&deviceStatus);
+ aOut->gpuDevice() = deviceStatus;
}
+#else
+ aOut->gpuDevice() = null_t();
#endif
return true;
}
static void
OpenParent(RefPtr<CompositorBridgeParent> aParent,
Endpoint<PCompositorBridgeParent>&& aEndpoint)
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -3,16 +3,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/. */
#include "GPUProcessManager.h"
#include "GPUProcessHost.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/APZCTreeManagerChild.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/ImageBridgeChild.h"
#include "mozilla/layers/ImageBridgeParent.h"
#include "mozilla/layers/InProcessCompositorSession.h"
#include "mozilla/layers/LayerTreeOwnerTracker.h"
#include "mozilla/layers/RemoteCompositorSession.h"
#include "mozilla/widget/PlatformWidgetTypes.h"
#ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING
@@ -389,18 +390,27 @@ GPUProcessManager::CreateRemoteSession(n
CompositorWidgetChild* widget = new CompositorWidgetChild(dispatcher, observer);
if (!child->SendPCompositorWidgetConstructor(widget, initData)) {
return nullptr;
}
if (!child->SendInitialize(aRootLayerTreeId)) {
return nullptr;
}
+ RefPtr<APZCTreeManagerChild> apz = nullptr;
+ if (aUseAPZ) {
+ PAPZCTreeManagerChild* papz = child->SendPAPZCTreeManagerConstructor(0);
+ if (!papz) {
+ return nullptr;
+ }
+ apz = static_cast<APZCTreeManagerChild*>(papz);
+ }
+
RefPtr<RemoteCompositorSession> session =
- new RemoteCompositorSession(child, widget, aRootLayerTreeId);
+ new RemoteCompositorSession(child, widget, apz, aRootLayerTreeId);
return session.forget();
#else
gfxCriticalNote << "Platform does not support out-of-process compositing";
return nullptr;
#endif
}
bool
--- a/gfx/ipc/GraphicsMessages.ipdlh
+++ b/gfx/ipc/GraphicsMessages.ipdlh
@@ -49,22 +49,28 @@ struct FeatureFailure
// If a feature state has changed from Enabled -> Failure, this will be non-
// null.
union FeatureChange
{
null_t;
FeatureFailure;
};
+union GPUDeviceStatus
+{
+ null_t;
+ D3D11DeviceStatus;
+};
+
struct GPUDeviceData
{
FeatureChange d3d11Compositing;
FeatureChange d3d9Compositing;
FeatureChange oglCompositing;
- D3D11DeviceStatus d3d11Device;
+ GPUDeviceStatus gpuDevice;
};
union GfxVarValue
{
BackendType;
bool;
gfxImageFormat;
IntSize;
--- a/gfx/ipc/InProcessCompositorSession.cpp
+++ b/gfx/ipc/InProcessCompositorSession.cpp
@@ -49,17 +49,17 @@ InProcessCompositorSession::GetInProcess
}
void
InProcessCompositorSession::SetContentController(GeckoContentController* aController)
{
mCompositorBridgeParent->SetControllerForLayerTree(mRootLayerTreeId, aController);
}
-already_AddRefed<IAPZCTreeManager>
+RefPtr<IAPZCTreeManager>
InProcessCompositorSession::GetAPZCTreeManager() const
{
return mCompositorBridgeParent->GetAPZCTreeManager(mRootLayerTreeId);
}
void
InProcessCompositorSession::Shutdown()
{
--- a/gfx/ipc/InProcessCompositorSession.h
+++ b/gfx/ipc/InProcessCompositorSession.h
@@ -24,17 +24,17 @@ public:
const uint64_t& aRootLayerTreeId,
CSSToLayoutDeviceScale aScale,
bool aUseAPZ,
bool aUseExternalSurfaceSize,
const gfx::IntSize& aSurfaceSize);
CompositorBridgeParent* GetInProcessBridge() const override;
void SetContentController(GeckoContentController* aController) override;
- already_AddRefed<IAPZCTreeManager> GetAPZCTreeManager() const override;
+ RefPtr<IAPZCTreeManager> GetAPZCTreeManager() const override;
void Shutdown() override;
private:
InProcessCompositorSession(widget::CompositorWidget* aWidget,
CompositorBridgeChild* aChild,
CompositorBridgeParent* aParent);
private:
--- a/gfx/ipc/RemoteCompositorSession.cpp
+++ b/gfx/ipc/RemoteCompositorSession.cpp
@@ -1,44 +1,50 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
#include "RemoteCompositorSession.h"
+#include "mozilla/layers/APZChild.h"
+#include "mozilla/layers/APZCTreeManagerChild.h"
+
namespace mozilla {
namespace layers {
using namespace gfx;
using namespace widget;
RemoteCompositorSession::RemoteCompositorSession(CompositorBridgeChild* aChild,
CompositorWidgetDelegate* aWidgetDelegate,
+ APZCTreeManagerChild* aAPZ,
const uint64_t& aRootLayerTreeId)
: CompositorSession(aWidgetDelegate, aChild, aRootLayerTreeId)
+ , mAPZ(aAPZ)
{
}
CompositorBridgeParent*
RemoteCompositorSession::GetInProcessBridge() const
{
return nullptr;
}
void
RemoteCompositorSession::SetContentController(GeckoContentController* aController)
{
- MOZ_CRASH("NYI");
+ mCompositorBridgeChild->SendPAPZConstructor(new APZChild(aController), 0);
}
-already_AddRefed<IAPZCTreeManager>
+RefPtr<IAPZCTreeManager>
RemoteCompositorSession::GetAPZCTreeManager() const
{
- return nullptr;
+ return mAPZ;
}
void
RemoteCompositorSession::Shutdown()
{
mCompositorBridgeChild->Destroy();
mCompositorBridgeChild = nullptr;
mCompositorWidgetDelegate = nullptr;
--- a/gfx/ipc/RemoteCompositorSession.h
+++ b/gfx/ipc/RemoteCompositorSession.h
@@ -13,21 +13,24 @@
namespace mozilla {
namespace layers {
class RemoteCompositorSession final : public CompositorSession
{
public:
RemoteCompositorSession(CompositorBridgeChild* aChild,
CompositorWidgetDelegate* aWidgetDelegate,
+ APZCTreeManagerChild* aAPZ,
const uint64_t& aRootLayerTreeId);
CompositorBridgeParent* GetInProcessBridge() const override;
void SetContentController(GeckoContentController* aController) override;
- already_AddRefed<IAPZCTreeManager> GetAPZCTreeManager() const override;
+ RefPtr<IAPZCTreeManager> GetAPZCTreeManager() const override;
void Shutdown() override;
+private:
+ RefPtr<APZCTreeManagerChild> mAPZ;
};
} // namespace layers
} // namespace mozilla
#endif // include_mozilla_gfx_ipc_RemoteCompositorSession_h
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -22,17 +22,21 @@ namespace layers {
class GeckoContentController
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoContentController)
/**
* Requests a paint of the given FrameMetrics |aFrameMetrics| from Gecko.
* Implementations per-platform are responsible for actually handling this.
- * This method will always be called on the Gecko main thread.
+ *
+ * This method must always be called on the repaint thread, which depends
+ * on the GeckoContentController. For ChromeProcessController it is the
+ * Gecko main thread, while for RemoteContentController it is the compositor
+ * thread where it can send IPDL messages.
*/
virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) = 0;
/**
* Different types of tap-related events that can be sent in
* the HandleTap function. The names should be relatively self-explanatory.
* Note that the eLongTapUp will always be preceded by an eLongTap, but not
* all eLongTap notifications will be followed by an eLongTapUp (for instance,
--- a/gfx/layers/apz/util/ChromeProcessController.cpp
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -166,16 +166,20 @@ ChromeProcessController::HandleTap(TapTy
if (MessageLoop::current() != mUILoop) {
mUILoop->PostTask(NewRunnableMethod<TapType, mozilla::LayoutDevicePoint, Modifiers,
ScrollableLayerGuid, uint64_t>(this,
&ChromeProcessController::HandleTap,
aType, aPoint, aModifiers, aGuid, aInputBlockId));
return;
}
+ if (!mAPZEventState) {
+ return;
+ }
+
nsCOMPtr<nsIPresShell> presShell = GetPresShell();
if (!presShell) {
return;
}
if (!presShell->GetPresContext()) {
return;
}
CSSToLayoutDeviceScale scale(presShell->GetPresContext()->CSSToDevPixelScale());
@@ -212,16 +216,20 @@ ChromeProcessController::NotifyAPZStateC
mUILoop->PostTask(NewRunnableMethod
<ScrollableLayerGuid,
APZStateChange,
int>(this, &ChromeProcessController::NotifyAPZStateChange,
aGuid, aChange, aArg));
return;
}
+ if (!mAPZEventState) {
+ return;
+ }
+
mAPZEventState->ProcessAPZStateChange(GetRootDocument(), aGuid.mScrollId, aChange, aArg);
}
void
ChromeProcessController::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
{
if (MessageLoop::current() != mUILoop) {
mUILoop->PostTask(NewRunnableMethod
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -19,18 +19,24 @@ class MessageLoop;
namespace mozilla {
namespace layers {
class IAPZCTreeManager;
class APZEventState;
-// A ChromeProcessController is attached to the root of a compositor's layer
-// tree.
+/**
+ * ChromeProcessController is a GeckoContentController attached to the root of
+ * a compositor's layer tree. It's used directly by APZ by default, and remoted
+ * using PAPZ if there is a gpu process.
+ *
+ * If ChromeProcessController needs to implement a new method on GeckoContentController
+ * PAPZ, APZChild, and RemoteContentController must be updated to handle it.
+ */
class ChromeProcessController : public mozilla::layers::GeckoContentController
{
protected:
typedef mozilla::layers::FrameMetrics FrameMetrics;
typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
public:
explicit ChromeProcessController(nsIWidget* aWidget, APZEventState* aAPZEventState, IAPZCTreeManager* aAPZCTreeManager);
--- a/gfx/layers/apz/util/ContentProcessController.h
+++ b/gfx/layers/apz/util/ContentProcessController.h
@@ -16,16 +16,27 @@ namespace mozilla {
namespace dom {
class TabChild;
} // namespace dom
namespace layers {
class APZChild;
+/**
+ * ContentProcessController is a GeckoContentController for a TabChild, and is always
+ * remoted using PAPZ/APZChild.
+ *
+ * ContentProcessController is created in ContentChild when a layer tree id has
+ * been allocated for a PBrowser that lives in that content process, and is destroyed
+ * when the Destroy message is received, or when the tab dies.
+ *
+ * If ContentProcessController needs to implement a new method on GeckoContentController
+ * PAPZ, APZChild, and RemoteContentController must be updated to handle it.
+ */
class ContentProcessController final
: public GeckoContentController
{
public:
~ContentProcessController();
static APZChild* Create(const dom::TabId& aTabId);
--- a/gfx/layers/ipc/APZChild.h
+++ b/gfx/layers/ipc/APZChild.h
@@ -10,16 +10,20 @@
#include "mozilla/layers/PAPZChild.h"
namespace mozilla {
namespace layers {
class GeckoContentController;
+/**
+ * APZChild implements PAPZChild and is used to remote a GeckoContentController
+ * that lives in a different process than where APZ lives.
+ */
class APZChild final : public PAPZChild
{
public:
explicit APZChild(RefPtr<GeckoContentController> aController);
~APZChild();
bool RecvRequestContentRepaint(const FrameMetrics& frame) override;
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -1427,41 +1427,74 @@ CompositorBridgeParent::ForceComposeToTa
AutoRestore<bool> override(mOverrideComposeReadiness);
mOverrideComposeReadiness = true;
mCompositorScheduler->ForceComposeToTarget(aTarget, aRect);
}
PAPZCTreeManagerParent*
CompositorBridgeParent::AllocPAPZCTreeManagerParent(const uint64_t& aLayersId)
{
- return nullptr;
+ // The main process should pass in 0 because we assume mRootLayerTreeID
+ MOZ_ASSERT(aLayersId == 0);
+
+ // This message doubles as initialization
+ MOZ_ASSERT(!mApzcTreeManager);
+ mApzcTreeManager = new APZCTreeManager();
+
+ MonitorAutoLock lock(*sIndirectLayerTreesLock);
+ CompositorBridgeParent::LayerTreeState& state = sIndirectLayerTrees[mRootLayerTreeID];
+ MOZ_ASSERT(state.mParent);
+ MOZ_ASSERT(!state.mApzcTreeManagerParent);
+ state.mApzcTreeManagerParent = new APZCTreeManagerParent(mRootLayerTreeID, state.mParent->GetAPZCTreeManager());
+
+ return state.mApzcTreeManagerParent;
}
bool
CompositorBridgeParent::DeallocPAPZCTreeManagerParent(PAPZCTreeManagerParent* aActor)
{
- return false;
+ delete aActor;
+ return true;
}
PAPZParent*
CompositorBridgeParent::AllocPAPZParent(const uint64_t& aLayersId)
{
- return nullptr;
+ // The main process should pass in 0 because we assume mRootLayerTreeID
+ MOZ_ASSERT(aLayersId == 0);
+
+ RemoteContentController* controller = new RemoteContentController();
+
+ // Increment the controller's refcount before we return it. This will keep the
+ // controller alive until it is released by IPDL in DeallocPAPZParent.
+ controller->AddRef();
+
+ MonitorAutoLock lock(*sIndirectLayerTreesLock);
+ CompositorBridgeParent::LayerTreeState& state = sIndirectLayerTrees[mRootLayerTreeID];
+ MOZ_ASSERT(!state.mController);
+ state.mController = controller;
+
+ return controller;
}
bool
CompositorBridgeParent::DeallocPAPZParent(PAPZParent* aActor)
{
- return false;
+ RemoteContentController* controller = static_cast<RemoteContentController*>(aActor);
+ controller->Release();
+ return true;
}
bool
CompositorBridgeParent::RecvAsyncPanZoomEnabled(const uint64_t& aLayersId, bool* aHasAPZ)
{
- return false;
+ // The main process should pass in 0 because we assume mRootLayerTreeID
+ MOZ_ASSERT(aLayersId == 0);
+ *aHasAPZ = AsyncPanZoomEnabled();
+ return true;
}
RefPtr<APZCTreeManager>
CompositorBridgeParent::GetAPZCTreeManager()
{
return mApzcTreeManager;
}
--- a/gfx/layers/ipc/PAPZ.ipdl
+++ b/gfx/layers/ipc/PAPZ.ipdl
@@ -24,35 +24,42 @@ using mozilla::layers::AsyncDragMetrics
using mozilla::Modifiers from "mozilla/EventForwards.h";
using class nsRegion from "nsRegion.h";
namespace mozilla {
namespace layers {
/**
- * If APZ is enabled then one PAPZ will be opened per PBrowser between the
- * process where the PBrowser child actor lives and the main process (the
- * PBrowser parent actor doesn't necessarily live in the main process, for
- * example with nested browsers). This will typically be set up when the layers
- * id is allocated for the PBrowser.
+ * PAPZ is a protocol for remoting a GeckoContentController. PAPZ lives on the
+ * PCompositorBridge protocol which either connects to the compositor thread
+ * in the main process, or to the compositor thread in the gpu processs.
*
- * Opened through PContent and runs on the main thread in both parent and child.
+ * PAPZParent lives in the compositor thread, while PAPZChild lives wherever the remoted
+ * GeckoContentController lives (generally the main thread of the main or content process).
+ * RemoteContentController implements PAPZParent, while APZChild implements PAPZChild.
+ *
+ * PAPZ is always used for ContentProcessController and only used for ChromeProcessController
+ * when there is a gpu process, otherwhise ChromeProcessController is used directly on the
+ * compositor thread. Only the methods that are used by the [Chrome,Content]ProcessController
+ * are implemented. If a new method is needed then PAPZ, APZChild, and RemoteContentController
+ * must be updated to handle it.
*/
sync protocol PAPZ
{
manager PCompositorBridge;
parent:
async UpdateHitRegion(nsRegion aRegion);
async __delete__();
child:
+
async RequestContentRepaint(FrameMetrics frame);
// The aCallTakeFocusForClickFromTap argument is used for eSingleTap types,
// to request that the child take focus before dispatching the mouse events
// for the tap (otherwise the resulting focus behaviour is incorrect).
async HandleTap(TapType aType, LayoutDevicePoint point, Modifiers aModifiers,
ScrollableLayerGuid aGuid, uint64_t aInputBlockId,
bool aCallTakeFocusForClickFromTap);
--- a/gfx/layers/ipc/PAPZCTreeManager.ipdl
+++ b/gfx/layers/ipc/PAPZCTreeManager.ipdl
@@ -29,16 +29,25 @@ using class mozilla::MouseInput from "In
using class mozilla::PanGestureInput from "InputData.h";
using class mozilla::PinchGestureInput from "InputData.h";
using class mozilla::TapGestureInput from "InputData.h";
using class mozilla::ScrollWheelInput from "InputData.h";
namespace mozilla {
namespace layers {
+/**
+ * PAPZCTreeManager is a protocol for remoting an IAPZCTreeManager. PAPZCTreeManager
+ * lives on the PCompositorBridge protocol which either connects to the compositor
+ * thread in the main process, or to the compositor thread in the gpu processs.
+ *
+ * PAPZCTreeManagerParent lives in the compositor thread, while PAPZCTreeManagerChild
+ * lives in the main thread of the main or the content process. APZCTreeManagerParent
+ * and APZCTreeManagerChild implement this protocol.
+ */
sync protocol PAPZCTreeManager
{
manager PCompositorBridge;
parent:
// These messages correspond to the methods
// on the IAPZCTreeManager interface
--- a/gfx/layers/ipc/RemoteContentController.h
+++ b/gfx/layers/ipc/RemoteContentController.h
@@ -15,21 +15,24 @@ namespace mozilla {
namespace dom {
class TabParent;
}
namespace layers {
/**
- * RemoteContentController uses the PAPZ protocol to implement a
- * GeckoContentController for a browser living in a remote process.
- * Most of the member functions can be called on any thread, exceptions are
- * annotated in comments. The PAPZ protocol runs on the main thread (so all the
- * Recv* member functions do too).
+ * RemoteContentController implements PAPZChild and is used to access a
+ * GeckoContentController that lives in a different process.
+ *
+ * RemoteContentController lives on the compositor thread. All methods can
+ * be called off the compositor thread and will get dispatched to the right
+ * thread, with the exception of RequestContentRepaint and NotifyFlushComplete,
+ * which must be called on the repaint thread, which in this case is the compositor
+ * thread.
*/
class RemoteContentController : public GeckoContentController
, public PAPZParent
{
using GeckoContentController::TapType;
using GeckoContentController::APZStateChange;
public:
--- a/gfx/skia/skia/src/ports/SkFontHost_cairo.cpp
+++ b/gfx/skia/skia/src/ports/SkFontHost_cairo.cpp
@@ -214,16 +214,19 @@ public:
if (!fPattern && isLCD(*rec)) {
rec->fMaskFormat = SkMask::kA8_Format;
}
// rotated text looks bad with hinting, so we disable it as needed
if (!gFontHintingEnabled || !isAxisAligned(*rec)) {
rec->setHinting(SkPaint::kNo_Hinting);
}
+
+ // Don't apply any gamma so that we match cairo-ft's results.
+ rec->ignorePreBlend();
}
virtual void onGetFontDescriptor(SkFontDescriptor*, bool*) const override
{
SkDEBUGCODE(SkDebugf("SkCairoFTTypeface::onGetFontDescriptor unimplemented\n"));
}
virtual int onCharsToGlyphs(void const*, SkTypeface::Encoding, uint16_t*, int) const override
@@ -755,17 +758,17 @@ void SkScalerContext_CairoFT::generateIm
}
void SkScalerContext_CairoFT::generatePath(const SkGlyph& glyph, SkPath* path)
{
SkASSERT(fScaledFont != nullptr);
CairoLockedFTFace faceLock(fScaledFont);
FT_Face face = faceLock.getFace();
- SkASSERT(&glyph && path);
+ SkASSERT(path);
uint32_t flags = fLoadGlyphFlags;
flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline
flags &= ~FT_LOAD_RENDER; // don't scan convert (we just want the outline)
FT_Error err = FT_Load_Glyph(face, glyph.getGlyphID(), flags);
if (err != 0) {
@@ -775,17 +778,19 @@ void SkScalerContext_CairoFT::generatePa
prepareGlyph(face->glyph);
generateGlyphPath(face, path);
}
void SkScalerContext_CairoFT::generateFontMetrics(SkPaint::FontMetrics* metrics)
{
- SkDEBUGCODE(SkDebugf("SkScalerContext_CairoFT::generateFontMetrics unimplemented\n"));
+ if (metrics) {
+ memset(metrics, 0, sizeof(SkPaint::FontMetrics));
+ }
}
SkUnichar SkScalerContext_CairoFT::generateGlyphToChar(uint16_t glyph)
{
SkASSERT(fScaledFont != nullptr);
CairoLockedFTFace faceLock(fScaledFont);
FT_Face face = faceLock.getFace();
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1999,17 +1999,17 @@ gfxWindowsPlatform::ImportGPUDeviceData(
gfxPlatform::ImportGPUDeviceData(aData);
gfxConfig::ImportChange(Feature::D3D11_COMPOSITING, aData.d3d11Compositing());
gfxConfig::ImportChange(Feature::D3D9_COMPOSITING, aData.d3d9Compositing());
DeviceManagerDx* dm = DeviceManagerDx::Get();
if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
- dm->ImportDeviceInfo(aData.d3d11Device());
+ dm->ImportDeviceInfo(aData.gpuDevice().get_D3D11DeviceStatus());
} else {
// There should be no devices, so this just takes away the device status.
dm->ResetDevices();
// Make sure we disable D2D if content processes might use it.
FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D);
if (d2d1.IsEnabled()) {
d2d1.SetFailed(
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -18,16 +18,20 @@
#include "SharedMemoryBasic.h"
#endif
#include "MainThreadUtils.h"
#include "mozilla/Sprintf.h"
#include "prenv.h"
#include "nsXPCOMPrivate.h"
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+#include "nsAppDirectoryServiceDefs.h"
+#endif
+
#include "nsExceptionHandler.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIFile.h"
#include "nsPrintfCString.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ipc/BrowserProcessSubThread.h"
@@ -603,16 +607,30 @@ AddAppDirToCommandLine(std::vector<std::
aCmdLine.AppendLooseValue(wpath);
#else
nsAutoCString path;
MOZ_ALWAYS_SUCCEEDS(appDir->GetNativePath(path));
aCmdLine.push_back("-appdir");
aCmdLine.push_back(path.get());
#endif
}
+
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+ // Full path to the profile dir
+ nsCOMPtr<nsIFile> profileDir;
+ rv = directoryService->Get(NS_APP_USER_PROFILE_50_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(profileDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString path;
+ MOZ_ALWAYS_SUCCEEDS(profileDir->GetNativePath(path));
+ aCmdLine.push_back("-profile");
+ aCmdLine.push_back(path.get());
+ }
+#endif
}
}
}
#if defined(XP_WIN) && defined(MOZ_SANDBOX)
static void
MaybeAddNsprLogFileAccess(std::vector<std::wstring>& aAllowedFilesReadWrite)
{
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -459,10 +459,22 @@ UnionTypeReadError(const char* aUnionNam
}
void ArrayLengthReadError(const char* aElementName)
{
nsPrintfCString message("error deserializing length of %s[]", aElementName);
NS_RUNTIMEABORT(message.get());
}
+void
+TableToArray(const nsTHashtable<nsPtrHashKey<void>>& aTable,
+ nsTArray<void*>& aArray)
+{
+ uint32_t i = 0;
+ void** elements = aArray.AppendElements(aTable.Count());
+ for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) {
+ elements[i] = iter.Get()->GetKey();
+ ++i;
+ }
+}
+
} // namespace ipc
} // namespace mozilla
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=4 ts=4 et :
*/
/* 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/. */
#ifndef mozilla_ipc_ProtocolUtils_h
#define mozilla_ipc_ProtocolUtils_h 1
@@ -116,21 +116,23 @@ struct ActorHandle
// that they can share the same state machine implementation. To
// further normalize, |Send| is used for 'call', |Recv| for 'answer'.
struct Trigger
{
enum Action { Send, Recv };
Trigger(Action action, int32_t msg) :
mAction(action),
- mMsg(msg)
- {}
+ mMessage(msg)
+ {
+ MOZ_ASSERT(0 <= msg && msg < INT32_MAX);
+ }
- Action mAction;
- int32_t mMsg;
+ uint32_t mAction : 1;
+ uint32_t mMessage : 31;
};
class ProtocolCloneContext
{
typedef mozilla::dom::ContentParent ContentParent;
typedef mozilla::net::NeckoParent NeckoParent;
RefPtr<ContentParent> mContentParent;
@@ -599,20 +601,41 @@ CreateEndpoints(const PrivateIPDLInterfa
parentTransport, aParentDestPid, aChildDestPid, aProtocol);
*aChildEndpoint = Endpoint<PFooChild>(aPrivate, mozilla::ipc::Transport::MODE_CLIENT,
childTransport, aChildDestPid, aParentDestPid, aChildProtocol);
return NS_OK;
}
+void
+TableToArray(const nsTHashtable<nsPtrHashKey<void>>& aTable,
+ nsTArray<void*>& aArray);
+
} // namespace ipc
template<typename Protocol>
-using ManagedContainer = nsTHashtable<nsPtrHashKey<Protocol>>;
+class ManagedContainer : public nsTHashtable<nsPtrHashKey<Protocol>>
+{
+ typedef nsTHashtable<nsPtrHashKey<Protocol>> BaseClass;
+
+public:
+ // Having the core logic work on void pointers, rather than typed pointers,
+ // means that we can have one instance of this code out-of-line, rather
+ // than several hundred instances of this code out-of-lined. (Those
+ // repeated instances don't necessarily get folded together by the linker
+ // because they contain member offsets and such that differ between the
+ // functions.) We do have to pay for it with some eye-bleedingly bad casts,
+ // though.
+ void ToArray(nsTArray<Protocol*>& aArray) const {
+ ::mozilla::ipc::TableToArray(*reinterpret_cast<const nsTHashtable<nsPtrHashKey<void>>*>
+ (static_cast<const BaseClass*>(this)),
+ reinterpret_cast<nsTArray<void*>&>(aArray));
+ }
+};
template<typename Protocol>
Protocol*
LoneManagedOrNullAsserts(const ManagedContainer<Protocol>& aManagees)
{
if (aManagees.IsEmpty()) {
return nullptr;
}
--- a/ipc/ipdl/ipdl/builtin.py
+++ b/ipc/ipdl/ipdl/builtin.py
@@ -45,14 +45,15 @@ HeaderIncludes = (
'mozilla/Attributes.h',
'IPCMessageStart.h',
'ipc/IPCMessageUtils.h',
'mozilla/RefPtr.h',
'nsStringGlue.h',
'nsTArray.h',
'mozilla/ipc/ProtocolUtils.h',
'nsTHashtable.h',
+ 'mozilla/OperatorNewExtensions.h',
)
CppIncludes = (
'nsIFile.h',
'GeckoProfiler.h',
)
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -846,17 +846,17 @@ IPDL union type."""
if self.recursive:
return ExprAssn(self.callGetPtr(),
ExprNew(self.bareType(self.side),
args=args))
else:
return ExprNew(self.bareType(self.side),
args=args,
- newargs=[ self.callGetPtr() ])
+ newargs=[ ExprVar('mozilla::KnownNotNull'), self.callGetPtr() ])
def callDtor(self):
if self.recursive:
return ExprDelete(self.callGetPtr())
else:
return ExprCall(
ExprSelect(self.callGetPtr(), '->', '~'+ self.typedef()))
@@ -1792,27 +1792,27 @@ class _GenerateProtocolCode(ipdl.ast.Vis
actionexpr)
def stateEnum(s):
if s is ipdl.ast.State.DEAD:
return _deadState()
else:
return ExprVar(s.decl.cxxname)
- # bool Transition(State from, Trigger trigger, State* next)
+ # bool Transition(Trigger trigger, State* next)
+ # The state we are transitioning from is stored in *next.
fromvar = ExprVar('from')
triggervar = ExprVar('trigger')
nextvar = ExprVar('next')
- msgexpr = ExprSelect(triggervar, '.', 'mMsg')
+ msgexpr = ExprSelect(triggervar, '.', 'mMessage')
actionexpr = ExprSelect(triggervar, '.', 'mAction')
transitionfunc = FunctionDefn(FunctionDecl(
'Transition',
- params=[ Decl(Type('State'), fromvar.name),
- Decl(Type('mozilla::ipc::Trigger'), triggervar.name),
+ params=[ Decl(Type('mozilla::ipc::Trigger'), triggervar.name),
Decl(Type('State', ptr=1), nextvar.name) ],
ret=Type.BOOL))
fromswitch = StmtSwitch(fromvar)
for ts in self.protocol.transitionStmts:
msgswitch = StmtSwitch(msgexpr)
@@ -1897,16 +1897,18 @@ class _GenerateProtocolCode(ipdl.ast.Vis
init=ExprVar('mozilla::ipc::Trigger::Send')))
if userecv:
transitionfunc.addstmt(
StmtDecl(Decl(Type('int32_t', const=1), recvvar.name),
init=ExprVar('mozilla::ipc::Trigger::Recv')))
if usesend or userecv:
transitionfunc.addstmt(Whitespace.NL)
+ transitionfunc.addstmt(StmtDecl(Decl(Type('State'), fromvar.name),
+ init=ExprDeref(nextvar)))
transitionfunc.addstmt(fromswitch)
# all --> Error transitions break to here. But only insert this
# block if there is any possibility of such transitions.
if self.protocol.transitionStmts:
transitionfunc.addstmts([
StmtExpr(ExprAssn(ExprDeref(nextvar), _errorState())),
StmtReturn(ExprLiteral.FALSE),
])
@@ -3081,32 +3083,20 @@ class _GenerateProtocolActorCode(ipdl.as
## const Array<T>& Managed() const
for managed in ptype.manages:
arrvar = ExprVar('aArr')
meth = MethodDefn(MethodDecl(
p.managedMethod(managed, self.side).name,
params=[ Decl(_cxxArrayType(p.managedCxxType(managed, self.side), ref=1),
arrvar.name) ],
const=1))
- ivar = ExprVar('i')
- elementsvar = ExprVar('elements')
- itervar = ExprVar('iter')
- meth.addstmt(StmtDecl(Decl(Type.UINT32, ivar.name),
- init=ExprLiteral.ZERO))
- meth.addstmt(StmtDecl(Decl(Type(_actorName(managed.name(), self.side), ptrptr=1), elementsvar.name),
- init=ExprCall(ExprSelect(arrvar, '.', 'AppendElements'),
- args=[ ExprCall(ExprSelect(p.managedVar(managed, self.side),
- '.', 'Count')) ])))
- foreachaccumulate = forLoopOverHashtable(p.managedVar(managed, self.side),
- itervar, const=True)
- foreachaccumulate.addstmt(StmtExpr(
- ExprAssn(ExprIndex(elementsvar, ivar),
- actorFromIter(itervar))))
- foreachaccumulate.addstmt(StmtExpr(ExprPrefixUnop(ivar, '++')))
- meth.addstmt(foreachaccumulate)
+ meth.addstmt(StmtExpr(
+ ExprCall(ExprSelect(p.managedVar(managed, self.side),
+ '.', 'ToArray'),
+ args=[ arrvar ])))
refmeth = MethodDefn(MethodDecl(
p.managedMethod(managed, self.side).name,
params=[ ],
ret=p.managedVarType(managed, self.side, const=1, ref=1),
const=1))
refmeth.addstmt(StmtReturn(p.managedVar(managed, self.side)))
@@ -5436,18 +5426,17 @@ class _GenerateProtocolActorCode(ipdl.as
or self.side is 'child' and direction is 'out'):
action = ExprVar('Trigger::Recv')
else: assert 0 and 'unknown combo %s/%s'% (self.side, direction)
msgid = md.pqMsgId() if not reply else md.pqReplyId()
ifbad = StmtIf(ExprNot(
ExprCall(
ExprVar(self.protocol.name +'::Transition'),
- args=[ stateexpr,
- ExprCall(ExprVar('Trigger'),
+ args=[ ExprCall(ExprVar('Trigger'),
args=[ action, ExprVar(msgid) ]),
ExprAddrOf(stateexpr) ])))
ifbad.addifstmts(_badTransition())
return [ ifbad ]
def checkedRead(self, ipdltype, expr, msgexpr, iterexpr, errfn, paramtype, sentinelKey, sentinel=True):
ifbad = StmtIf(ExprNot(self.read(ipdltype, expr, msgexpr, iterexpr)))
if isinstance(paramtype, list):
--- a/ipc/mscom/COMPtrHolder.h
+++ b/ipc/mscom/COMPtrHolder.h
@@ -127,17 +127,17 @@ struct ParamTraits<mozilla::mscom::COMPt
mozilla::mscom::ProxyStream proxyStream(buf.get(), length);
if (!proxyStream.IsValid()) {
return false;
}
Interface* rawInterface = nullptr;
if (!proxyStream.GetInterface(_IID, (void**)&rawInterface)) {
return false;
}
- paramType::COMPtrType ptr(rawInterface);
+ typename paramType::COMPtrType ptr(rawInterface);
aResult->Set(mozilla::Move(ptr));
return true;
}
};
} // namespace IPC
#endif // mozilla_mscom_COMPtrHolder_h
--- a/ipc/mscom/EnsureMTA.h
+++ b/ipc/mscom/EnsureMTA.h
@@ -7,17 +7,17 @@
#ifndef mozilla_mscom_EnsureMTA_h
#define mozilla_mscom_EnsureMTA_h
#include "MainThreadUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Function.h"
#include "mozilla/mscom/COMApartmentRegion.h"
-#include "mozilla/mscom/utils.h"
+#include "mozilla/mscom/Utils.h"
#include "nsCOMPtr.h"
#include "nsIThread.h"
#include "nsThreadUtils.h"
#include <windows.h>
namespace mozilla {
namespace mscom {
--- a/ipc/mscom/Interceptor.cpp
+++ b/ipc/mscom/Interceptor.cpp
@@ -6,17 +6,17 @@
#define INITGUID
#include "mozilla/mscom/Interceptor.h"
#include "mozilla/mscom/InterceptorLog.h"
#include "mozilla/mscom/DispatchForwarder.h"
#include "mozilla/mscom/MainThreadInvoker.h"
#include "mozilla/mscom/Registration.h"
-#include "mozilla/mscom/utils.h"
+#include "mozilla/mscom/Utils.h"
#include "MainThreadUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsThreadUtils.h"
namespace mozilla {
--- a/ipc/mscom/MainThreadHandoff.cpp
+++ b/ipc/mscom/MainThreadHandoff.cpp
@@ -3,17 +3,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/. */
#include "mozilla/mscom/MainThreadHandoff.h"
#include "mozilla/mscom/InterceptorLog.h"
#include "mozilla/mscom/Registration.h"
-#include "mozilla/mscom/utils.h"
+#include "mozilla/mscom/Utils.h"
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "nsThreadUtils.h"
using mozilla::DebugOnly;
namespace {
--- a/ipc/mscom/ProxyStream.cpp
+++ b/ipc/mscom/ProxyStream.cpp
@@ -2,17 +2,17 @@
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DynamicallyLinkedFunctionPtr.h"
#include "mozilla/mscom/EnsureMTA.h"
#include "mozilla/mscom/ProxyStream.h"
-#include "mozilla/mscom/utils.h"
+#include "mozilla/mscom/Utils.h"
#include "mozilla/Move.h"
#include <windows.h>
#include <objbase.h>
#include <shlwapi.h>
namespace mozilla {
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -776,17 +776,17 @@ ParseVarOrConstStatement(AsmJSParser& pa
TokenKind tk;
if (!PeekToken(parser, &tk))
return false;
if (tk != TOK_VAR && tk != TOK_CONST) {
*var = nullptr;
return true;
}
- *var = parser.statement(YieldIsName);
+ *var = parser.statementListItem(YieldIsName);
if (!*var)
return false;
MOZ_ASSERT((*var)->isKind(PNK_VAR) || (*var)->isKind(PNK_CONST));
return true;
}
/*****************************************************************************/
@@ -7201,17 +7201,17 @@ CheckModuleReturn(ModuleValidator& m)
TokenStream& ts = m.parser().tokenStream;
if (tk != TOK_RETURN) {
return m.failCurrentOffset((tk == TOK_RC || tk == TOK_EOF)
? "expecting return statement"
: "invalid asm.js. statement");
}
ts.ungetToken();
- ParseNode* returnStmt = m.parser().statement(YieldIsName);
+ ParseNode* returnStmt = m.parser().statementListItem(YieldIsName);
if (!returnStmt)
return false;
ParseNode* returnExpr = ReturnExpr(returnStmt);
if (!returnExpr)
return m.fail(returnStmt, "export statement must return something");
if (returnExpr->isKind(PNK_OBJECT)) {
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -693,46 +693,59 @@ class FunctionCompiler
void assign(unsigned slot, MDefinition* def)
{
if (inDeadCode())
return;
curBlock_->setSlot(info().localSlot(slot), def);
}
private:
+ // False means we're sure to be out-of-bounds after this bounds check.
+ bool maybeAddBoundsCheck(MDefinition* base, const MWasmMemoryAccess& access)
+ {
+ if (access.offset() > uint32_t(INT32_MAX)) {
+ curBlock_->end(MWasmTrap::New(alloc(), Trap::OutOfBounds));
+ curBlock_ = nullptr;
+ return false;
+ }
+ if (!mg().usesSignal.forOOB)
+ curBlock_->add(MWasmBoundsCheck::New(alloc(), base, access));
+ return true;
+ }
+
MDefinition* loadHeapPrivate(MDefinition* base, const MWasmMemoryAccess& access,
bool isInt64 = false)
{
if (inDeadCode())
return nullptr;
MInstruction* load = nullptr;
if (mg().isAsmJS()) {
load = MAsmJSLoadHeap::New(alloc(), base, access);
} else {
- if (!mg().usesSignal.forOOB)
- curBlock_->add(MWasmBoundsCheck::New(alloc(), base, access));
+ if (!maybeAddBoundsCheck(base, access))
+ return nullptr;
load = MWasmLoad::New(alloc(), base, access, isInt64);
}
curBlock_->add(load);
return load;
}
void storeHeapPrivate(MDefinition* base, const MWasmMemoryAccess& access, MDefinition* v)
{
if (inDeadCode())
return;
MInstruction* store = nullptr;
if (mg().isAsmJS()) {
store = MAsmJSStoreHeap::New(alloc(), base, access, v);
} else {
- if (!mg().usesSignal.forOOB)
- curBlock_->add(MWasmBoundsCheck::New(alloc(), base, access));
+ if (!maybeAddBoundsCheck(base, access))
+ return;
store = MWasmStore::New(alloc(), base, access, v);
}
curBlock_->add(store);
}
public:
MDefinition* loadHeap(MDefinition* base, const MWasmMemoryAccess& access, bool isInt64)
@@ -1104,17 +1117,17 @@ class FunctionCompiler
curBlock_ = nullptr;
}
void unreachableTrap()
{
if (inDeadCode())
return;
- auto* ins = MAsmThrowUnreachable::New(alloc());
+ auto* ins = MWasmTrap::New(alloc(), wasm::Trap::Unreachable);
curBlock_->end(ins);
curBlock_ = nullptr;
}
private:
static bool hasPushed(MBasicBlock* block)
{
uint32_t numPushed = block->stackDepth() - block->info().firstStackSlot();
--- a/js/src/builtin/Sorting.js
+++ b/js/src/builtin/Sorting.js
@@ -96,29 +96,34 @@ function SortByColumn(array, len, aux, c
function RadixSort(array, len, buffer, nbytes, signed, floating, comparefn) {
// Determined by performance testing.
if (len < 128) {
QuickSort(array, len, comparefn);
return array;
}
- // Verify that the buffer is non-null
- assert(buffer !== null, "Attached data buffer should be reified when array length is >= 128.");
-
let aux = new List();
for (let i = 0; i < len; i++) {
aux[i] = 0;
}
let view = array;
let signMask = 1 << nbytes * 8 - 1;
// Preprocess
if (floating) {
+ // This happens if the array object is constructed under JIT
+ if (buffer === null) {
+ buffer = callFunction(std_TypedArray_buffer, array);
+ }
+
+ // Verify that the buffer is non-null
+ assert(buffer !== null, "Attached data buffer should be reified when array length is >= 128.");
+
view = new Int32Array(buffer);
// Flip sign bit for positive numbers; flip all bits for negative
// numbers
for (let i = 0; i < len; i++) {
if (view[i] & signMask) {
view[i] ^= 0xFFFFFFFF;
} else {
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -761,17 +761,17 @@ Parser<ParseHandler>::parse()
ParseContext globalpc(this, &globalsc, /* newDirectives = */ nullptr);
if (!globalpc.init())
return null();
ParseContext::VarScope varScope(this);
if (!varScope.init(pc))
return null();
- Node pn = statements(YieldIsName);
+ Node pn = statementList(YieldIsName);
if (!pn)
return null();
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (tt != TOK_EOF) {
report(ParseError, false, null(), JSMSG_GARBAGE_AFTER_INPUT,
@@ -1291,17 +1291,17 @@ Parser<ParseHandler>::propagateFreeNames
template <>
bool
Parser<FullParseHandler>::checkStatementsEOF()
{
// This is designed to be paired with parsing a statement list at the top
// level.
//
- // The statements() call breaks on TOK_RC, so make sure we've
+ // The statementList() call breaks on TOK_RC, so make sure we've
// reached EOF here.
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return false;
if (tt != TOK_EOF) {
report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
"expression", TokenKindToDesc(tt));
return false;
@@ -1727,17 +1727,17 @@ Parser<FullParseHandler>::evalBody(EvalS
if (!varScope.init(pc))
return nullptr;
// All evals have an implicit non-extensible lexical scope.
ParseContext::Scope lexicalScope(this);
if (!lexicalScope.init(pc))
return nullptr;
- ParseNode* body = statements(YieldIsName);
+ ParseNode* body = statementList(YieldIsName);
if (!body)
return nullptr;
if (!checkStatementsEOF())
return nullptr;
body = finishLexicalScope(lexicalScope, body);
if (!body)
@@ -1800,17 +1800,17 @@ Parser<FullParseHandler>::globalBody(Glo
ParseContext globalpc(this, globalsc, /* newDirectives = */ nullptr);
if (!globalpc.init())
return nullptr;
ParseContext::VarScope varScope(this);
if (!varScope.init(pc))
return nullptr;
- ParseNode* body = statements(YieldIsName);
+ ParseNode* body = statementList(YieldIsName);
if (!body)
return nullptr;
if (!checkStatementsEOF())
return nullptr;
if (!FoldConstants(context, &body, this))
return nullptr;
@@ -1839,17 +1839,17 @@ Parser<FullParseHandler>::moduleBody(Mod
ParseContext::VarScope varScope(this);
if (!varScope.init(pc))
return nullptr;
Node mn = handler.newModule();
if (!mn)
return null();
- ParseNode* pn = statements(YieldIsKeyword);
+ ParseNode* pn = statementList(YieldIsKeyword);
if (!pn)
return null();
MOZ_ASSERT(pn->isKind(PNK_STATEMENTLIST));
mn->pn_body = pn;
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
@@ -2244,17 +2244,17 @@ Parser<ParseHandler>::functionBody(InHan
MOZ_ASSERT(!pc->funHasReturnExpr && !pc->funHasReturnVoid);
#ifdef DEBUG
uint32_t startYieldOffset = pc->lastYieldOffset;
#endif
Node pn;
if (type == StatementListBody) {
- pn = statements(yieldHandling);
+ pn = statementList(yieldHandling);
if (!pn)
return null();
} else {
MOZ_ASSERT(type == ExpressionBody);
Node kid = assignExpr(inHandling, yieldHandling, TripledotProhibited);
if (!kid)
return null();
@@ -2709,48 +2709,47 @@ Parser<ParseHandler>::checkFunctionDefin
if (kind == Statement) {
TokenPos pos = handler.getPosition(pn);
RootedPropertyName funName(context, funAtom->asPropertyName());
// In sloppy mode, Annex B.3.2 allows labelled function
// declarations. Otherwise it is a parse error.
ParseContext::Statement* declaredInStmt = pc->innermostStatement();
if (declaredInStmt && declaredInStmt->kind() == StatementKind::Label) {
- if (pc->sc()->strict()) {
- reportWithOffset(ParseError, false, pos.begin, JSMSG_FUNCTION_LABEL);
- return false;
- }
-
- // Find the innermost non-label statement.
+ MOZ_ASSERT(!pc->sc()->strict(),
+ "labeled functions shouldn't be parsed in strict mode");
+
+ // Find the innermost non-label statement. Report an error if it's
+ // unbraced: functions can't appear in it. Otherwise the statement
+ // (or its absence) determines the scope the function's bound in.
while (declaredInStmt && declaredInStmt->kind() == StatementKind::Label)
declaredInStmt = declaredInStmt->enclosing();
if (declaredInStmt && !StatementKindIsBraced(declaredInStmt->kind())) {
reportWithOffset(ParseError, false, pos.begin, JSMSG_SLOPPY_FUNCTION_LABEL);
return false;
}
}
if (declaredInStmt) {
- DeclarationKind declKind = DeclarationKind::LexicalFunction;
- if (!checkLexicalDeclarationDirectlyWithinBlock(*declaredInStmt, declKind, pos))
- return false;
+ MOZ_ASSERT(declaredInStmt->kind() != StatementKind::Label);
+ MOZ_ASSERT(StatementKindIsBraced(declaredInStmt->kind()));
if (!pc->sc()->strict()) {
// Under sloppy mode, try Annex B.3.3 semantics. If making an
// additional 'var' binding of the same name does not throw an
// early error, do so. This 'var' binding would be assigned
// the function object when its declaration is reached, not at
// the start of the block.
if (!tryDeclareVarForAnnexBLexicalFunction(funName, tryAnnexB))
return false;
}
- if (!noteDeclaredName(funName, declKind, pos))
+ if (!noteDeclaredName(funName, DeclarationKind::LexicalFunction, pos))
return false;
} else {
if (!noteDeclaredName(funName, DeclarationKind::BodyLevelFunction, pos))
return false;
// Body-level functions in modules are always closed over.
if (pc->atModuleLevel())
pc->varScope().lookupDeclaredName(funName)->value()->setClosedOver();
@@ -3523,24 +3522,19 @@ Parser<ParseHandler>::maybeParseDirectiv
if (pc->isFunctionBox())
return asmJS(list);
return report(ParseWarning, false, pn, JSMSG_USE_ASM_DIRECTIVE_FAIL);
}
}
return true;
}
-/*
- * Parse the statements in a block, creating a StatementList node that lists
- * the statements. If called from block-parsing code, the caller must match
- * '{' before and '}' after.
- */
template <typename ParseHandler>
typename ParseHandler::Node
-Parser<ParseHandler>::statements(YieldHandling yieldHandling)
+Parser<ParseHandler>::statementList(YieldHandling yieldHandling)
{
JS_CHECK_RECURSION(context, return null());
Node pn = handler.newStatementList(pos());
if (!pn)
return null();
bool canHaveDirectives = pc->atBodyLevel();
@@ -3557,17 +3551,17 @@ Parser<ParseHandler>::statements(YieldHa
if (tt == TOK_EOF || tt == TOK_RC)
break;
if (afterReturn) {
TokenPos pos(0, 0);
if (!tokenStream.peekTokenPos(&pos, TokenStream::Operand))
return null();
statementBegin = pos.begin;
}
- Node next = statement(yieldHandling, canHaveDirectives);
+ Node next = statementListItem(yieldHandling, canHaveDirectives);
if (!next) {
if (tokenStream.isEOF())
isUnexpectedEOF_ = true;
return null();
}
if (!warnedAboutStatementsAfterReturn) {
if (afterReturn) {
if (!handler.isStatementPermittedAfterReturnStatement(next)) {
@@ -3952,17 +3946,17 @@ Parser<ParseHandler>::blockStatement(Yie
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
ParseContext::Statement stmt(pc, StatementKind::Block);
ParseContext::Scope scope(this);
if (!scope.init(pc))
return null();
- Node list = statements(yieldHandling);
+ Node list = statementList(yieldHandling);
if (!list)
return null();
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, errorNumber);
return finishLexicalScope(scope, list);
}
@@ -4859,16 +4853,37 @@ Parser<ParseHandler>::expressionStatemen
Node pnexpr = expr(InAllowed, yieldHandling, TripledotProhibited, invoked);
if (!pnexpr)
return null();
if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
return null();
return handler.newExprStatement(pnexpr, pos().end);
}
+template <class ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::consequentOrAlternative(YieldHandling yieldHandling)
+{
+ TokenKind next;
+ if (!tokenStream.peekToken(&next, TokenStream::Operand))
+ return null();
+
+ if (next == TOK_FUNCTION) {
+ // Apply Annex B.3.4 in non-strict code to allow FunctionDeclaration as
+ // the consequent/alternative of an |if| or |else|. Parser::statement
+ // will report the strict mode error.
+ if (!pc->sc()->strict()) {
+ tokenStream.consumeKnownToken(next, TokenStream::Operand);
+ return functionStmt(yieldHandling, NameRequired);
+ }
+ }
+
+ return statement(yieldHandling);
+}
+
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::ifStatement(YieldHandling yieldHandling)
{
Vector<Node, 4> condList(context), thenList(context);
Vector<uint32_t, 4> posList(context);
Node elseBranch;
@@ -4885,32 +4900,32 @@ Parser<ParseHandler>::ifStatement(YieldH
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::Operand))
return null();
if (tt == TOK_SEMI) {
if (!report(ParseExtraWarning, false, null(), JSMSG_EMPTY_CONSEQUENT))
return null();
}
- Node thenBranch = statement(yieldHandling);
+ Node thenBranch = consequentOrAlternative(yieldHandling);
if (!thenBranch)
return null();
if (!condList.append(cond) || !thenList.append(thenBranch) || !posList.append(begin))
return null();
bool matched;
if (!tokenStream.matchToken(&matched, TOK_ELSE, TokenStream::Operand))
return null();
if (matched) {
if (!tokenStream.matchToken(&matched, TOK_IF, TokenStream::Operand))
return null();
if (matched)
continue;
- elseBranch = statement(yieldHandling);
+ elseBranch = consequentOrAlternative(yieldHandling);
if (!elseBranch)
return null();
} else {
elseBranch = null();
}
break;
}
@@ -5057,23 +5072,34 @@ Parser<ParseHandler>::forHeadStart(Yield
// parse as an identifier. (|let| in for-of is always a declaration.)
// Thus we must can't just sniff out TOK_CONST/TOK_LET here. :-(
bool parsingLexicalDeclaration = false;
bool letIsIdentifier = false;
if (tt == TOK_LET || tt == TOK_CONST) {
parsingLexicalDeclaration = true;
tokenStream.consumeKnownToken(tt, TokenStream::Operand);
} else if (tt == TOK_NAME && tokenStream.nextName() == context->names().let) {
- // Check for the backwards-compatibility corner case in sloppy
- // mode like |for (let in e)| where the 'let' token should be
- // parsed as an identifier.
- if (!peekShouldParseLetDeclaration(&parsingLexicalDeclaration, TokenStream::Operand))
+ MOZ_ASSERT(!pc->sc()->strict(),
+ "should parse |let| as TOK_LET in strict mode code");
+
+ // We could have a {For,Lexical}Declaration, or we could have a
+ // LeftHandSideExpression with lookahead restrictions so it's not
+ // ambiguous with the former. Check for a continuation of the former
+ // to decide which we have.
+ tokenStream.consumeKnownToken(TOK_NAME, TokenStream::Operand);
+
+ TokenKind next;
+ if (!tokenStream.peekToken(&next))
return false;
- letIsIdentifier = !parsingLexicalDeclaration;
+ parsingLexicalDeclaration = nextTokenContinuesLetDeclaration(next, yieldHandling);
+ if (!parsingLexicalDeclaration) {
+ tokenStream.ungetToken();
+ letIsIdentifier = true;
+ }
}
if (parsingLexicalDeclaration) {
forLoopLexicalScope.emplace(this);
if (!forLoopLexicalScope->init(pc))
return null();
// Push a temporary ForLoopLexicalHead Statement that allows for
@@ -5381,17 +5407,17 @@ Parser<ParseHandler>::switchStatement(Yi
if (tt == TOK_RC || tt == TOK_CASE || tt == TOK_DEFAULT)
break;
if (afterReturn) {
TokenPos pos(0, 0);
if (!tokenStream.peekTokenPos(&pos, TokenStream::Operand))
return null();
statementBegin = pos.begin;
}
- Node stmt = statement(yieldHandling);
+ Node stmt = statementListItem(yieldHandling);
if (!stmt)
return null();
if (!warnedAboutStatementsAfterReturn) {
if (afterReturn) {
if (!handler.isStatementPermittedAfterReturnStatement(stmt)) {
if (!reportWithOffset(ParseWarning, false, statementBegin,
JSMSG_STMT_AFTER_RETURN))
{
@@ -5771,16 +5797,51 @@ SyntaxParseHandler::Node
Parser<SyntaxParseHandler>::withStatement(YieldHandling yieldHandling)
{
JS_ALWAYS_FALSE(abortIfSyntaxParser());
return null();
}
template <typename ParseHandler>
typename ParseHandler::Node
+Parser<ParseHandler>::labeledItem(YieldHandling yieldHandling)
+{
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+
+ if (tt == TOK_FUNCTION) {
+ TokenKind next;
+ if (!tokenStream.peekToken(&next))
+ return null();
+
+ // GeneratorDeclaration is only matched by HoistableDeclaration in
+ // StatementListItem, so generators can't be inside labels.
+ if (next == TOK_MUL) {
+ report(ParseError, false, null(), JSMSG_GENERATOR_LABEL);
+ return null();
+ }
+
+ // Per 13.13.1 it's a syntax error if LabelledItem: FunctionDeclaration
+ // is ever matched. Per Annex B.3.2 that modifies this text, this
+ // applies only to strict mode code.
+ if (pc->sc()->strict()) {
+ report(ParseError, false, null(), JSMSG_FUNCTION_LABEL);
+ return null();
+ }
+
+ return functionStmt(yieldHandling, NameRequired);
+ }
+
+ tokenStream.ungetToken();
+ return statement(yieldHandling);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
Parser<ParseHandler>::labeledStatement(YieldHandling yieldHandling)
{
uint32_t begin = pos().begin;
RootedPropertyName label(context, tokenStream.currentName());
auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
return stmt->label() == label;
};
@@ -5789,17 +5850,17 @@ Parser<ParseHandler>::labeledStatement(Y
report(ParseError, false, null(), JSMSG_DUPLICATE_LABEL);
return null();
}
tokenStream.consumeKnownToken(TOK_COLON);
/* Push a label struct and parse the statement. */
ParseContext::LabelStatement stmt(pc, label);
- Node pn = statement(yieldHandling);
+ Node pn = labeledItem(yieldHandling);
if (!pn)
return null();
return handler.newLabeledStatement(label, pn, begin);
}
template <typename ParseHandler>
typename ParseHandler::Node
@@ -5860,17 +5921,17 @@ Parser<ParseHandler>::tryStatement(Yield
{
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY);
ParseContext::Statement stmt(pc, StatementKind::Try);
ParseContext::Scope scope(this);
if (!scope.init(pc))
return null();
- innerBlock = statements(yieldHandling);
+ innerBlock = statementList(yieldHandling);
if (!innerBlock)
return null();
innerBlock = finishLexicalScope(scope, innerBlock);
if (!innerBlock)
return null();
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_TRY);
@@ -6000,17 +6061,17 @@ Parser<ParseHandler>::tryStatement(Yield
if (tt == TOK_FINALLY) {
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY);
ParseContext::Statement stmt(pc, StatementKind::Finally);
ParseContext::Scope scope(this);
if (!scope.init(pc))
return null();
- finallyBlock = statements(yieldHandling);
+ finallyBlock = statementList(yieldHandling);
if (!finallyBlock)
return null();
finallyBlock = finishLexicalScope(scope, finallyBlock);
if (!finallyBlock)
return null();
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_FINALLY);
@@ -6042,27 +6103,27 @@ Parser<ParseHandler>::catchBlockStatemen
if (!scope.init(pc))
return null();
// The catch parameter name cannot be redeclared inside the catch
// block, so declare the name in the inner scope.
if (!noteDeclaredName(simpleCatchParam, DeclarationKind::SimpleCatchParameter, pos()))
return null();
- Node list = statements(yieldHandling);
+ Node list = statementList(yieldHandling);
if (!list)
return null();
// The catch parameter name is not bound in this scope, so remove it
// before generating bindings.
scope.removeSimpleCatchParameter(pc, simpleCatchParam);
body = finishLexicalScope(scope, list);
} else {
- body = statements(yieldHandling);
+ body = statementList(yieldHandling);
}
if (!body)
return null();
MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_CATCH);
return body;
}
@@ -6356,127 +6417,104 @@ SyntaxParseHandler::Node
Parser<SyntaxParseHandler>::classDefinition(YieldHandling yieldHandling,
ClassContext classContext,
DefaultHandling defaultHandling)
{
MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
return SyntaxParseHandler::NodeFailure;
}
-template <typename ParseHandler>
+template <class ParseHandler>
bool
-Parser<ParseHandler>::shouldParseLetDeclaration(bool* parseDeclOut)
-{
- TokenKind tt;
- if (!tokenStream.peekToken(&tt))
- return false;
-
- if (tt == TOK_NAME) {
- // |let| followed by a name is a lexical declaration. This is so even
- // if the name is on a new line. ASI applies *only* if an offending
- // token not allowed by the grammar is encountered, and there's no
- // [no LineTerminator here] restriction in LexicalDeclaration or
- // ForDeclaration forbidding a line break.
+Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME),
+ "TOK_LET should have been summarily considered a "
+ "LexicalDeclaration");
+ MOZ_ASSERT(tokenStream.currentName() == context->names().let);
+
+#ifdef DEBUG
+ TokenKind verify;
+ MOZ_ALWAYS_TRUE(tokenStream.peekToken(&verify));
+ MOZ_ASSERT(next == verify);
+#endif
+
+ // Destructuring is (for once) the easy case.
+ if (next == TOK_LB || next == TOK_LC)
+ return true;
+
+ // Otherwise a let declaration must have a name.
+ if (next == TOK_NAME) {
+ // One non-"yield" TOK_NAME edge case deserves special comment.
+ // Consider this:
//
- // It's a tricky point, but this is true *even if* the name is "let", a
- // name that can't be bound by LexicalDeclaration or ForDeclaration.
- // Per ES6 5.3, static semantics early errors are validated *after*
- // determining productions matching the source text. So in this
- // example:
- //
- // let // ASI opportunity...except not
+ // let // not an ASI opportunity
// let;
//
- // the text matches LexicalDeclaration. *Then* static semantics in
- // ES6 13.3.1.1 (corresponding to the LexicalDeclaration production
- // just chosen), per ES6 5.3, are validated to recognize the Script as
- // invalid. It can't be evaluated, so a SyntaxError is thrown.
- *parseDeclOut = true;
- } else if (tt == TOK_LB || tt == TOK_LC) {
- *parseDeclOut = true;
- } else {
- // Whatever we have isn't a declaration. Either it's an expression, or
- // it's invalid: expression-parsing code will decide.
- *parseDeclOut = false;
- }
-
- return true;
-}
-
-template <typename ParseHandler>
-bool
-Parser<ParseHandler>::peekShouldParseLetDeclaration(bool* parseDeclOut,
- TokenStream::Modifier modifier)
-{
- // 'let' is a reserved keyword in strict mode and we shouldn't get here.
- MOZ_ASSERT(!pc->sc()->strict());
-
- *parseDeclOut = false;
-
-#ifdef DEBUG
- TokenKind tt;
- if (!tokenStream.peekToken(&tt, modifier))
+ // Static semantics in §13.3.1.1 turn a LexicalDeclaration that binds
+ // "let" into an early error. Does this retroactively permit ASI so
+ // that we should parse this as two ExpressionStatements? No. ASI
+ // resolves during parsing. Static semantics only apply to the full
+ // parse tree with ASI applied. No backsies!
+ if (tokenStream.nextName() != context->names().yield)
+ return true;
+ } else if (next != TOK_YIELD) {
return false;
- MOZ_ASSERT(tt == TOK_NAME && tokenStream.nextName() == context->names().let);
-#endif
-
- tokenStream.consumeKnownToken(TOK_NAME, modifier);
- if (!shouldParseLetDeclaration(parseDeclOut))
- return false;
-
- // Unget the TOK_NAME of 'let' if not parsing a declaration.
- if (!*parseDeclOut)
- tokenStream.ungetToken();
-
- return true;
+ }
+
+ // We have the name "yield": the grammar parameter exactly states whether
+ // this is okay. Even if YieldIsKeyword, the code might be valid if ASI
+ // induces a preceding semicolon. If YieldIsName, the code is valid
+ // outside strict mode, and declaration-parsing code will enforce strict
+ // mode restrictions.
+ //
+ // No checkYieldNameValidity for TOK_YIELD is needed here. It'll happen
+ // when TOK_YIELD is consumed as BindingIdentifier or as start of a fresh
+ // Statement.
+ return yieldHandling == YieldIsName;
}
template <typename ParseHandler>
typename ParseHandler::Node
-Parser<ParseHandler>::statement(YieldHandling yieldHandling, bool canHaveDirectives)
+Parser<ParseHandler>::variableStatement(YieldHandling yieldHandling)
+{
+ Node vars = declarationList(yieldHandling, PNK_VAR);
+ if (!vars)
+ return null();
+ if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
+ return null();
+ return vars;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::statement(YieldHandling yieldHandling)
{
MOZ_ASSERT(checkOptionsCalled);
JS_CHECK_RECURSION(context, return null());
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
switch (tt) {
// BlockStatement[?Yield, ?Return]
case TOK_LC:
return blockStatement(yieldHandling);
// VariableStatement[?Yield]
- case TOK_VAR: {
- Node pn = declarationList(yieldHandling, PNK_VAR);
- if (!pn)
- return null();
- if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
- return null();
- return pn;
- }
+ case TOK_VAR:
+ return variableStatement(yieldHandling);
// EmptyStatement
case TOK_SEMI:
return handler.newEmptyStatement(pos());
// ExpressionStatement[?Yield].
- //
- // These should probably be handled by a single ExpressionStatement
- // function in a default, not split up this way.
- case TOK_STRING:
- if (!canHaveDirectives && tokenStream.currentToken().atom() == context->names().useAsm) {
- if (!abortIfSyntaxParser())
- return null();
- if (!report(ParseWarning, false, null(), JSMSG_USE_ASM_DIRECTIVE_FAIL))
- return null();
- }
- return expressionStatement(yieldHandling);
case TOK_YIELD: {
// Don't use a ternary operator here due to obscure linker issues
// around using static consts in the arms of a ternary.
TokenStream::Modifier modifier;
if (yieldExpressionsSupported())
modifier = TokenStream::Operand;
else
@@ -6489,39 +6527,68 @@ Parser<ParseHandler>::statement(YieldHan
if (!checkYieldNameValidity())
return null();
return labeledStatement(yieldHandling);
}
return expressionStatement(yieldHandling);
}
case TOK_NAME: {
- // 'let' is a contextual keyword outside strict mode. In strict mode
- // it's always tokenized as TOK_LET except in this one weird case:
- //
- // "use strict" // ExpressionStatement, terminated by ASI
- // let a = 1; // LexicalDeclaration
- //
- // We can't apply strict mode until we know "use strict" is the entire
- // statement, but we can't know "use strict" is the entire statement
- // until we see the next token. So 'let' is still TOK_NAME here.
- if (tokenStream.currentName() == context->names().let) {
- bool parseDecl;
- if (!shouldParseLetDeclaration(&parseDecl))
- return null();
-
- if (parseDecl)
- return lexicalDeclaration(yieldHandling, /* isConst = */ false);
- }
-
TokenKind next;
if (!tokenStream.peekToken(&next))
return null();
+
+#ifdef DEBUG
+ if (tokenStream.currentName() == context->names().let) {
+ MOZ_ASSERT(!pc->sc()->strict(),
+ "observing |let| as TOK_NAME and not TOK_LET implies "
+ "non-strict code (and the edge case of 'use strict' "
+ "immediately followed by |let| on a new line only "
+ "applies to StatementListItems, not to Statements)");
+ }
+#endif
+
+ // Statement context forbids LexicalDeclaration.
+ if ((next == TOK_LB || next == TOK_LC || next == TOK_NAME) &&
+ tokenStream.currentName() == context->names().let)
+ {
+ bool forbiddenLetDeclaration = false;
+ if (next == TOK_LB) {
+ // ExpressionStatement has a 'let [' lookahead restriction.
+ forbiddenLetDeclaration = true;
+ } else {
+ // 'let {' and 'let foo' aren't completely forbidden, if ASI
+ // causes 'let' to be the entire Statement. But if they're
+ // same-line, we can aggressively give a better error message.
+ //
+ // Note that this ignores 'yield' as TOK_YIELD: we'll handle it
+ // correctly but with a worse error message.
+ TokenKind nextSameLine;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+
+ MOZ_ASSERT(nextSameLine == TOK_NAME ||
+ nextSameLine == TOK_LC ||
+ nextSameLine == TOK_EOL);
+
+ forbiddenLetDeclaration = nextSameLine != TOK_EOL;
+ }
+
+ if (forbiddenLetDeclaration) {
+ report(ParseError, false, null(), JSMSG_FORBIDDEN_AS_STATEMENT,
+ "lexical declarations");
+ return null();
+ }
+ }
+
+ // NOTE: It's unfortunately allowed to have a label named 'let' in
+ // non-strict code. 💯
if (next == TOK_COLON)
return labeledStatement(yieldHandling);
+
return expressionStatement(yieldHandling);
}
case TOK_NEW:
return expressionStatement(yieldHandling, PredictInvoked);
default:
return expressionStatement(yieldHandling);
@@ -6580,33 +6647,232 @@ Parser<ParseHandler>::statement(YieldHan
// TryStatement[?Yield, ?Return]
case TOK_TRY:
return tryStatement(yieldHandling);
// DebuggerStatement
case TOK_DEBUGGER:
return debuggerStatement();
- // HoistableDeclaration[?Yield]
+ // |function| is forbidden by lookahead restriction (unless as child
+ // statement of |if| or |else|, but Parser::consequentOrAlternative
+ // handles that).
+ case TOK_FUNCTION:
+ report(ParseError, false, null(), JSMSG_FORBIDDEN_AS_STATEMENT, "function declarations");
+ return null();
+
+ // |class| is also forbidden by lookahead restriction.
+ case TOK_CLASS:
+ report(ParseError, false, null(), JSMSG_FORBIDDEN_AS_STATEMENT, "classes");
+ return null();
+
+ // ImportDeclaration (only inside modules)
+ case TOK_IMPORT:
+ return importDeclaration();
+
+ // ExportDeclaration (only inside modules)
+ case TOK_EXPORT:
+ return exportDeclaration();
+
+ // Miscellaneous error cases arguably better caught here than elsewhere.
+
+ case TOK_CATCH:
+ report(ParseError, false, null(), JSMSG_CATCH_WITHOUT_TRY);
+ return null();
+
+ case TOK_FINALLY:
+ report(ParseError, false, null(), JSMSG_FINALLY_WITHOUT_TRY);
+ return null();
+
+ // TOK_LET implies we're in strict mode code where static semantics
+ // forbid IdentifierName to be "let": a stronger restriction than
+ // Statement's lookahead restriction on |let [|. Provide a better error
+ // message here than the default case would.
+ case TOK_LET:
+ report(ParseError, false, null(), JSMSG_FORBIDDEN_AS_STATEMENT, "let declarations");
+ return null();
+
+ // NOTE: default case handled in the ExpressionStatement section.
+ }
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling,
+ bool canHaveDirectives /* = false */)
+{
+ MOZ_ASSERT(checkOptionsCalled);
+
+ JS_CHECK_RECURSION(context, return null());
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+
+ switch (tt) {
+ // BlockStatement[?Yield, ?Return]
+ case TOK_LC:
+ return blockStatement(yieldHandling);
+
+ // VariableStatement[?Yield]
+ case TOK_VAR:
+ return variableStatement(yieldHandling);
+
+ // EmptyStatement
+ case TOK_SEMI:
+ return handler.newEmptyStatement(pos());
+
+ // ExpressionStatement[?Yield].
+ //
+ // These should probably be handled by a single ExpressionStatement
+ // function in a default, not split up this way.
+ case TOK_STRING:
+ if (!canHaveDirectives && tokenStream.currentToken().atom() == context->names().useAsm) {
+ if (!abortIfSyntaxParser())
+ return null();
+ if (!report(ParseWarning, false, null(), JSMSG_USE_ASM_DIRECTIVE_FAIL))
+ return null();
+ }
+ return expressionStatement(yieldHandling);
+
+ case TOK_YIELD: {
+ // Don't use a ternary operator here due to obscure linker issues
+ // around using static consts in the arms of a ternary.
+ TokenStream::Modifier modifier;
+ if (yieldExpressionsSupported())
+ modifier = TokenStream::Operand;
+ else
+ modifier = TokenStream::None;
+
+ TokenKind next;
+ if (!tokenStream.peekToken(&next, modifier))
+ return null();
+ if (next == TOK_COLON) {
+ if (!checkYieldNameValidity())
+ return null();
+ return labeledStatement(yieldHandling);
+ }
+ return expressionStatement(yieldHandling);
+ }
+
+ case TOK_NAME: {
+ TokenKind next;
+ if (!tokenStream.peekToken(&next))
+ return null();
+
+ if (tokenStream.currentName() == context->names().let) {
+ if (nextTokenContinuesLetDeclaration(next, yieldHandling))
+ return lexicalDeclaration(yieldHandling, /* isConst = */ false);
+
+ // IdentifierName can't be "let" in strict mode code. |let| in
+ // strict mode code is usually TOK_LET, but in this one weird case
+ // in global code it's TOK_NAME:
+ //
+ // "use strict" // ExpressionStatement ended by ASI
+ // let <...whatever else...> // a fresh StatementListItem
+ //
+ // Carefully reject strict mode |let| non-declarations.
+ if (pc->sc()->strict()) {
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "declaration pattern", TokenKindToDesc(next));
+ return null();
+ }
+ }
+
+ if (next == TOK_COLON)
+ return labeledStatement(yieldHandling);
+
+ return expressionStatement(yieldHandling);
+ }
+
+ case TOK_NEW:
+ return expressionStatement(yieldHandling, PredictInvoked);
+
+ default:
+ return expressionStatement(yieldHandling);
+
+ // IfStatement[?Yield, ?Return]
+ case TOK_IF:
+ return ifStatement(yieldHandling);
+
+ // BreakableStatement[?Yield, ?Return]
+ //
+ // BreakableStatement[Yield, Return]:
+ // IterationStatement[?Yield, ?Return]
+ // SwitchStatement[?Yield, ?Return]
+ case TOK_DO:
+ return doWhileStatement(yieldHandling);
+
+ case TOK_WHILE:
+ return whileStatement(yieldHandling);
+
+ case TOK_FOR:
+ return forStatement(yieldHandling);
+
+ case TOK_SWITCH:
+ return switchStatement(yieldHandling);
+
+ // ContinueStatement[?Yield]
+ case TOK_CONTINUE:
+ return continueStatement(yieldHandling);
+
+ // BreakStatement[?Yield]
+ case TOK_BREAK:
+ return breakStatement(yieldHandling);
+
+ // [+Return] ReturnStatement[?Yield]
+ case TOK_RETURN:
+ // The Return parameter is only used here, and the effect is easily
+ // detected this way, so don't bother passing around an extra parameter
+ // everywhere.
+ if (!pc->isFunctionBox()) {
+ report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_return_str);
+ return null();
+ }
+ return returnStatement(yieldHandling);
+
+ // WithStatement[?Yield, ?Return]
+ case TOK_WITH:
+ return withStatement(yieldHandling);
+
+ // LabelledStatement[?Yield, ?Return]
+ // This is really handled by TOK_NAME and TOK_YIELD cases above.
+
+ // ThrowStatement[?Yield]
+ case TOK_THROW:
+ return throwStatement(yieldHandling);
+
+ // TryStatement[?Yield, ?Return]
+ case TOK_TRY:
+ return tryStatement(yieldHandling);
+
+ // DebuggerStatement
+ case TOK_DEBUGGER:
+ return debuggerStatement();
+
+ // Declaration[Yield]:
+
+ // HoistableDeclaration[?Yield, ~Default]
case TOK_FUNCTION:
return functionStmt(yieldHandling, NameRequired);
- // ClassDeclaration[?Yield]
+ // ClassDeclaration[?Yield, ~Default]
case TOK_CLASS:
if (!abortIfSyntaxParser())
return null();
return classDefinition(yieldHandling, ClassStatement, NameRequired);
- // LexicalDeclaration[In, ?Yield]
+ // LexicalDeclaration[In, ?Yield]
+ // LetOrConst BindingList[?In, ?Yield]
case TOK_LET:
case TOK_CONST:
if (!abortIfSyntaxParser())
return null();
- // [In] is the default behavior, because for-loops currently specially
- // parse their heads to handle |in| in this situation.
+ // [In] is the default behavior, because for-loops specially parse
+ // their heads to handle |in| in this situation.
return lexicalDeclaration(yieldHandling, /* isConst = */ tt == TOK_CONST);
// ImportDeclaration (only inside modules)
case TOK_IMPORT:
return importDeclaration();
// ExportDeclaration (only inside modules)
case TOK_EXPORT:
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -933,17 +933,18 @@ class Parser final : private JS::AutoGCR
inline Node newName(PropertyName* name);
inline Node newName(PropertyName* name, TokenPos pos);
inline Node newYieldExpression(uint32_t begin, Node expr, bool isYieldStar = false);
inline bool abortIfSyntaxParser();
public:
/* Public entry points for parsing. */
- Node statement(YieldHandling yieldHandling, bool canHaveDirectives = false);
+ Node statement(YieldHandling yieldHandling);
+ Node statementListItem(YieldHandling yieldHandling, bool canHaveDirectives = false);
bool maybeParseDirective(Node list, Node pn, bool* cont);
// Parse the body of an eval.
//
// Eval scripts are distinguished from global scripts in that in ES6, per
// 18.2.1.1 steps 9 and 10, all eval scripts are executed under a fresh
// lexical scope.
@@ -1012,21 +1013,21 @@ class Parser final : private JS::AutoGCR
* number of the parsers this is convenient and avoids a lot of
* unnecessary ungetting and regetting of tokens.
*
* Some parsers have two versions: an always-inlined version (with an 'i'
* suffix) and a never-inlined version (with an 'n' suffix).
*/
Node functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling);
Node functionExpr(InvokedPrediction invoked = PredictUninvoked);
- Node statements(YieldHandling yieldHandling);
+
+ Node statementList(YieldHandling yieldHandling);
Node blockStatement(YieldHandling yieldHandling,
unsigned errorNumber = JSMSG_CURLY_IN_COMPOUND);
- Node ifStatement(YieldHandling yieldHandling);
Node doWhileStatement(YieldHandling yieldHandling);
Node whileStatement(YieldHandling yieldHandling);
Node forStatement(YieldHandling yieldHandling);
bool forHeadStart(YieldHandling yieldHandling,
ParseNodeKind* forHeadKind,
Node* forInitialPart,
mozilla::Maybe<ParseContext::Scope>& forLetImpliedScope,
@@ -1034,23 +1035,36 @@ class Parser final : private JS::AutoGCR
bool validateForInOrOfLHSExpression(Node target);
Node expressionAfterForInOrOf(ParseNodeKind forHeadKind, YieldHandling yieldHandling);
Node switchStatement(YieldHandling yieldHandling);
Node continueStatement(YieldHandling yieldHandling);
Node breakStatement(YieldHandling yieldHandling);
Node returnStatement(YieldHandling yieldHandling);
Node withStatement(YieldHandling yieldHandling);
- Node labeledStatement(YieldHandling yieldHandling);
Node throwStatement(YieldHandling yieldHandling);
Node tryStatement(YieldHandling yieldHandling);
Node catchBlockStatement(YieldHandling yieldHandling, HandlePropertyName simpleCatchParam);
Node debuggerStatement();
+ Node variableStatement(YieldHandling yieldHandling);
+
+ Node labeledStatement(YieldHandling yieldHandling);
+ Node labeledItem(YieldHandling yieldHandling);
+
+ Node ifStatement(YieldHandling yieldHandling);
+ Node consequentOrAlternative(YieldHandling yieldHandling);
+
+ // While on a |let| TOK_NAME token, examine |next|. Indicate whether
+ // |next|, the next token already gotten with modifier TokenStream::None,
+ // continues a LexicalDeclaration.
+ bool nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling);
+
Node lexicalDeclaration(YieldHandling yieldHandling, bool isConst);
+
Node importDeclaration();
Node exportDeclaration();
Node expressionStatement(YieldHandling yieldHandling,
InvokedPrediction invoked = PredictUninvoked);
// Declaration parsing. The main entrypoint is Parser::declarationList,
// with sub-functionality split out into the remaining methods.
@@ -1234,25 +1248,16 @@ class Parser final : private JS::AutoGCR
bool trySyntaxParseInnerFunction(Node pn, HandleFunction fun, InHandling inHandling,
FunctionSyntaxKind kind, GeneratorKind generatorKind,
bool tryAnnexB, Directives inheritedDirectives,
Directives* newDirectives);
bool finishFunctionScopes();
bool finishFunction();
bool leaveInnerFunction(ParseContext* outerpc);
- // Use when the current token is TOK_NAME and is known to be 'let'.
- bool shouldParseLetDeclaration(bool* parseDeclOut);
-
- // Use when the lookahead token is TOK_NAME and is known to be 'let'. If a
- // let declaration should be parsed, the TOK_NAME token of 'let' is
- // consumed. Otherwise, the current token remains the TOK_NAME token of
- // 'let'.
- bool peekShouldParseLetDeclaration(bool* parseDeclOut, TokenStream::Modifier modifier);
-
public:
enum FunctionCallBehavior {
PermitAssignmentToFunctionCalls,
ForbidAssignmentToFunctionCalls
};
bool isValidSimpleAssignmentTarget(Node node,
FunctionCallBehavior behavior = ForbidAssignmentToFunctionCalls);
--- a/js/src/jit-test/tests/ion/dce-with-rinstructions.js
+++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js
@@ -1,12 +1,14 @@
setJitCompilerOption("baseline.warmup.trigger", 10);
setJitCompilerOption("ion.warmup.trigger", 20);
var i;
+var config = getBuildConfiguration();
+
// Check that we are able to remove the operation inside recover test functions (denoted by "rop..."),
// when we inline the first version of uceFault, and ensure that the bailout is correct
// when uceFault is replaced (which cause an invalidation bailout)
var uceFault = function (i) {
if (i > 98)
uceFault = function (i) { return true; };
return false;
@@ -1281,16 +1283,39 @@ function rhypot_object_4args(i) {
t2 = 3000;
t3 = 4000;
if (uceFault_hypot_object_4args(i) || uceFault_hypot_object_4args(i) )
assertEq(x, Math.sqrt(i * i + (i + 1) * (i + 1) + (i + 2) * (i + 2) + (i + 3) * (i + 3)));
assertRecoveredOnBailout(x, false);
return i;
}
+var uceFault_random = eval(uneval(uceFault).replace('uceFault', 'uceFault_random'));
+function rrandom(i) {
+ // setRNGState() exists only in debug builds
+
+ if(config.debug) {
+ setRNGState(2, 0);
+ var x = Math.random();
+ if (uceFault_random(i) || uceFault_random(i)) {
+ setRNGState(2, 0);
+ assertEq(x, Math.random());
+ }
+ assertRecoveredOnBailout(x, true);
+ } else {
+ var x = Math.random();
+ if (uceFault_random(i) || uceFault_random(i)) {
+ Math.random();
+ }
+ assertRecoveredOnBailout(x, true);
+ }
+
+ return i;
+}
+
var uceFault_sin_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_sin_number'));
function rsin_number(i) {
var x = Math.sin(i);
if (uceFault_sin_number(i) || uceFault_sin_number(i))
assertEq(x, Math.sin(i));
assertRecoveredOnBailout(x, true);
return i;
}
@@ -1439,16 +1464,17 @@ for (i = 0; i < 100; i++) {
rtrunc_to_int32_object(i);
rtrunc_to_int32_string(i);
rhypot_number_2args(i);
rhypot_number_3args(i);
rhypot_number_4args(i);
rhypot_object_2args(i);
rhypot_object_3args(i);
rhypot_object_4args(i);
+ rrandom(i);
rsin_number(i);
rsin_object(i);
rlog_number(i);
rlog_object(i);
}
// Test that we can refer multiple time to the same recover instruction, as well
// as chaining recover instructions.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/typedarray/sort.js
@@ -0,0 +1,24 @@
+setJitCompilerOption("ion.warmup.trigger", 40);
+
+const constructors = [
+ Int8Array,
+ Uint8Array,
+ Uint8ClampedArray,
+ Int16Array,
+ Uint16Array,
+ Int32Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array ];
+
+// Ensure that when creating TypedArrays under JIT
+// the sort() method works as expected (bug 1295034).
+for (var ctor of constructors) {
+ for (var _ of Array(1024)) {
+ var testArray = new ctor(10);
+ testArray.sort();
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -11515,20 +11515,20 @@ CodeGenerator::visitAsmJSInterruptCheck(
MOZ_ASSERT((sizeof(AsmJSFrame) + masm.framePushed()) % ABIStackAlignment == 0);
masm.call(wasm::SymbolicAddress::HandleExecutionInterrupt);
masm.branchIfFalseBool(ReturnReg, wasm::JumpTarget::Throw);
masm.bind(&rejoin);
}
void
-CodeGenerator::visitAsmThrowUnreachable(LAsmThrowUnreachable* lir)
+CodeGenerator::visitWasmTrap(LWasmTrap* lir)
{
MOZ_ASSERT(gen->compilingAsmJS());
- masm.jump(wasm::JumpTarget::Unreachable);
+ masm.jump(wasm::JumpTarget(lir->mir()->trap()));
}
typedef bool (*RecompileFn)(JSContext*);
static const VMFunction RecompileFnInfo = FunctionInfo<RecompileFn>(Recompile, "Recompile");
typedef bool (*ForcedRecompileFn)(JSContext*);
static const VMFunction ForcedRecompileFnInfo =
FunctionInfo<ForcedRecompileFn>(ForcedRecompile, "ForcedRecompile");
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -410,17 +410,17 @@ class CodeGenerator final : public CodeG
void visitAssertResultV(LAssertResultV* ins);
void visitAssertResultT(LAssertResultT* ins);
void emitAssertResultV(const ValueOperand output, const TemporaryTypeSet* typeset);
void emitAssertObjectOrStringResult(Register input, MIRType type, const TemporaryTypeSet* typeset);
void visitInterruptCheck(LInterruptCheck* lir);
void visitOutOfLineInterruptCheckImplicit(OutOfLineInterruptCheckImplicit* ins);
void visitAsmJSInterruptCheck(LAsmJSInterruptCheck* lir);
- void visitAsmThrowUnreachable(LAsmThrowUnreachable* lir);
+ void visitWasmTrap(LWasmTrap* lir);
void visitRecompileCheck(LRecompileCheck* ins);
void visitRotate(LRotate* ins);
void visitRandom(LRandom* ins);
void visitSignExtend(LSignExtend* ins);
#ifdef DEBUG
void emitDebugForceBailing(LInstruction* lir);
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2525,19 +2525,19 @@ LIRGenerator::visitInterruptCheck(MInter
void
LIRGenerator::visitAsmJSInterruptCheck(MAsmJSInterruptCheck* ins)
{
gen->setPerformsCall();
add(new(alloc()) LAsmJSInterruptCheck, ins);
}
void
-LIRGenerator::visitAsmThrowUnreachable(MAsmThrowUnreachable* ins)
-{
- add(new(alloc()) LAsmThrowUnreachable, ins);
+LIRGenerator::visitWasmTrap(MWasmTrap* ins)
+{
+ add(new(alloc()) LWasmTrap, ins);
}
void
LIRGenerator::visitAsmReinterpret(MAsmReinterpret* ins)
{
if (ins->type() == MIRType::Int64)
defineInt64(new(alloc()) LAsmReinterpretToI64(useRegisterAtStart(ins->input())), ins);
else if (ins->input()->type() == MIRType::Int64)
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -186,17 +186,17 @@ class LIRGenerator : public LIRGenerator
void visitConvertElementsToDoubles(MConvertElementsToDoubles* ins);
void visitMaybeToDoubleElement(MMaybeToDoubleElement* ins);
void visitMaybeCopyElementsForWrite(MMaybeCopyElementsForWrite* ins);
void visitLoadSlot(MLoadSlot* ins);
void visitLoadFixedSlotAndUnbox(MLoadFixedSlotAndUnbox* ins);
void visitFunctionEnvironment(MFunctionEnvironment* ins);
void visitInterruptCheck(MInterruptCheck* ins);
void visitAsmJSInterruptCheck(MAsmJSInterruptCheck* ins);
- void visitAsmThrowUnreachable(MAsmThrowUnreachable* ins);
+ void visitWasmTrap(MWasmTrap* ins);
void visitAsmReinterpret(MAsmReinterpret* ins);
void visitStoreSlot(MStoreSlot* ins);
void visitFilterTypeSet(MFilterTypeSet* ins);
void visitTypeBarrier(MTypeBarrier* ins);
void visitMonitorTypes(MMonitorTypes* ins);
void visitPostWriteBarrier(MPostWriteBarrier* ins);
void visitPostWriteElementBarrier(MPostWriteElementBarrier* ins);
void visitArrayLength(MArrayLength* ins);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -6579,16 +6579,26 @@ class MRandom : public MNullaryInstructi
}
bool possiblyCalls() const override {
return true;
}
void computeRange(TempAllocator& alloc) override;
+ MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
+
+ bool canRecoverOnBailout() const override {
+#ifdef JS_MORE_DETERMINISTIC
+ return false;
+#else
+ return true;
+#endif
+ }
+
ALLOW_CLONE(MRandom)
};
class MMathFunction
: public MUnaryInstruction,
public FloatingPointPolicy<0>::Data
{
public:
@@ -7733,28 +7743,38 @@ class MInterruptCheck : public MNullaryI
class MAsmJSInterruptCheck
: public MNullaryInstruction
{
public:
INSTRUCTION_HEADER(AsmJSInterruptCheck)
TRIVIAL_NEW_WRAPPERS
};
-// Directly jumps to the unreachable trap handler.
-class MAsmThrowUnreachable
+// Directly jumps to the indicated trap, leaving Wasm code and reporting a
+// runtime error.
+
+class MWasmTrap
: public MAryControlInstruction<0, 0>,
public NoTypePolicy::Data
{
- public:
- INSTRUCTION_HEADER(AsmThrowUnreachable)
- TRIVIAL_NEW_WRAPPERS
-
- AliasSet getAliasSet() const override {
- return AliasSet::None();
- }
+ wasm::Trap trap_;
+
+ explicit MWasmTrap(wasm::Trap trap)
+ : trap_(trap)
+ {}
+
+ public:
+ INSTRUCTION_HEADER(WasmTrap)
+ TRIVIAL_NEW_WRAPPERS
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::None();
+ }
+
+ wasm::Trap trap() const { return trap_; }
};
// Checks if a value is JS_UNINITIALIZED_LEXICAL, bailout out if so, leaving
// it to baseline to throw at the correct pc.
class MLexicalCheck
: public MUnaryInstruction,
public BoxPolicy<0>::Data
{
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -257,28 +257,28 @@ namespace jit {
_(Floor) \
_(Ceil) \
_(Round) \
_(In) \
_(InstanceOf) \
_(CallInstanceOf) \
_(InterruptCheck) \
_(AsmJSInterruptCheck) \
- _(AsmThrowUnreachable) \
_(GetDOMProperty) \
_(GetDOMMember) \
_(SetDOMProperty) \
_(IsConstructor) \
_(IsCallable) \
_(IsObject) \
_(HasClass) \
_(CopySign) \
_(WasmBoundsCheck) \
_(WasmLoad) \
_(WasmStore) \
+ _(WasmTrap) \
_(WasmTruncateToInt32) \
_(AsmJSNeg) \
_(AsmJSUnsignedToDouble) \
_(AsmJSUnsignedToFloat32) \
_(AsmJSLoadHeap) \
_(AsmJSStoreHeap) \
_(WasmLoadGlobalVar) \
_(WasmStoreGlobalVar) \
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -1001,16 +1001,34 @@ RMathFunction::recover(JSContext* cx, Sn
return true;
}
default:
MOZ_CRASH("Unknown math function.");
}
}
bool
+MRandom::writeRecoverData(CompactBufferWriter& writer) const
+{
+ MOZ_ASSERT(this->canRecoverOnBailout());
+ writer.writeUnsigned(uint32_t(RInstruction::Recover_Random));
+ return true;
+}
+
+RRandom::RRandom(CompactBufferReader& reader)
+{}
+
+bool
+RRandom::recover(JSContext* cx, SnapshotIterator& iter) const
+{
+ iter.storeInstructionResult(DoubleValue(math_random_impl(cx)));
+ return true;
+}
+
+bool
MStringSplit::writeRecoverData(CompactBufferWriter& writer) const
{
MOZ_ASSERT(canRecoverOnBailout());
writer.writeUnsigned(uint32_t(RInstruction::Recover_StringSplit));
return true;
}
RStringSplit::RStringSplit(CompactBufferReader& reader)
--- a/js/src/jit/Recover.h
+++ b/js/src/jit/Recover.h
@@ -83,16 +83,17 @@ namespace jit {
_(Pow) \
_(PowHalf) \
_(MinMax) \
_(Abs) \
_(Sqrt) \
_(Atan2) \
_(Hypot) \
_(MathFunction) \
+ _(Random) \
_(StringSplit) \
_(RegExpMatcher) \
_(RegExpSearcher) \
_(RegExpTester) \
_(StringReplace) \
_(TypeOf) \
_(ToDouble) \
_(ToFloat32) \
@@ -457,16 +458,23 @@ class RMathFunction final : public RInst
uint8_t function_;
public:
RINSTRUCTION_HEADER_NUM_OP_(MathFunction, 1)
MOZ_MUST_USE bool recover(JSContext* cx, SnapshotIterator& iter) const;
};
+class RRandom final : public RInstruction
+{
+ RINSTRUCTION_HEADER_NUM_OP_(Random, 0)
+ public:
+ MOZ_MUST_USE bool recover(JSContext* cx, SnapshotIterator& iter) const;
+};
+
class RStringSplit final : public RInstruction
{
public:
RINSTRUCTION_HEADER_NUM_OP_(StringSplit, 3)
MOZ_MUST_USE bool recover(JSContext* cx, SnapshotIterator& iter) const;
};
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -2286,21 +2286,17 @@ CodeGeneratorARM::visitAsmJSLoadHeap(LAs
memoryBarrier(mir->barrierAfter());
}
void
CodeGeneratorARM::visitWasmBoundsCheck(LWasmBoundsCheck* ins)
{
MWasmBoundsCheck* mir = ins->mir();
- uint32_t offset = mir->offset();
- if (offset > INT32_MAX) {
- masm.as_b(wasm::JumpTarget::OutOfBounds);
- return;
- }
+ MOZ_ASSERT(mir->offset() <= INT32_MAX);
if (!mir->isRedundant()) {
// No guarantee that heapBase + endOffset can be properly encoded in
// the cmp immediate in ma_BoundsCheck, so use an explicit add instead.
uint32_t endOffset = mir->endOffset();
Register ptr = ToRegister(ins->ptr());
@@ -2343,21 +2339,17 @@ template <typename T>
void
CodeGeneratorARM::emitWasmLoad(T* lir)
{
const MWasmLoad* mir = lir->mir();
MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
uint32_t offset = mir->offset();
- if (offset > INT32_MAX) {
- // This is unreachable because of bounds checks.
- masm.breakpoint();
- return;
- }
+ MOZ_ASSERT(offset <= INT32_MAX);
Register ptr = ToRegister(lir->ptr());
Scalar::Type type = mir->accessType();
// Maybe add the offset.
if (offset || type == Scalar::Int64) {
Register ptrPlusOffset = ToRegister(lir->ptrCopy());
masm.ma_add(Imm32(offset), ptrPlusOffset);
@@ -2414,21 +2406,17 @@ template<typename T>
void
CodeGeneratorARM::emitWasmUnalignedLoad(T* lir)
{
const MWasmLoad* mir = lir->mir();
MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
uint32_t offset = mir->offset();
- if (offset > INT32_MAX) {
- // This is unreachable because of bounds checks.
- masm.breakpoint();
- return;
- }
+ MOZ_ASSERT(offset <= INT32_MAX);
Register ptr = ToRegister(lir->ptrCopy());
if (offset)
masm.ma_add(Imm32(offset), ptr);
// Add HeapReg to ptr, so we can use base+index addressing in the byte loads.
masm.ma_add(HeapReg, ptr);
@@ -2498,21 +2486,17 @@ template <typename T>
void
CodeGeneratorARM::emitWasmStore(T* lir)
{
const MWasmStore* mir = lir->mir();
MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
uint32_t offset = mir->offset();
- if (offset > INT32_MAX) {
- // This is unreachable because of bounds checks.
- masm.breakpoint();
- return;
- }
+ MOZ_ASSERT(offset <= INT32_MAX);
Register ptr = ToRegister(lir->ptr());
unsigned byteSize = mir->byteSize();
Scalar::Type type = mir->accessType();
// Maybe add the offset.
if (offset || type == Scalar::Int64) {
Register ptrPlusOffset = ToRegister(lir->ptrCopy());
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
@@ -1661,20 +1661,17 @@ CodeGeneratorMIPSShared::visitWasmCallI6
}
void
CodeGeneratorMIPSShared::visitWasmBoundsCheck(LWasmBoundsCheck* ins)
{
MWasmBoundsCheck* mir = ins->mir();
uint32_t offset = mir->offset();
- if (offset > INT32_MAX) {
- masm.jump(wasm::JumpTarget::OutOfBounds);
- return;
- }
+ MOZ_ASSERT(offset <= INT32_MAX);
uint32_t endOffset = mir->endOffset();
Register ptr = ToRegister(ins->ptr());
masm.move32(Imm32(endOffset), SecondScratchReg);
masm.addPtr(ptr, SecondScratchReg);
// Detect unsigned overflow.
@@ -1688,21 +1685,17 @@ CodeGeneratorMIPSShared::visitWasmBounds
void
CodeGeneratorMIPSShared::visitWasmLoad(LWasmLoad* lir)
{
const MWasmLoad* mir = lir->mir();
MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
uint32_t offset = mir->offset();
- if (offset > INT32_MAX) {
- // This is unreachable because of bounds checks.
- masm.breakpoint();
- return;
- }
+ MOZ_ASSERT(offset <= INT32_MAX);
Register ptr = ToRegister(lir->ptr());
// Maybe add the offset.
if (offset) {
Register ptrPlusOffset = ToRegister(lir->ptrCopy());
masm.addPtr(Imm32(offset), ptrPlusOffset);
ptr = ptrPlusOffset;
@@ -1740,21 +1733,17 @@ CodeGeneratorMIPSShared::visitWasmLoad(L
void
CodeGeneratorMIPSShared::visitWasmStore(LWasmStore* lir)
{
const MWasmStore* mir = lir->mir();
MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
uint32_t offset = mir->offset();
- if (offset > INT32_MAX) {
- // This is unreachable because of bounds checks.
- masm.breakpoint();
- return;
- }
+ MOZ_ASSERT(offset <= INT32_MAX);
Register ptr = ToRegister(lir->ptr());
// Maybe add the offset.
if (offset) {
Register ptrPlusOffset = ToRegister(lir->ptrCopy());
masm.addPtr(Imm32(offset), ptrPlusOffset);
ptr = ptrPlusOffset;
--- a/js/src/jit/mips32/MacroAssembler-mips32.cpp
+++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp
@@ -1062,36 +1062,24 @@ MacroAssemblerMIPSCompat::storePtr(Regis
{
movePtr(ImmPtr(dest.addr), ScratchRegister);
storePtr(src, Address(ScratchRegister, 0));
}
void
MacroAssemblerMIPSCompat::clampIntToUint8(Register reg)
{
- // look at (reg >> 8) if it is 0, then src shouldn't be clamped
- // if it is <0, then we want to clamp to 0,
- // otherwise, we wish to clamp to 255
- Label done;
- ma_move(ScratchRegister, reg);
- asMasm().rshiftPtrArithmetic(Imm32(8), ScratchRegister);
- ma_b(ScratchRegister, ScratchRegister, &done, Assembler::Zero, ShortJump);
- {
- Label negative;
- ma_b(ScratchRegister, ScratchRegister, &negative, Assembler::Signed, ShortJump);
- {
- ma_li(reg, Imm32(255));
- ma_b(&done, ShortJump);
- }
- bind(&negative);
- {
- ma_move(reg, zero);
- }
- }
- bind(&done);
+ // If reg is < 0, then we want to clamp to 0.
+ as_slti(ScratchRegister, reg, 0);
+ as_movn(reg, zero, ScratchRegister);
+
+ // If reg is >= 255, then we want to clamp to 255.
+ ma_li(SecondScratchReg, Imm32(255));
+ as_slti(ScratchRegister, reg, 255);
+ as_movz(reg, SecondScratchReg, ScratchRegister);
}
// Note: this function clobbers the input register.
void
MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output)
{
MOZ_ASSERT(input != ScratchDoubleReg);
Label positive, done;
--- a/js/src/jit/mips64/MacroAssembler-mips64.cpp
+++ b/js/src/jit/mips64/MacroAssembler-mips64.cpp
@@ -1209,36 +1209,24 @@ MacroAssemblerMIPS64Compat::storePtr(Reg
{
movePtr(ImmPtr(dest.addr), ScratchRegister);
storePtr(src, Address(ScratchRegister, 0));
}
void
MacroAssemblerMIPS64Compat::clampIntToUint8(Register reg)
{
- // look at (reg >> 8) if it is 0, then src shouldn't be clamped
- // if it is <0, then we want to clamp to 0,
- // otherwise, we wish to clamp to 255
- Label done;
- ma_move(ScratchRegister, reg);
- asMasm().rshiftPtrArithmetic(Imm32(8), ScratchRegister);
- ma_b(ScratchRegister, ScratchRegister, &done, Assembler::Zero, ShortJump);
- {
- Label negative;
- ma_b(ScratchRegister, ScratchRegister, &negative, Assembler::Signed, ShortJump);
- {
- ma_li(reg, Imm32(255));
- ma_b(&done, ShortJump);
- }
- bind(&negative);
- {
- ma_move(reg, zero);
- }
- }
- bind(&done);
+ // If reg is < 0, then we want to clamp to 0.
+ as_slti(ScratchRegister, reg, 0);
+ as_movn(reg, zero, ScratchRegister);
+
+ // If reg is >= 255, then we want to clamp to 255.
+ ma_li(SecondScratchReg, Imm32(255));
+ as_slti(ScratchRegister, reg, 255);
+ as_movz(reg, SecondScratchReg, ScratchRegister);
}
// Note: this function clobbers the input register.
void
MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output)
{
MOZ_ASSERT(input != ScratchDoubleReg);
Label positive, done;
@@ -1325,25 +1313,23 @@ MacroAssemblerMIPS64Compat::unboxNonDoub
computeScaledAddress(src, SecondScratchReg);
loadPtr(Address(SecondScratchReg, src.offset), dest);
ma_dext(dest, dest, Imm32(0), Imm32(JSVAL_TAG_SHIFT));
}
void
MacroAssemblerMIPS64Compat::unboxInt32(const ValueOperand& operand, Register dest)
{
- ma_dsll(dest, operand.valueReg(), Imm32(32));
- ma_dsra(dest, dest, Imm32(32));
+ ma_sll(dest, operand.valueReg(), Imm32(0));
}
void
MacroAssemblerMIPS64Compat::unboxInt32(Register src, Register dest)
{
- ma_dsll(dest, src, Imm32(32));
- ma_dsra(dest, dest, Imm32(32));
+ ma_sll(dest, src, Imm32(0));
}
void
MacroAssemblerMIPS64Compat::unboxInt32(const Address& src, Register dest)
{
load32(Address(src.base, src.offset), dest);
}
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -1399,23 +1399,27 @@ class LAsmJSInterruptCheck : public LIns
LAsmJSInterruptCheck()
{ }
bool isCall() const {
return true;
}
};
-class LAsmThrowUnreachable : public LInstructionHelper<0, 0, 0>
-{
- public:
- LIR_HEADER(AsmThrowUnreachable);
-
- LAsmThrowUnreachable()
+class LWasmTrap : public LInstructionHelper<0, 0, 0>
+{
+ public:
+ LIR_HEADER(WasmTrap);
+
+ LWasmTrap()
{ }
+
+ const MWasmTrap* mir() const {
+ return mir_->toWasmTrap();
+ }
};
template<size_t Defs, size_t Ops>
class LAsmReinterpretBase : public LInstructionHelper<Defs, Ops, 0>
{
typedef LInstructionHelper<Defs, Ops, 0> Base;
public:
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -364,17 +364,17 @@
_(RoundF) \
_(In) \
_(InArray) \
_(InstanceOfO) \
_(InstanceOfV) \
_(CallInstanceOf) \
_(InterruptCheck) \
_(AsmJSInterruptCheck) \
- _(AsmThrowUnreachable) \
+ _(WasmTrap) \
_(AsmReinterpret) \
_(AsmReinterpretToI64) \
_(AsmReinterpretFromI64) \
_(Rotate) \
_(RotateI64) \
_(GetDOMProperty) \
_(GetDOMMemberV) \
_(GetDOMMemberT) \
--- a/js/src/jit/x64/CodeGenerator-x64.cpp
+++ b/js/src/jit/x64/CodeGenerator-x64.cpp
@@ -533,21 +533,17 @@ void
CodeGeneratorX64::emitWasmLoad(T* ins)
{
const MWasmLoad* mir = ins->mir();
bool isInt64 = mir->type() == MIRType::Int64;
Scalar::Type accessType = mir->accessType();
MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD NYI");
MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
-
- if (mir->offset() > INT32_MAX) {
- masm.jump(wasm::JumpTarget::OutOfBounds);
- return;
- }
+ MOZ_ASSERT(mir->offset() <= INT32_MAX);
const LAllocation* ptr = ins->ptr();
Operand srcAddr = ptr->isBogus()
? Operand(HeapReg, mir->offset())
: Operand(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
uint32_t before = masm.size();
if (isInt64)
@@ -578,21 +574,17 @@ template <typename T>
void
CodeGeneratorX64::emitWasmStore(T* ins)
{
const MWasmStore* mir = ins->mir();
Scalar::Type accessType = mir->accessType();
MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD NYI");
MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
-
- if (mir->offset() > INT32_MAX) {
- masm.jump(wasm::JumpTarget::OutOfBounds);
- return;
- }
+ MOZ_ASSERT(mir->offset() <= INT32_MAX);
const LAllocation* value = ins->getOperand(ins->ValueIndex);
const LAllocation* ptr = ins->ptr();
Operand dstAddr = ptr->isBogus()
? Operand(HeapReg, mir->offset())
: Operand(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
uint32_t before = masm.size();
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -489,21 +489,19 @@ CodeGeneratorX86Shared::emitAsmJSBoundsC
masm.append(wasm::BoundsCheck(cmpOffset));
}
void
CodeGeneratorX86Shared::visitWasmBoundsCheck(LWasmBoundsCheck* ins)
{
const MWasmBoundsCheck* mir = ins->mir();
+
MOZ_ASSERT(gen->needsBoundsCheckBranch(mir));
- if (mir->offset() > INT32_MAX) {
- masm.jump(wasm::JumpTarget::OutOfBounds);
- return;
- }
+ MOZ_ASSERT(mir->offset() <= INT32_MAX);
Register ptrReg = ToRegister(ins->ptr());
maybeEmitWasmBoundsCheckBranch(mir, ptrReg, mir->isRedundant());
}
void
CodeGeneratorX86Shared::visitWasmTruncateToInt32(LWasmTruncateToInt32* lir)
{
@@ -543,19 +541,18 @@ void
CodeGeneratorX86Shared::maybeEmitWasmBoundsCheckBranch(const MWasmMemoryAccess* mir, Register ptr,
bool redundant)
{
if (!mir->needsBoundsCheck())
return;
MOZ_ASSERT(mir->endOffset() >= 1,
"need to subtract 1 to use JAE, see also AssemblerX86Shared::UpdateBoundsCheck");
- /*
- * TODO: See 1287224 Unify MWasmBoundsCheck::redunant_ and needsBoundsCheck
- */
+
+ // TODO: See 1287224 Unify MWasmBoundsCheck::redunant_ and needsBoundsCheck
if (!redundant) {
uint32_t cmpOffset = masm.cmp32WithPatch(ptr, Imm32(1 - mir->endOffset())).offset();
masm.j(Assembler::AboveOrEqual, wasm::JumpTarget::OutOfBounds);
masm.append(wasm::BoundsCheck(cmpOffset));
} else {
#ifdef DEBUG
Label ok;
uint32_t cmpOffset = masm.cmp32WithPatch(ptr, Imm32(1 - mir->endOffset())).offset();
--- a/js/src/jit/x86/CodeGenerator-x86.cpp
+++ b/js/src/jit/x86/CodeGenerator-x86.cpp
@@ -495,22 +495,17 @@ template <typename T>
void
CodeGeneratorX86::emitWasmLoad(T* ins)
{
const MWasmLoad* mir = ins->mir();
Scalar::Type accessType = mir->accessType();
MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD NYI");
MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
-
- if (mir->offset() > INT32_MAX) {
- // This is unreachable because of the bounds check.
- masm.breakpoint();
- return;
- }
+ MOZ_ASSERT(mir->offset() <= INT32_MAX);
const LAllocation* ptr = ins->ptr();
Operand srcAddr = ptr->isBogus()
? Operand(PatchedAbsoluteAddress(mir->offset()))
: Operand(ToRegister(ptr), mir->offset());
if (mir->type() == MIRType::Int64)
loadI64(accessType, srcAddr, ToOutRegister64(ins));
@@ -534,22 +529,17 @@ template <typename T>
void
CodeGeneratorX86::emitWasmStore(T* ins)
{
const MWasmStore* mir = ins->mir();
Scalar::Type accessType = mir->accessType();
MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD NYI");
MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
-
- if (mir->offset() > INT32_MAX) {
- // This is unreachable because of the bounds check.
- masm.breakpoint();
- return;
- }
+ MOZ_ASSERT(mir->offset() <= INT32_MAX);
const LAllocation* ptr = ins->ptr();
Operand dstAddr = ptr->isBogus()
? Operand(PatchedAbsoluteAddress(mir->offset()))
: Operand(ToRegister(ptr), mir->offset());
if (accessType == Scalar::Int64)
storeI64(accessType, ins->getInt64Operand(LWasmStoreI64::ValueIndex), dstAddr);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -243,28 +243,30 @@ MSG_DEF(JSMSG_DEPRECATED_BLOCK_SCOPE_FUN
MSG_DEF(JSMSG_DUPLICATE_EXPORT_NAME, 1, JSEXN_SYNTAXERR, "duplicate export name '{0}'")
MSG_DEF(JSMSG_DUPLICATE_FORMAL, 1, JSEXN_SYNTAXERR, "duplicate formal argument {0}")
MSG_DEF(JSMSG_DUPLICATE_LABEL, 0, JSEXN_SYNTAXERR, "duplicate label")
MSG_DEF(JSMSG_DUPLICATE_PROPERTY, 1, JSEXN_SYNTAXERR, "property name {0} appears more than once in object literal")
MSG_DEF(JSMSG_EMPTY_CONSEQUENT, 0, JSEXN_SYNTAXERR, "mistyped ; after conditional?")
MSG_DEF(JSMSG_EQUAL_AS_ASSIGN, 0, JSEXN_SYNTAXERR, "test for equality (==) mistyped as assignment (=)?")
MSG_DEF(JSMSG_EXPORT_DECL_AT_TOP_LEVEL,0, JSEXN_SYNTAXERR, "export declarations may only appear at top level of a module")
MSG_DEF(JSMSG_FINALLY_WITHOUT_TRY, 0, JSEXN_SYNTAXERR, "finally without try")
+MSG_DEF(JSMSG_FORBIDDEN_AS_STATEMENT, 1, JSEXN_SYNTAXERR, "{0} can't appear in single-statement context")
MSG_DEF(JSMSG_FROM_AFTER_IMPORT_CLAUSE, 0, JSEXN_SYNTAXERR, "missing keyword 'from' after import clause")
MSG_DEF(JSMSG_FROM_AFTER_EXPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'from' after export *")
MSG_DEF(JSMSG_GARBAGE_AFTER_INPUT, 2, JSEXN_SYNTAXERR, "unexpected garbage after {0}, starting with {1}")
MSG_DEF(JSMSG_IDSTART_AFTER_NUMBER, 0, JSEXN_SYNTAXERR, "identifier starts immediately after numeric literal")
MSG_DEF(JSMSG_ILLEGAL_CHARACTER, 0, JSEXN_SYNTAXERR, "illegal character")
MSG_DEF(JSMSG_IMPORT_DECL_AT_TOP_LEVEL, 0, JSEXN_SYNTAXERR, "import declarations may only appear at top level of a module")
MSG_DEF(JSMSG_INVALID_FOR_IN_DECL_WITH_INIT,0,JSEXN_SYNTAXERR,"for-in loop head declarations may not have initializers")
MSG_DEF(JSMSG_LABEL_NOT_FOUND, 0, JSEXN_SYNTAXERR, "label not found")
MSG_DEF(JSMSG_LET_CLASS_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a class")
MSG_DEF(JSMSG_LET_COMP_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a comprehension variable")
MSG_DEF(JSMSG_LEXICAL_DECL_NOT_IN_BLOCK, 1, JSEXN_SYNTAXERR, "{0} declaration not directly within block")
MSG_DEF(JSMSG_LEXICAL_DECL_LABEL, 1, JSEXN_SYNTAXERR, "{0} declarations cannot be labelled")
+MSG_DEF(JSMSG_GENERATOR_LABEL, 0, JSEXN_SYNTAXERR, "generator functions cannot be labelled")
MSG_DEF(JSMSG_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions cannot be labelled")
MSG_DEF(JSMSG_SLOPPY_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions can only be labelled inside blocks")
MSG_DEF(JSMSG_LINE_BREAK_AFTER_THROW, 0, JSEXN_SYNTAXERR, "no line break is allowed between 'throw' and its expression")
MSG_DEF(JSMSG_MALFORMED_ESCAPE, 1, JSEXN_SYNTAXERR, "malformed {0} character escape sequence")
MSG_DEF(JSMSG_MISSING_BINARY_DIGITS, 0, JSEXN_SYNTAXERR, "missing binary digits after '0b'")
MSG_DEF(JSMSG_MISSING_EXPONENT, 0, JSEXN_SYNTAXERR, "missing exponent")
MSG_DEF(JSMSG_MISSING_EXPR_AFTER_THROW,0, JSEXN_SYNTAXERR, "throw statement is missing an expression")
MSG_DEF(JSMSG_MISSING_FORMAL, 0, JSEXN_SYNTAXERR, "missing formal parameter")
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -714,26 +714,29 @@ JSCompartment::ensureRandomNumberGenerat
{
if (randomNumberGenerator.isNothing()) {
mozilla::Array<uint64_t, 2> seed;
GenerateXorShift128PlusSeed(seed);
randomNumberGenerator.emplace(seed[0], seed[1]);
}
}
+double
+js::math_random_impl(JSContext* cx)
+{
+ JSCompartment* comp = cx->compartment();
+ comp->ensureRandomNumberGenerator();
+ return comp->randomNumberGenerator.ref().nextDouble();
+}
+
bool
js::math_random(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
-
- JSCompartment* comp = cx->compartment();
- comp->ensureRandomNumberGenerator();
-
- double z = comp->randomNumberGenerator.ref().nextDouble();
- args.rval().setDouble(z);
+ args.rval().setNumber(math_random_impl(cx));
return true;
}
bool
js::math_round_handle(JSContext* cx, HandleValue arg, MutableHandleValue res)
{
double d;
if (!ToNumber(cx, arg, &d))
--- a/js/src/jsmath.h
+++ b/js/src/jsmath.h
@@ -89,16 +89,19 @@ InitMathClass(JSContext* cx, HandleObjec
// Fill |seed[0]| and |seed[1]| with random bits, suitable for
// seeding a XorShift128+ random number generator.
extern void
GenerateXorShift128PlusSeed(mozilla::Array<uint64_t, 2>& seed);
extern uint64_t
random_next(uint64_t* rngState, int bits);
+extern double
+math_random_impl(JSContext* cx);
+
extern bool
math_random(JSContext* cx, unsigned argc, js::Value* vp);
extern bool
math_abs_handle(JSContext* cx, js::HandleValue v, js::MutableHandleValue r);
extern bool
math_abs(JSContext* cx, unsigned argc, js::Value* vp);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2539,16 +2539,23 @@ js::SetPrototype(JSContext* cx, HandleOb
{
// The proxy trap subsystem fully handles prototype-setting for proxies
// with dynamic [[Prototype]]s.
if (obj->hasDynamicPrototype()) {
MOZ_ASSERT(obj->is<ProxyObject>());
return Proxy::setPrototype(cx, obj, proto, result);
}
+ /*
+ * ES6 9.1.2 step 3-4 if |obj.[[Prototype]]| has SameValue as |proto| return true.
+ * Since the values in question are objects, we can just compare pointers.
+ */
+ if (proto == obj->staticPrototype())
+ return result.succeed();
+
/* Disallow mutation of immutable [[Prototype]]s. */
if (obj->staticPrototypeIsImmutable() && ImmutablePrototypesEnabled)
return result.fail(JSMSG_CANT_SET_PROTO);
/*
* Disallow mutating the [[Prototype]] on ArrayBuffer objects, which
* due to their complicated delegate-object shenanigans can't easily
* have a mutable [[Prototype]].
@@ -2573,23 +2580,16 @@ js::SetPrototype(JSContext* cx, HandleOb
* for flash-related security reasons.
*/
if (!strcmp(obj->getClass()->name, "Location")) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_SET_PROTO_OF,
"incompatible Location object");
return false;
}
- /*
- * ES6 9.1.2 step 3-4 if |obj.[[Prototype]]| has SameValue as |proto| return true.
- * Since the values in question are objects, we can just compare pointers.
- */
- if (proto == obj->staticPrototype())
- return result.succeed();
-
/* ES6 9.1.2 step 5 forbids changing [[Prototype]] if not [[Extensible]]. */
bool extensible;
if (!IsExtensible(cx, obj, &extensible))
return false;
if (!extensible)
return result.fail(JSMSG_CANT_SET_PROTO);
// If this is a global object, resolve the Object class so that its
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Object/setPrototypeOf-same-value.js
@@ -0,0 +1,11 @@
+// Setting a "new" prototype to the current [[Prototype]] value should never fail
+
+var x = {}, t = Object.create(x);
+Object.preventExtensions(t);
+// Should not fail, because it is the same [[Prototype]] value
+Object.setPrototypeOf(t, x);
+
+// Object.prototype's [[Prototype]] is immutable, make sure we can still set null
+Object.setPrototypeOf(Object.prototype, null);
+
+reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Syntax/declaration-forbidden-in-label.js
@@ -0,0 +1,34 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1288459;
+var summary =
+ "Properly implement the spec's distinctions between StatementListItem and " +
+ "Statement grammar productions and their uses";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+assertThrowsInstanceOf(() => Function("a: let x;"), SyntaxError);
+assertThrowsInstanceOf(() => Function("b: const y = 3;"), SyntaxError);
+assertThrowsInstanceOf(() => Function("c: class z {};"), SyntaxError);
+
+assertThrowsInstanceOf(() => Function("'use strict'; d: function w() {};"), SyntaxError);
+
+// Annex B.3.2 allows this in non-strict mode code.
+Function("e: function x() {};");
+
+assertThrowsInstanceOf(() => Function("f: function* y() {}"), SyntaxError);
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Syntax/let-as-label.js
@@ -0,0 +1,29 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1288459;
+var summary = "let can't be used as a label in strict mode code";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+Function("let: 42")
+assertThrowsInstanceOf(() => Function(" 'use strict'; let: 42"), SyntaxError);
+assertThrowsInstanceOf(() => Function(" 'use strict' \n let: 42"), SyntaxError);
+
+eval("let: 42")
+assertThrowsInstanceOf(() => eval(" 'use strict'; let: 42"), SyntaxError);
+assertThrowsInstanceOf(() => eval(" 'use strict' \n let: 42;"), SyntaxError);
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Syntax/statement-versus-statementlistitem.js
@@ -0,0 +1,148 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs evaluate, parseModule
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1288459;
+var summary =
+ "Properly implement the spec's distinctions between StatementListItem and " +
+ "Statement grammar productions and their uses";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var counter = 0;
+
+// Test all the different contexts that expect a Statement.
+
+function contextAcceptsStatement(pat)
+{
+ var goodCode =
+`function f${counter++}()
+{
+ ${pat.replace("@@@", "let \n {} \n ;")}
+}
+`;
+
+ evaluate(goodCode);
+
+ var badCode =
+`function f${counter++}()
+{
+ ${pat.replace("@@@", "let {} \n ;")}
+}
+`;
+
+ try
+ {
+ evaluate(badCode);
+ throw new Error("didn't throw");
+ }
+ catch (e)
+ {
+ assertEq(e instanceof SyntaxError, true,
+ "didn't get a syntax error, instead got: " + e);
+ }
+}
+
+contextAcceptsStatement("if (0) @@@");
+contextAcceptsStatement("if (0) ; else @@@");
+contextAcceptsStatement("if (0) ; else if (0) @@@");
+contextAcceptsStatement("if (0) ; else if (0) ; else @@@");
+// Can't test do-while loops this way because the Statement isn't in trailing
+// position, so 'let' followed by newline *must* be followed by 'while'.
+contextAcceptsStatement("while (1) @@@");
+contextAcceptsStatement("for (1; 1; 0) @@@");
+contextAcceptsStatement("for (var x; 1; 0) @@@");
+contextAcceptsStatement("for (let x; 1; 0) @@@");
+contextAcceptsStatement("for (const x = 1; 1; 0) @@@");
+contextAcceptsStatement("for (x in 0) @@@");
+contextAcceptsStatement("for (var x in 0) @@@");
+contextAcceptsStatement("for (let x in 0) @@@");
+contextAcceptsStatement("for (const x in 0) @@@");
+contextAcceptsStatement("for (x of []) @@@");
+contextAcceptsStatement("for (var x of []) @@@");
+contextAcceptsStatement("for (let x of []) @@@");
+contextAcceptsStatement("for (const x of []) @@@");
+contextAcceptsStatement("with (1) @@@");
+contextAcceptsStatement("foo: @@@");
+
+// Test all the different contexts that expect a StatementListItem.
+
+function contextAcceptsStatementListItem(pat)
+{
+ var goodCode =
+`function f${counter++}()
+{
+ ${pat.replace("@@@", "let \n [] = [] ;")}
+}
+`;
+
+ evaluate(goodCode);
+
+ var moarGoodCode = pat.replace("@@@", "let \n [] = [] ;");
+ evaluate(moarGoodCode);
+
+ var goodModuleCode = pat.replace("@@@", "let \n [] = [] ;");
+ parseModule(goodModuleCode);
+
+ var badCode =
+`function f${counter++}()
+{
+ ${pat.replace("@@@", "let \n let = 3 ;")}
+}
+`;
+
+ try
+ {
+ evaluate(badCode);
+ throw new Error("didn't throw");
+ }
+ catch (e)
+ {
+ assertEq(e instanceof SyntaxError, true,
+ "didn't get a syntax error, instead got: " + e);
+ }
+}
+
+contextAcceptsStatementListItem("{ @@@ }");
+contextAcceptsStatementListItem("switch (1) { case 1: @@@ }");
+contextAcceptsStatementListItem("switch (1) { default: @@@ }");
+contextAcceptsStatementListItem("switch (1) { case 0: case 1: @@@ }");
+contextAcceptsStatementListItem("switch (1) { case 0: break; case 1: @@@ }");
+contextAcceptsStatementListItem("switch (1) { default: @@@ case 2: }");
+contextAcceptsStatementListItem("try { @@@ } catch (e) { }");
+contextAcceptsStatementListItem("try { @@@ } finally { }");
+contextAcceptsStatementListItem("try { @@@ } catch (e) { } finally { }");
+contextAcceptsStatementListItem("try { } catch (e) { @@@ }");
+contextAcceptsStatementListItem("try { } finally { @@@ }");
+contextAcceptsStatementListItem("try { } catch (e) { @@@ } finally { }");
+contextAcceptsStatementListItem("try { } catch (e) { } finally { @@@ }");
+contextAcceptsStatementListItem("_ => { @@@ }");
+contextAcceptsStatementListItem("function q() { @@@ }");
+contextAcceptsStatementListItem("function* q() { @@@ }");
+contextAcceptsStatementListItem("(function() { @@@ })");
+contextAcceptsStatementListItem("(function*() { @@@ })");
+contextAcceptsStatementListItem("({ *q() { @@@ } })");
+contextAcceptsStatementListItem("({ q() { @@@ } })");
+contextAcceptsStatementListItem("({ get q() { @@@ } })");
+contextAcceptsStatementListItem("({ set q(v) { @@@ } })");
+contextAcceptsStatementListItem("(class { f() { @@@ } })");
+contextAcceptsStatementListItem("(class { static f() { @@@ } })");
+contextAcceptsStatementListItem("(class { static *f() { @@@ } })");
+contextAcceptsStatementListItem("(class { static get f() { @@@ } })");
+contextAcceptsStatementListItem("(class { static set f(v) { @@@ } })");
+contextAcceptsStatementListItem("(class { static() { @@@ } })");
+contextAcceptsStatementListItem("@@@");
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Syntax/yield-as-identifier.js
@@ -0,0 +1,54 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1288459;
+var summary = "|yield| is sometimes a valid identifier";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+function t(code)
+{
+ var strictSemi = " 'use strict'; " + code;
+ var strictASI = " 'use strict' \n " + code;
+
+ var creationFunctions = ["Function"];
+ if (typeof evaluate === "function")
+ creationFunctions.push("evaluate");
+ if (typeof parseModule === "function")
+ creationFunctions.push("parseModule");
+
+ for (var func of creationFunctions)
+ {
+ var g = newGlobal();
+ var f = g[func];
+
+ if (func === "parseModule")
+ assertThrowsInstanceOf(() => f(code), g.SyntaxError);
+ else
+ f(code);
+
+ assertThrowsInstanceOf(() => f(strictSemi), g.SyntaxError);
+ assertThrowsInstanceOf(() => f(strictASI), g.SyntaxError);
+ }
+}
+
+t("var yield = 3;");
+t("let yield = 3;");
+t("const yield = 3;");
+t("for (var yield = 3; ; ) break;");
+t("for (let yield = 3; ; ) break;");
+t("for (const yield = 3; ; ) break;");
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2241,17 +2241,18 @@ static const JSFunctionSpec intrinsic_fu
JS_FN("std_String_toLocaleLowerCase", str_toLocaleLowerCase, 0,0),
JS_FN("std_String_toLocaleUpperCase", str_toLocaleUpperCase, 0,0),
#if !EXPOSE_INTL_API
JS_FN("std_String_localeCompare", str_localeCompare, 1,0),
#else
JS_FN("std_String_normalize", str_normalize, 0,0),
#endif
JS_FN("std_String_concat", str_concat, 1,0),
-
+
+ JS_FN("std_TypedArray_buffer", js::TypedArray_bufferGetter, 1,0),
JS_FN("std_WeakMap_has", WeakMap_has, 1,0),
JS_FN("std_WeakMap_get", WeakMap_get, 2,0),
JS_FN("std_WeakMap_set", WeakMap_set, 2,0),
JS_FN("std_WeakMap_delete", WeakMap_delete, 1,0),
JS_FN("std_SIMD_Int8x16_extractLane", simd_int8x16_extractLane, 2,0),
JS_FN("std_SIMD_Int16x8_extractLane", simd_int16x8_extractLane, 2,0),
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -1321,18 +1321,18 @@ BufferGetterImpl(JSContext* cx, const Ca
MOZ_ASSERT(TypedArrayObject::is(args.thisv()));
Rooted<TypedArrayObject*> tarray(cx, &args.thisv().toObject().as<TypedArrayObject>());
if (!TypedArrayObject::ensureHasBuffer(cx, tarray))
return false;
args.rval().set(TypedArrayObject::bufferValue(tarray));
return true;
}
-static bool
-TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp)
+/*static*/ bool
+js::TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<TypedArrayObject::is, BufferGetterImpl>(cx, args);
}
/* static */ const JSPropertySpec
TypedArrayObject::protoAccessors[] = {
JS_PSG("length", TypedArray_lengthGetter, 0),
--- a/js/src/vm/TypedArrayObject.h
+++ b/js/src/vm/TypedArrayObject.h
@@ -289,16 +289,18 @@ class TypedArrayObject : public NativeOb
/* Accessors and functions */
static bool is(HandleValue v);
static bool set(JSContext* cx, unsigned argc, Value* vp);
};
+MOZ_MUST_USE bool TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp);
+
extern TypedArrayObject*
TypedArrayCreateWithTemplate(JSContext* cx, HandleObject templateObj, int32_t len);
inline bool
IsTypedArrayClass(const Class* clasp)
{
return &TypedArrayObject::classes[0] <= clasp &&
clasp < &TypedArrayObject::classes[Scalar::MaxTypedArrayViewType];
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -418,17 +418,16 @@ public:
mAnimatedGeometryRoot(nullptr),
mScrollClip(nullptr),
mReferenceFrame(nullptr),
mLayer(nullptr),
mSolidColor(NS_RGBA(0, 0, 0, 0)),
mIsSolidColorInVisibleRegion(false),
mFontSmoothingBackgroundColor(NS_RGBA(0,0,0,0)),
mSingleItemFixedToViewport(false),
- mIsCaret(false),
mNeedComponentAlpha(false),
mForceTransparentSurface(false),
mHideAllLayersBelow(false),
mOpaqueForAnimatedGeometryRootParent(false),
mDisableFlattening(false),
mBackfaceHidden(false),
mImage(nullptr),
mCommonClipCount(-1),
@@ -582,20 +581,16 @@ public:
*/
nscolor mFontSmoothingBackgroundColor;
/**
* True if the layer contains exactly one item that returned true for
* ShouldFixToViewport.
*/
bool mSingleItemFixedToViewport;
/**
- * True if the layer contains exactly one item for the caret.
- */
- bool mIsCaret;
- /**
* True if there is any text visible in the layer that's over
* transparent pixels in the layer.
*/
bool mNeedComponentAlpha;
/**
* Set if the layer should be treated as transparent, even if its entire
* area is covered by opaque display items. For example, this needs to
* be set if something is going to "punch holes" in the layer by clearing
@@ -678,18 +673,16 @@ struct NewLayerEntry {
: mAnimatedGeometryRoot(nullptr)
, mScrollClip(nullptr)
, mLayerContentsVisibleRect(0, 0, -1, -1)
, mLayerState(LAYER_INACTIVE)
, mHideAllLayersBelow(false)
, mOpaqueForAnimatedGeometryRootParent(false)
, mPropagateComponentAlphaFlattening(true)
, mUntransformedVisibleRegion(false)
- , mIsCaret(false)
- , mIsPerspectiveItem(false)
{}
// mLayer is null if the previous entry is for a PaintedLayer that hasn't
// been optimized to some other form (yet).
RefPtr<Layer> mLayer;
AnimatedGeometryRoot* mAnimatedGeometryRoot;
const DisplayItemScrollClip* mScrollClip;
// If non-null, this ScrollMetadata is set to the be the first ScrollMetadata
// on the layer.
@@ -715,18 +708,16 @@ struct NewLayerEntry {
bool mOpaqueForAnimatedGeometryRootParent;
// If true, then the content flags for this layer should contribute
// to our decision to flatten component alpha layers, false otherwise.
bool mPropagateComponentAlphaFlattening;
// mVisibleRegion is relative to the associated frame before
// transform.
bool mUntransformedVisibleRegion;
- bool mIsCaret;
- bool mIsPerspectiveItem;
};
class PaintedLayerDataTree;
/**
* This is tree node type for PaintedLayerDataTree.
* Each node corresponds to a different animated geometry root, and contains
* a stack of PaintedLayerDatas, in bottom-to-top order.
@@ -3082,17 +3073,16 @@ void ContainerState::FinishPaintedLayerD
NS_ASSERTION(newLayerEntry->mLayer == data->mLayer,
"Painted layer at wrong index");
// Store optimized layer in reserved slot
newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex + 1];
NS_ASSERTION(!newLayerEntry->mLayer, "Slot already occupied?");
newLayerEntry->mLayer = layer;
newLayerEntry->mAnimatedGeometryRoot = data->mAnimatedGeometryRoot;
newLayerEntry->mScrollClip = data->mScrollClip;
- newLayerEntry->mIsCaret = data->mIsCaret;
// Hide the PaintedLayer. We leave it in the layer tree so that we
// can find and recycle it later.
ParentLayerIntRect emptyRect;
data->mLayer->SetClipRect(Some(emptyRect));
data->mLayer->SetVisibleRegion(LayerIntRegion());
data->mLayer->InvalidateRegion(data->mLayer->GetValidRegion().GetBounds());
data->mLayer->SetEventRegions(EventRegions());
@@ -3531,23 +3521,21 @@ ContainerState::NewPaintedLayerData(nsDi
{
PaintedLayerData data;
data.mAnimatedGeometryRoot = aAnimatedGeometryRoot;
data.mScrollClip = aScrollClip;
data.mAnimatedGeometryRootOffset = aTopLeft;
data.mReferenceFrame = aItem->ReferenceFrame();
data.mSingleItemFixedToViewport = aShouldFixToViewport;
data.mBackfaceHidden = aItem->Frame()->In3DContextAndBackfaceIsHidden();
- data.mIsCaret = aItem->GetType() == nsDisplayItem::TYPE_CARET;
data.mNewChildLayersIndex = mNewChildLayers.Length();
NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
newLayerEntry->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
newLayerEntry->mScrollClip = aScrollClip;
- newLayerEntry->mIsCaret = data.mIsCaret;
// newLayerEntry->mOpaqueRegion is filled in later from
// paintedLayerData->mOpaqueRegion, if necessary.
// Allocate another entry for this layer's optimization to ColorLayer/ImageLayer
mNewChildLayers.AppendElement();
return data;
}
@@ -4160,27 +4148,23 @@ ContainerState::ProcessDisplayItems(nsDi
NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, ownLayer) < 0,
"Layer already in list???");
NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
newLayerEntry->mLayer = ownLayer;
newLayerEntry->mAnimatedGeometryRoot = animatedGeometryRoot;
newLayerEntry->mScrollClip = agrScrollClip;
newLayerEntry->mLayerState = layerState;
- if (itemType == nsDisplayItem::TYPE_PERSPECTIVE) {
- newLayerEntry->mIsPerspectiveItem = true;
- }
// Don't attempt to flatten compnent alpha layers that are within
// a forced active layer, or an active transform;
if (itemType == nsDisplayItem::TYPE_TRANSFORM ||
layerState == LAYER_ACTIVE_FORCE) {
newLayerEntry->mPropagateComponentAlphaFlattening = false;
}
- newLayerEntry->mIsCaret = itemType == nsDisplayItem::TYPE_CARET;
// nsDisplayTransform::BuildLayer must set layerContentsVisibleRect.
// We rely on this to ensure 3D transforms compute a reasonable
// layer visible region.
NS_ASSERTION(itemType != nsDisplayItem::TYPE_TRANSFORM ||
layerContentsVisibleRect.width >= 0,
"Transform items must set layerContentsVisibleRect!");
if (mLayerBuilder->IsBuildingRetainedLayers()) {
newLayerEntry->mLayerContentsVisibleRect = layerContentsVisibleRect;
--- a/layout/base/RestyleManagerBase.h
+++ b/layout/base/RestyleManagerBase.h
@@ -4,23 +4,31 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_RestyleManagerBase_h
#define mozilla_RestyleManagerBase_h
#include "mozilla/OverflowChangedTracker.h"
#include "nsChangeHint.h"
+#include "nsPresContext.h"
+class nsCString;
+class nsCSSFrameConstructor;
class nsStyleChangeList;
namespace mozilla {
+class EventStates;
+class RestyleManager;
class ServoRestyleManager;
-class RestyleManager;
+
+namespace dom {
+class Element;
+}
/**
* Class for sharing data and logic common to both RestyleManager and
* ServoRestyleManager.
*/
class RestyleManagerBase
{
protected:
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -3,16 +3,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/. */
#include "mozilla/ServoRestyleManager.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/dom/ChildIterator.h"
#include "nsContentUtils.h"
+#include "nsPrintfCString.h"
#include "nsStyleChangeList.h"
using namespace mozilla::dom;
namespace mozilla {
ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
: RestyleManagerBase(aPresContext)
--- a/layout/base/nsChangeHint.h
+++ b/layout/base/nsChangeHint.h
@@ -126,16 +126,21 @@ enum nsChangeHint {
*/
nsChangeHint_RecomputePosition = 1 << 16,
/**
* Behaves like ReconstructFrame, but only if the frame has descendants
* that are absolutely or fixed position. Use this hint when a style change
* has changed whether the frame is a container for fixed-pos or abs-pos
* elements, but reframing is otherwise not needed.
+ *
+ * Note that nsStyleContext::CalcStyleDifference adjusts results
+ * returned by style struct CalcDifference methods to return this hint
+ * only if there was a change to whether the element's overall style
+ * indicates that it establishes a containing block.
*/
nsChangeHint_UpdateContainingBlock = 1 << 17,
/**
* This change hint has *no* change handling behavior. However, it
* exists to be a non-inherited hint, because when the border-style
* changes, and it's inherited by a child, that might require a reflow
* due to the border-width change on the child.
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1643,17 +1643,17 @@ nsDisplayList::ComputeVisibilityForSubli
bool anyVisible = false;
AutoTArray<nsDisplayItem*, 512> elements;
MoveListTo(this, &elements);
for (int32_t i = elements.Length() - 1; i >= 0; --i) {
nsDisplayItem* item = elements[i];
- if (item->mForceNotVisible) {
+ if (item->mForceNotVisible && !item->GetSameCoordinateSystemChildren()) {
NS_ASSERTION(item->mVisibleRect.IsEmpty(),
"invisible items should have empty vis rect");
} else {
nsRect bounds = item->GetClippedBounds(aBuilder);
nsRegion itemVisible;
itemVisible.And(*aVisibleRegion, bounds);
item->mVisibleRect = itemVisible.GetBounds();
@@ -1789,17 +1789,17 @@ already_AddRefed<LayerManager> nsDisplay
if (computeInvalidRect) {
props = Move(LayerProperties::CloneFrom(layerManager->GetRoot()));
}
// Clear any ScrollMetadata that may have been set on the root layer on a
// previous paint. This paint will set new metrics if necessary, and if we
// don't clear the old one here, we may be left with extra metrics.
if (Layer* root = layerManager->GetRoot()) {
- root->SetScrollMetadata(nsTArray<ScrollMetadata>());
+ root->SetScrollMetadata(nsTArray<ScrollMetadata>());
}
ContainerLayerParameters containerParameters
(presShell->GetResolution(), presShell->GetResolution());
RefPtr<ContainerLayer> root = layerBuilder->
BuildContainerLayerFor(aBuilder, layerManager, frame, nullptr, this,
containerParameters, nullptr);
@@ -2314,19 +2314,22 @@ nsDisplayItem::ComputeVisibility(nsDispl
{
return !mVisibleRect.IsEmpty() &&
!IsInvisibleInRect(aVisibleRegion->GetBounds());
}
bool
nsDisplayItem::RecomputeVisibility(nsDisplayListBuilder* aBuilder,
nsRegion* aVisibleRegion) {
- if (mForceNotVisible) {
+ if (mForceNotVisible && !GetSameCoordinateSystemChildren()) {
+ // mForceNotVisible wants to ensure that this display item doesn't render
+ // anything itself. If this item has contents, then we obviously want to
+ // render those, so we don't need this check in that case.
NS_ASSERTION(mVisibleRect.IsEmpty(),
- "invisible items should have empty vis rect");
+ "invisible items without children should have empty vis rect");
} else {
nsRect bounds = GetClippedBounds(aBuilder);
nsRegion itemVisible;
itemVisible.And(*aVisibleRegion, bounds);
mVisibleRect = itemVisible.GetBounds();
}
@@ -5083,18 +5086,18 @@ already_AddRefed<Layer>
nsDisplayFixedPosition::BuildLayer(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
const ContainerLayerParameters& aContainerParameters) {
RefPtr<Layer> layer =
nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters);
layer->SetIsFixedPosition(true);
- nsPresContext* presContext = Frame()->PresContext();
- nsIFrame* fixedFrame = mIsFixedBackground ? presContext->PresShell()->GetRootFrame() : Frame();
+ nsPresContext* presContext = mFrame->PresContext();
+ nsIFrame* fixedFrame = mIsFixedBackground ? presContext->PresShell()->GetRootFrame() : mFrame;
const nsIFrame* viewportFrame = fixedFrame->GetParent();
// anchorRect will be in the container's coordinate system (aLayer's parent layer).
// This is the same as the display items' reference frame.
nsRect anchorRect;
if (viewportFrame) {
// Fixed position frames are reflowed into the scroll-port size if one has
// been set.
--- a/layout/base/nsLayoutDebugger.cpp
+++ b/layout/base/nsLayoutDebugger.cpp
@@ -12,16 +12,17 @@
#include "nsAttrValue.h"
#include "nsFrame.h"
#include "nsDisplayList.h"
#include "FrameLayerBuilder.h"
#include "nsPrintfCString.h"
#include "DisplayItemScrollClip.h"
+#include <iostream>
#include <stdio.h>
using namespace mozilla;
using namespace mozilla::layers;
#ifdef DEBUG
class nsLayoutDebugger : public nsILayoutDebugger {
public:
@@ -243,16 +244,35 @@ void
nsFrame::PrintDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayList& aList,
std::stringstream& aStream,
bool aDumpHtml)
{
PrintDisplayListTo(aBuilder, aList, aStream, 0, aDumpHtml);
}
+/**
+ * The two functions below are intended to be called from a debugger.
+ */
+void
+PrintDisplayItemToStdout(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem)
+{
+ std::stringstream stream;
+ PrintDisplayItemTo(aBuilder, aItem, stream, 0, true, false);
+ std::cout << stream.str() << std::endl;
+}
+
+void
+PrintDisplayListToStdout(nsDisplayListBuilder* aBuilder, const nsDisplayList& aList)
+{
+ std::stringstream stream;
+ PrintDisplayListTo(aBuilder, aList, stream, 0, false);
+ std::cout << stream.str() << std::endl;
+}
+
#ifdef MOZ_DUMP_PAINTING
static void
PrintDisplayListSetItem(nsDisplayListBuilder* aBuilder,
const char* aItemName,
const nsDisplayList& aList,
std::stringstream& aStream,
bool aDumpHtml)
{
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1180,19 +1180,18 @@ nsIFrame::Extend3DContext() const
return false;
}
if (HasOpacity()) {
return false;
}
const nsStyleEffects* effects = StyleEffects();
- nsRect temp;
return !nsFrame::ShouldApplyOverflowClipping(this, disp) &&
- !GetClipPropClipRect(disp, effects, &temp, GetSize()) &&
+ !GetClipPropClipRect(disp, effects, GetSize()) &&
!nsSVGIntegrationUtils::UsingEffectsForFrame(this);
}
bool
nsIFrame::Combines3DTransformWithAncestors() const
{
if (!GetParent() || !GetParent()->Extend3DContext()) {
return false;
@@ -1930,70 +1929,45 @@ inline static bool IsSVGContentWithCSSCl
// elements regardless of the value of the 'position' property. Here we obey
// the CSS spec for outer-<svg> (since that's what we generally do), but
// obey the SVG spec for other SVG elements to which 'clip' applies.
return (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) &&
aFrame->GetContent()->IsAnyOfSVGElements(nsGkAtoms::svg,
nsGkAtoms::foreignObject);
}
-bool
+Maybe<nsRect>
nsIFrame::GetClipPropClipRect(const nsStyleDisplay* aDisp,
const nsStyleEffects* aEffects,
- nsRect* aRect,
const nsSize& aSize) const
{
- NS_PRECONDITION(aRect, "Must have aRect out parameter");
-
if (!(aEffects->mClipFlags & NS_STYLE_CLIP_RECT) ||
!(aDisp->IsAbsolutelyPositioned(this) || IsSVGContentWithCSSClip(this))) {
- return false;
- }
-
- *aRect = aEffects->mClip;
+ return Nothing();
+ }
+
+ nsRect rect = aEffects->mClip;
if (MOZ_LIKELY(StyleBorder()->mBoxDecorationBreak ==
StyleBoxDecorationBreak::Slice)) {
// The clip applies to the joined boxes so it's relative the first
// continuation.
nscoord y = 0;
for (nsIFrame* f = GetPrevContinuation(); f; f = f->GetPrevContinuation()) {
y += f->GetRect().height;
}
- aRect->MoveBy(nsPoint(0, -y));
+ rect.MoveBy(nsPoint(0, -y));
}
if (NS_STYLE_CLIP_RIGHT_AUTO & aEffects->mClipFlags) {
- aRect->width = aSize.width - aRect->x;
+ rect.width = aSize.width - rect.x;
}
if (NS_STYLE_CLIP_BOTTOM_AUTO & aEffects->mClipFlags) {
- aRect->height = aSize.height - aRect->y;
- }
- return true;
-}
-
-/**
- * If the CSS 'clip' property applies to this frame, set it up
- * in aBuilder->ClipState() to clip all content descendants. Returns true
- * if the property applies, and if so also returns the clip rect (relative
- * to aFrame) in *aRect.
- */
-static bool
-ApplyClipPropClipping(nsDisplayListBuilder* aBuilder,
- const nsIFrame* aFrame,
- const nsStyleDisplay* aDisp,
- const nsStyleEffects* aEffects,
- nsRect* aRect,
- DisplayListClipState::AutoSaveRestore& aClipState)
-{
- if (!aFrame->GetClipPropClipRect(aDisp, aEffects, aRect, aFrame->GetSize()))
- return false;
-
- nsRect clipRect = *aRect + aBuilder->ToReferenceFrame(aFrame);
- aClipState.ClipContentDescendants(clipRect);
- return true;
+ rect.height = aSize.height - rect.y;
+ }
+ return Some(rect);
}
/**
* If the CSS 'overflow' property applies to this frame, and is not
* handled by constructing a dedicated nsHTML/XULScrollFrame, set up clipping
* for that overflow in aBuilder->ClipState() to clip all containing-block
* descendants.
*/
@@ -2186,47 +2160,49 @@ nsIFrame::BuildDisplayListForStackingCon
return;
}
}
if (disp->mWillChangeBitField != 0) {
aBuilder->AddToWillChangeBudget(this, GetSize());
}
+ bool extend3DContext = Extend3DContext();
Maybe<nsDisplayListBuilder::AutoPreserves3DContext> autoPreserves3DContext;
- if (Extend3DContext() && !Combines3DTransformWithAncestors()) {
+ if (extend3DContext && !Combines3DTransformWithAncestors()) {
// Start a new preserves3d context to keep informations on
// nsDisplayListBuilder.
autoPreserves3DContext.emplace(aBuilder);
// Save dirty rect on the builder to avoid being distorted for
// multiple transforms along the chain.
aBuilder->SetPreserves3DDirtyRect(aDirtyRect);
}
// For preserves3d, use the dirty rect already installed on the
// builder, since aDirtyRect maybe distorted for transforms along
// the chain.
nsRect dirtyRect = aDirtyRect;
bool inTransform = aBuilder->IsInTransform();
bool isTransformed = IsTransformed();
+ bool hasPerspective = HasPerspective();
// reset blend mode so we can keep track if this stacking context needs have
// a nsDisplayBlendContainer. Set the blend mode back when the routine exits
// so we keep track if the parent stacking context needs a container too.
AutoSaveRestoreContainsBlendMode autoRestoreBlendMode(*aBuilder);
aBuilder->SetContainsBlendMode(false);
nsRect dirtyRectOutsideTransform = dirtyRect;
if (isTransformed) {
const nsRect overflow = GetVisualOverflowRectRelativeToSelf();
if (nsDisplayTransform::ShouldPrerenderTransformedContent(aBuilder,
this)) {
dirtyRect = overflow;
} else {
- if (overflow.IsEmpty() && !Extend3DContext()) {
+ if (overflow.IsEmpty() && !extend3DContext) {
return;
}
// If we're in preserve-3d then grab the dirty rect that was given to the root
// and transform using the combined transform.
if (Combines3DTransformWithAncestors()) {
dirtyRect = aBuilder->GetPreserves3DDirtyRect(this);
}
@@ -2264,20 +2240,69 @@ nsIFrame::BuildDisplayListForStackingCon
nsLayoutUtils::GetNearestScrollableFrame(GetParent(),
nsLayoutUtils::SCROLLABLE_SAME_DOC |
nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN));
bool useFixedPosition = nsLayoutUtils::IsFixedPosFrameInDisplayPort(this);
nsDisplayListBuilder::AutoBuildingDisplayList
buildingDisplayList(aBuilder, this, dirtyRect, true);
+ // Depending on the effects that are applied to this frame, we can create
+ // multiple container display items and wrap them around our contents.
+ // This enum lists all the potential container display items, in the order
+ // outside to inside.
+ enum class ContainerItemType : uint8_t {
+ eNone = 0,
+ eOwnLayerIfNeeded,
+ eBlendMode,
+ eFixedPosition,
+ eStickyPosition,
+ eOwnLayerForTransformWithRoundedClip,
+ ePerspective,
+ eTransform,
+ eSeparatorTransforms,
+ eOpacity,
+ eSVGEffects,
+ eBlendContainer
+ };
+
DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ // If there is a current clip, then depending on the container items we
+ // create, different things can happen to it. Some container items simply
+ // propagate the clip to their children and aren't clipped themselves.
+ // But other container items, especially those that establish a different
+ // geometry for their contents (e.g. transforms), capture the clip on
+ // themselves and unset the clip for their contents. If we create more than
+ // one of those container items, the clip will be captured on the outermost
+ // one and the inner container items will be unclipped.
+ ContainerItemType clipCapturedBy = ContainerItemType::eNone;
+ if (useFixedPosition) {
+ clipCapturedBy = ContainerItemType::eFixedPosition;
+ } else if (useStickyPosition) {
+ clipCapturedBy = ContainerItemType::eStickyPosition;
+ } else if (isTransformed) {
+ if ((hasPerspective || extend3DContext) && clipState.SavedStateHasRoundedCorners()) {
+ // If we're creating an nsDisplayTransform item that is going to combine
+ // its transform with its children (preserve-3d or perspective), then we
+ // can't have an intermediate surface. Mask layers force an intermediate
+ // surface, so if we're going to need both then create a separate
+ // wrapping layer for the mask.
+ clipCapturedBy = ContainerItemType::eOwnLayerForTransformWithRoundedClip;
+ } else if (hasPerspective) {
+ clipCapturedBy = ContainerItemType::ePerspective;
+ } else {
+ clipCapturedBy = ContainerItemType::eTransform;
+ }
+ } else if (usingSVGEffects) {
+ clipCapturedBy = ContainerItemType::eSVGEffects;
+ }
+
bool clearClip = false;
- if (isTransformed || usingSVGEffects || useFixedPosition || useStickyPosition) {
+ if (clipCapturedBy != ContainerItemType::eNone) {
// We don't need to pass ancestor clipping down to our children;
// everything goes inside a display item's child list, and the display
// item itself will be clipped.
// For transforms we also need to clear ancestor clipping because it's
// relative to the wrong display item reference frame anyway.
clearClip = true;
}
@@ -2288,25 +2313,26 @@ nsIFrame::BuildDisplayListForStackingCon
DisplayListClipState::AutoSaveRestore nestedClipState(aBuilder);
nsDisplayListBuilder::AutoInTransformSetter
inTransformSetter(aBuilder, inTransform);
nsDisplayListBuilder::AutoSaveRestorePerspectiveIndex
perspectiveIndex(aBuilder, this);
CheckForApzAwareEventHandlers(aBuilder, this);
- nsRect clipPropClip;
- if (ApplyClipPropClipping(aBuilder, this, disp, effects, &clipPropClip,
- nestedClipState)) {
- dirtyRect.IntersectRect(dirtyRect, clipPropClip);
- }
-
- // Extend3DContext() also guarantees that applyAbsPosClipping and usingSVGEffects are false
+ Maybe<nsRect> clipPropClip = GetClipPropClipRect(disp, effects, GetSize());
+ if (clipPropClip) {
+ dirtyRect.IntersectRect(dirtyRect, *clipPropClip);
+ nestedClipState.ClipContentDescendants(
+ *clipPropClip + aBuilder->ToReferenceFrame(this));
+ }
+
+ // extend3DContext also guarantees that applyAbsPosClipping and usingSVGEffects are false
// We only modify the preserve-3d rect if we are the top of a preserve-3d heirarchy
- if (Extend3DContext()) {
+ if (extend3DContext) {
// Mark these first so MarkAbsoluteFramesForDisplayList knows if we are
// going to be forced to descend into frames.
aBuilder->MarkPreserve3DFramesForDisplayList(this);
}
MarkAbsoluteFramesForDisplayList(aBuilder, dirtyRect);
nsDisplayLayerEventRegions* eventRegions = nullptr;
@@ -2404,28 +2430,25 @@ nsIFrame::BuildDisplayListForStackingCon
if (aBuilder->ContainsBlendMode()) {
DisplayListClipState::AutoSaveRestore blendContainerClipState(aBuilder);
blendContainerClipState.Clear();
resultList.AppendNewToTop(
nsDisplayBlendContainer::CreateForMixBlendMode(aBuilder, this, &resultList,
containerItemScrollClip));
}
- if (!isTransformed && !useFixedPosition && !useStickyPosition) {
- // Restore saved clip state now so that any display items we create below
- // are clipped properly.
- clipState.ExitStackingContextContents(&containerItemScrollClip);
- }
-
/* If there are any SVG effects, wrap the list up in an SVG effects item
* (which also handles CSS group opacity). Note that we create an SVG effects
* item even if resultList is empty, since a filter can produce graphical
* output even if the element being filtered wouldn't otherwise do so.
*/
if (usingSVGEffects) {
+ if (clipCapturedBy == ContainerItemType::eSVGEffects) {
+ clipState.ExitStackingContextContents(&containerItemScrollClip);
+ }
// Revert to the post-filter dirty rect.
buildingDisplayList.SetDirtyRect(dirtyRectOutsideSVGEffects);
/* List now emptied, so add the new list to the top. */
resultList.AppendNewToTop(
new (aBuilder) nsDisplaySVGEffects(aBuilder, this, &resultList, useOpacity));
// Also add the hoisted scroll info items. We need those for APZ scrolling
// because nsDisplaySVGEffects items can't build active layers.
aBuilder->ExitSVGEffectsContents();
@@ -2452,17 +2475,17 @@ nsIFrame::BuildDisplayListForStackingCon
*
* For the preserve-3d case we want to individually wrap every child in the list with
* a separate nsDisplayTransform instead. When the child is already an nsDisplayTransform,
* we can skip this step, as the computed transform will already include our own.
*
* We also traverse into sublists created by nsDisplayWrapList, so that we find all the
* correct children.
*/
- if (isTransformed && !resultList.IsEmpty() && Extend3DContext()) {
+ if (isTransformed && !resultList.IsEmpty() && extend3DContext) {
// Install dummy nsDisplayTransform as a leaf containing
// descendants not participating this 3D rendering context.
nsDisplayList nonparticipants;
nsDisplayList participants;
int index = 1;
while (nsDisplayItem* item = resultList.RemoveBottom()) {
if (ItemParticipatesIn3DContext(this, item) && !item->GetClip().HasClip()) {
@@ -2481,29 +2504,19 @@ nsIFrame::BuildDisplayListForStackingCon
nonparticipants.AppendToTop(item);
}
}
WrapSeparatorTransform(aBuilder, this, dirtyRect,
&nonparticipants, &participants, index++);
resultList.AppendToTop(&participants);
}
- /* If we're creating an nsDisplayTransform item that is going to combine its transform
- * with its children (preserve-3d or perspective), then we can't have an intermediate
- * surface. Mask layers force an intermediate surface, so if we're going to need both
- * then create a separate wrapping layer for the mask.
- */
- bool needsLayerForMask = isTransformed && (Extend3DContext() || HasPerspective()) &&
- clipState.SavedStateHasRoundedCorners();
- NS_ASSERTION(!Combines3DTransformWithAncestors() || !clipState.SavedStateHasRoundedCorners(),
- "Can't support mask layers on intermediate preserve-3d frames");
-
if (isTransformed && !resultList.IsEmpty()) {
- // Restore clip state now so nsDisplayTransform is clipped properly.
- if (!HasPerspective() && !useFixedPosition && !useStickyPosition && !needsLayerForMask) {
+ if (clipCapturedBy == ContainerItemType::eTransform) {
+ // Restore clip state now so nsDisplayTransform is clipped properly.
clipState.ExitStackingContextContents(&containerItemScrollClip);
}
// Revert to the dirtyrect coming in from the parent, without our transform
// taken into account.
buildingDisplayList.SetDirtyRect(dirtyRectOutsideTransform);
// Revert to the outer reference frame and offset because all display
// items we create from now on are outside the transform.
nsPoint toOuterReferenceFrame;
@@ -2521,44 +2534,47 @@ nsIFrame::BuildDisplayListForStackingCon
dirtyRectOutsideSVGEffects.Contains(GetVisualOverflowRectRelativeToSelf());
nsDisplayTransform *transformItem =
new (aBuilder) nsDisplayTransform(aBuilder, this,
&resultList, dirtyRect, 0,
isFullyVisible);
resultList.AppendNewToTop(transformItem);
}
- if (HasPerspective()) {
- if (!useFixedPosition && !useStickyPosition && !needsLayerForMask) {
+ if (hasPerspective) {
+ if (clipCapturedBy == ContainerItemType::ePerspective) {
clipState.ExitStackingContextContents(&containerItemScrollClip);
}
resultList.AppendNewToTop(
new (aBuilder) nsDisplayPerspective(
aBuilder, this,
GetContainingBlock()->GetContent()->GetPrimaryFrame(), &resultList));
}
}
- if (useFixedPosition || useStickyPosition || needsLayerForMask) {
+ if (clipCapturedBy == ContainerItemType::eOwnLayerForTransformWithRoundedClip) {
clipState.ExitStackingContextContents(&containerItemScrollClip);
- }
-
- if (needsLayerForMask) {
resultList.AppendNewToTop(
new (aBuilder) nsDisplayOwnLayer(aBuilder, this, &resultList, 0,
mozilla::layers::FrameMetrics::NULL_SCROLL_ID,
0.0f, /* aForceActive = */ false));
}
/* If we have sticky positioning, wrap it in a sticky position item.
*/
if (useFixedPosition) {
+ if (clipCapturedBy == ContainerItemType::eFixedPosition) {
+ clipState.ExitStackingContextContents(&containerItemScrollClip);
+ }
resultList.AppendNewToTop(
new (aBuilder) nsDisplayFixedPosition(aBuilder, this, &resultList));
} else if (useStickyPosition) {
+ if (clipCapturedBy == ContainerItemType::eStickyPosition) {
+ clipState.ExitStackingContextContents(&containerItemScrollClip);
+ }
resultList.AppendNewToTop(
new (aBuilder) nsDisplayStickyPosition(aBuilder, this, &resultList));
}
/* If there's blending, wrap up the list in a blend-mode item. Note
* that opacity can be applied before blending as the blend color is
* not affected by foreground opacity (only background alpha).
*/
@@ -2821,22 +2837,22 @@ nsIFrame::BuildDisplayListForChild(nsDis
aBuilder->SetContainsBlendMode(true);
}
// True stacking context.
// For stacking contexts, BuildDisplayListForStackingContext handles
// clipping and MarkAbsoluteFramesForDisplayList.
child->BuildDisplayListForStackingContext(aBuilder, dirty, &list);
aBuilder->DisplayCaret(child, dirty, &list);
} else {
- nsRect clipRect;
- if (ApplyClipPropClipping(aBuilder, child, disp, effects, &clipRect,
- clipState)) {
- // clipRect is in builder-reference-frame coordinates,
- // dirty/clippedDirtyRect are in child coordinates
- dirty.IntersectRect(dirty, clipRect);
+ Maybe<nsRect> clipPropClip =
+ child->GetClipPropClipRect(disp, effects, child->GetSize());
+ if (clipPropClip) {
+ dirty.IntersectRect(dirty, *clipPropClip);
+ clipState.ClipContentDescendants(
+ *clipPropClip + aBuilder->ToReferenceFrame(child));
}
child->MarkAbsoluteFramesForDisplayList(aBuilder, dirty);
if (aBuilder->IsBuildingLayerEventRegions()) {
// If this frame has a different animated geometry root than its parent,
// make sure we accumulate event regions for its layer.
if (buildingForChild.IsAnimatedGeometryRoot()) {
@@ -7891,20 +7907,18 @@ UnionBorderBoxes(nsIFrame* aFrame, bool
if (nsFrame::ShouldApplyOverflowClipping(aFrame, disp) ||
fType == nsGkAtoms::scrollFrame ||
fType == nsGkAtoms::listControlFrame ||
fType == nsGkAtoms::svgOuterSVGFrame) {
return u;
}
const nsStyleEffects* effects = aFrame->StyleEffects();
- nsRect clipPropClipRect;
- bool hasClipPropClip =
- aFrame->GetClipPropClipRect(disp, effects, &clipPropClipRect,
- bounds.Size());
+ Maybe<nsRect> clipPropClipRect =
+ aFrame->GetClipPropClipRect(disp, effects, bounds.Size());
// Iterate over all children except pop-ups.
const nsIFrame::ChildListIDs skip(nsIFrame::kPopupList |
nsIFrame::kSelectPopupList);
for (nsIFrame::ChildListIterator childLists(aFrame);
!childLists.IsDone(); childLists.Next()) {
if (skip.Contains(childLists.CurrentID())) {
continue;
@@ -7925,19 +7939,19 @@ UnionBorderBoxes(nsIFrame* aFrame, bool
bool validRect = true;
nsRect childRect = UnionBorderBoxes(child, true, validRect) +
child->GetPosition();
if (!validRect) {
continue;
}
- if (hasClipPropClip) {
+ if (clipPropClipRect) {
// Intersect with the clip before transforming.
- childRect.IntersectRect(childRect, clipPropClipRect);
+ childRect.IntersectRect(childRect, *clipPropClipRect);
}
// Note that we transform each child separately according to
// aFrame's transform, and then union, which gives a different
// (smaller) result from unioning and then transforming the
// union. This doesn't match the way we handle overflow areas
// with 2-D transforms, though it does match the way we handle
// overflow areas in preserve-3d 3-D scenes.
@@ -8147,23 +8161,22 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
ComputeAndIncludeOutlineArea(this, aOverflowAreas, aNewSize);
// Nothing in here should affect scrollable overflow.
aOverflowAreas.VisualOverflow() =
ComputeEffectsRect(this, aOverflowAreas.VisualOverflow(), aNewSize);
// Absolute position clipping
const nsStyleEffects* effects = StyleEffects();
- nsRect clipPropClipRect;
- bool hasClipPropClip =
- GetClipPropClipRect(disp, effects, &clipPropClipRect, aNewSize);
- if (hasClipPropClip) {
+ Maybe<nsRect> clipPropClipRect =
+ GetClipPropClipRect(disp, effects, aNewSize);
+ if (clipPropClipRect) {
NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
nsRect& o = aOverflowAreas.Overflow(otype);
- o.IntersectRect(o, clipPropClipRect);
+ o.IntersectRect(o, *clipPropClipRect);
}
}
/* If we're transformed, transform the overflow rect by the current transformation. */
bool hasTransform = IsTransformed();
nsSize oldSize = mRect.Size();
bool sizeChanged = ((aOldSize ? *aOldSize : oldSize) != aNewSize);
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -722,23 +722,30 @@ nsHTMLScrollFrame::ReflowContents(Scroll
// This just happens sometimes.
TryLayout(aState, &kidDesiredSize,
aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN,
aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN,
true);
}
void
-nsHTMLScrollFrame::PlaceScrollArea(const ScrollReflowInput& aState,
+nsHTMLScrollFrame::PlaceScrollArea(ScrollReflowInput& aState,
const nsPoint& aScrollPosition)
{
nsIFrame *scrolledFrame = mHelper.mScrolledFrame;
// Set the x,y of the scrolled frame to the correct value
scrolledFrame->SetPosition(mHelper.mScrollPort.TopLeft() - aScrollPosition);
+ // Recompute our scrollable overflow, taking perspective children into
+ // account. Note that this only recomputes the overflow areas stored on the
+ // helper (which are used to compute scrollable length and scrollbar thumb
+ // sizes) but not the overflow areas stored on the frame. This seems to work
+ // for now, but it's possible that we may need to update both in the future.
+ AdjustForPerspective(aState.mContentsOverflowAreas.ScrollableOverflow());
+
nsRect scrolledArea;
// Preserve the width or height of empty rects
nsSize portSize = mHelper.mScrollPort.Size();
nsRect scrolledRect =
mHelper.GetUnsnappedScrolledRectInternal(aState.mContentsOverflowAreas.ScrollableOverflow(),
portSize);
scrolledArea.UnionRectEdges(scrolledRect,
nsRect(nsPoint(0,0), portSize));
@@ -831,16 +838,150 @@ GetBrowserRoot(nsIContent* aContent)
frameElement->NodeInfo()->Equals(nsGkAtoms::browser, kNameSpaceID_XUL))
return frameElement;
}
}
return nullptr;
}
+// When we have perspective set on the outer scroll frame, and transformed
+// children (possibly with preserve-3d) then the effective transform on the
+// child depends on the offset to the scroll frame, which changes as we scroll.
+// This perspective transform can cause the element to move relative to the
+// scrolled inner frame, which would cause the scrollable length changes during
+// scrolling if we didn't account for it. Since we don't want scrollHeight/Width
+// and the size of scrollbar thumbs to change during scrolling, we compute the
+// scrollable overflow by determining the scroll position at which the child
+// becomes completely visible within the scrollport rather than using the union
+// of the overflow areas at their current position.
+void
+GetScrollableOverflowForPerspective(nsIFrame* aScrolledFrame,
+ nsIFrame* aCurrentFrame,
+ const nsRect aScrollPort,
+ nsPoint aOffset,
+ nsRect& aScrolledFrameOverflowArea)
+{
+ // Iterate over all children except pop-ups.
+ FrameChildListIDs skip = nsIFrame::kSelectPopupList | nsIFrame::kPopupList;
+ for (nsIFrame::ChildListIterator childLists(aCurrentFrame);
+ !childLists.IsDone(); childLists.Next()) {
+ if (skip.Contains(childLists.CurrentID())) {
+ continue;
+ }
+
+ for (nsIFrame* child : childLists.CurrentList()) {
+ nsPoint offset = aOffset;
+
+ // When we reach a direct child of the scroll, then we record the offset
+ // to convert from that frame's coordinate into the scroll frame's
+ // coordinates. Preserve-3d descendant frames use the same offset as their
+ // ancestors, since TransformRect already converts us into the coordinate
+ // space of the preserve-3d root.
+ if (aScrolledFrame == aCurrentFrame) {
+ offset = child->GetPosition();
+ }
+
+ if (child->Extend3DContext()) {
+ // If we're a preserve-3d frame, then recurse and include our
+ // descendants since overflow of preserve-3d frames is only included
+ // in the post-transform overflow area of the preserve-3d root frame.
+ GetScrollableOverflowForPerspective(aScrolledFrame, child, aScrollPort,
+ offset, aScrolledFrameOverflowArea);
+ }
+
+ // If we're transformed, then we want to consider the possibility that
+ // this frame might move relative to the scrolled frame when scrolling.
+ // For preserve-3d, leaf frames have correct overflow rects relative to
+ // themselves. preserve-3d 'nodes' (intermediate frames and the root) have
+ // only their untransformed children included in their overflow relative
+ // to self, which is what we want to include here.
+ if (child->IsTransformed()) {
+ // Compute the overflow rect for this leaf transform frame in the
+ // coordinate space of the scrolled frame.
+ nsPoint scrollPos = aScrolledFrame->GetPosition();
+ nsRect preScroll = nsDisplayTransform::TransformRect(
+ child->GetScrollableOverflowRectRelativeToSelf(), child);
+
+ // Temporarily override the scroll position of the scrolled frame by
+ // 10 CSS pixels, and then recompute what the overflow rect would be.
+ // This scroll position may not be valid, but that shouldn't matter
+ // for our calculations.
+ aScrolledFrame->SetPosition(scrollPos + nsPoint(600, 600));
+ nsRect postScroll = nsDisplayTransform::TransformRect(
+ child->GetScrollableOverflowRectRelativeToSelf(), child);
+ aScrolledFrame->SetPosition(scrollPos);
+
+ // Compute how many app units the overflow rects moves by when we adjust
+ // the scroll position by 1 app unit.
+ double rightDelta =
+ (postScroll.XMost() - preScroll.XMost() + 600.0) / 600.0;
+ double bottomDelta =
+ (postScroll.YMost() - preScroll.YMost() + 600.0) / 600.0;
+
+ // We can't ever have negative scrolling.
+ NS_ASSERTION(rightDelta > 0.0f && bottomDelta > 0.0f,
+ "Scrolling can't be reversed!");
+
+ // Move preScroll into the coordinate space of the scrollport.
+ preScroll += offset + scrollPos;
+
+ // For each of the four edges of preScroll, figure out how far they
+ // extend beyond the scrollport. Ignore negative values since that means
+ // that side is already scrolled in to view and we don't need to add
+ // overflow to account for it.
+ nsMargin overhang(std::max(0, aScrollPort.Y() - preScroll.Y()),
+ std::max(0, preScroll.XMost() - aScrollPort.XMost()),
+ std::max(0, preScroll.YMost() - aScrollPort.YMost()),
+ std::max(0, aScrollPort.X() - preScroll.X()));
+
+ // Scale according to rightDelta/bottomDelta to adjust for the different
+ // scroll rates.
+ overhang.top /= bottomDelta;
+ overhang.right /= rightDelta;
+ overhang.bottom /= bottomDelta;
+ overhang.left /= rightDelta;
+
+ // Take the minimum overflow rect that would allow the current scroll
+ // position, using the size of the scroll port and offset by the
+ // inverse of the scroll position.
+ nsRect overflow(0, 0, aScrollPort.width, aScrollPort.height);
+
+ // Expand it by our margins to get an overflow rect that would allow all
+ // edges of our transformed content to be scrolled into view.
+ overflow.Inflate(overhang);
+
+ // Merge it with the combined overflow
+ aScrolledFrameOverflowArea.UnionRect(aScrolledFrameOverflowArea,
+ overflow);
+ } else if (aCurrentFrame == aScrolledFrame) {
+ aScrolledFrameOverflowArea.UnionRect(
+ aScrolledFrameOverflowArea,
+ child->GetScrollableOverflowRectRelativeToParent());
+ }
+ }
+ }
+}
+
+void
+nsHTMLScrollFrame::AdjustForPerspective(nsRect& aScrollableOverflow)
+{
+ // If we have perspective that is being applied to our children, then
+ // the effective transform on the child depends on the relative position
+ // of the child to us and changes during scrolling.
+ if (!ChildrenHavePerspective()) {
+ return;
+ }
+ aScrollableOverflow.SetEmpty();
+ GetScrollableOverflowForPerspective(mHelper.mScrolledFrame,
+ mHelper.mScrolledFrame,
+ mHelper.mScrollPort,
+ nsPoint(), aScrollableOverflow);
+}
+
void
nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus)
{
MarkInReflow();
DO_GLOBAL_REFLOW_COUNT("nsHTMLScrollFrame");
@@ -888,16 +1029,26 @@ nsHTMLScrollFrame::Reflow(nsPresContext*
mHelper.mScrolledFrame->GetScrollableOverflowRectRelativeToParent();
nsPoint oldScrollPosition = mHelper.GetScrollPosition();
state.mComputedBorder = aReflowInput.ComputedPhysicalBorderPadding() -
aReflowInput.ComputedPhysicalPadding();
ReflowContents(&state, aDesiredSize);
+ aDesiredSize.Width() = state.mInsideBorderSize.width +
+ state.mComputedBorder.LeftRight();
+ aDesiredSize.Height() = state.mInsideBorderSize.height +
+ state.mComputedBorder.TopBottom();
+
+ // Set the size of the frame now since computing the perspective-correct
+ // overflow (within PlaceScrollArea) can rely on it.
+ SetSize(aDesiredSize.GetWritingMode(),
+ aDesiredSize.Size(aDesiredSize.GetWritingMode()));
+
// Restore the old scroll position, for now, even if that's not valid anymore
// because we changed size. We'll fix it up in a post-reflow callback, because
// our current size may only be temporary (e.g. we're compute XUL desired sizes).
PlaceScrollArea(state, oldScrollPosition);
if (!mHelper.mPostedReflowCallback) {
// Make sure we'll try scrolling to restored position
PresContext()->PresShell()->PostReflowCallback(&mHelper);
mHelper.mPostedReflowCallback = true;
@@ -927,21 +1078,16 @@ nsHTMLScrollFrame::Reflow(nsPresContext*
state.mInsideBorderSize);
mHelper.LayoutScrollbars(state.mBoxState, insideBorderArea,
oldScrollAreaBounds);
} else {
mHelper.mSkippedScrollbarLayout = true;
}
}
- aDesiredSize.Width() = state.mInsideBorderSize.width +
- state.mComputedBorder.LeftRight();
- aDesiredSize.Height() = state.mInsideBorderSize.height +
- state.mComputedBorder.TopBottom();
-
aDesiredSize.SetOverflowAreasToDesiredBounds();
if (mHelper.IsIgnoringViewportClipping()) {
aDesiredSize.mOverflowAreas.UnionWith(
state.mContentsOverflowAreas + mHelper.mScrolledFrame->GetPosition());
}
mHelper.UpdateSticky();
FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus);
@@ -2739,17 +2885,28 @@ ScrollFrameHelper::ScrollToImpl(nsPoint
if (needFrameVisibilityUpdate) {
presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow();
}
}
if (mOuter->ChildrenHavePerspective()) {
// The overflow areas of descendants may depend on the scroll position,
// so ensure they get updated.
+
+ // First we recompute the overflow areas of the transformed children
+ // that use the perspective. FinishAndStoreOverflow only calls this
+ // if the size changes, so we need to do it manually.
mOuter->RecomputePerspectiveChildrenOverflow(mOuter);
+
+ // Update the overflow for the scrolled frame to take any changes from the
+ // children into account.
+ mScrolledFrame->UpdateOverflow();
+
+ // Update the overflow for the outer so that we recompute scrollbars.
+ mOuter->UpdateOverflow();
}
ScheduleSyntheticMouseMove();
{ // scope the AutoScrollbarRepaintSuppression
AutoScrollbarRepaintSuppression repaintSuppression(this, !schedulePaint);
nsWeakFrame weakFrame(mOuter);
UpdateScrollbarPosition();
@@ -4696,16 +4853,20 @@ nsXULScrollFrame::LayoutScrollArea(nsBox
nsSize minSize = mHelper.mScrolledFrame->GetXULMinSize(aState);
if (minSize.height > childRect.height)
childRect.height = minSize.height;
if (minSize.width > childRect.width)
childRect.width = minSize.width;
+ // TODO: Handle transformed children that inherit perspective
+ // from this frame. See AdjustForPerspective for how we handle
+ // this for HTML scroll frames.
+
aState.SetLayoutFlags(flags);
ClampAndSetBounds(aState, childRect, aScrollPosition);
mHelper.mScrolledFrame->XULLayout(aState);
childRect = mHelper.mScrolledFrame->GetRect();
if (childRect.width < mHelper.mScrollPort.width ||
childRect.height < mHelper.mScrollPort.height)
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -682,17 +682,17 @@ public:
bool ScrolledContentDependsOnHeight(ScrollReflowInput* aState);
void ReflowScrolledFrame(ScrollReflowInput* aState,
bool aAssumeHScroll,
bool aAssumeVScroll,
ReflowOutput* aMetrics,
bool aFirstPass);
void ReflowContents(ScrollReflowInput* aState,
const ReflowOutput& aDesiredSize);
- void PlaceScrollArea(const ScrollReflowInput& aState,
+ void PlaceScrollArea(ScrollReflowInput& aState,
const nsPoint& aScrollPosition);
nscoord GetIntrinsicVScrollbarWidth(nsRenderingContext *aRenderingContext);
virtual bool GetBorderRadii(const nsSize& aFrameSize, const nsSize& aBorderArea,
Sides aSkipSides, nscoord aRadii[8]) const override {
return mHelper.GetBorderRadii(aFrameSize, aBorderArea, aSkipSides, aRadii);
}
@@ -705,16 +705,21 @@ public:
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) override;
virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override {
return mHelper.ComputeCustomOverflow(aOverflowAreas);
}
+ // Recomputes the scrollable overflow area we store in the helper to take children
+ // that are affected by perpsective set on the outer frame and scroll at different
+ // rates.
+ void AdjustForPerspective(nsRect& aScrollableOverflow);
+
// Called to set the child frames. We typically have three: the scroll area,
// the vertical scrollbar, and the horizontal scrollbar.
virtual void SetInitialChildList(ChildListID aListID,
nsFrameList& aChildList) override;
virtual void AppendFrames(ChildListID aListID,
nsFrameList& aFrameList) override;
virtual void InsertFrames(ChildListID aListID,
nsIFrame* aPrevFrame,
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -2873,30 +2873,28 @@ public:
/**
* Return true if and only if this frame obeys visibility:hidden.
* if it does not, then nsContainerFrame will hide its view even though
* this means children can't be made visible again.
*/
virtual bool SupportsVisibilityHidden() { return true; }
/**
- * Returns true if the frame has a valid clip rect set via the 'clip'
- * property, and the 'clip' property applies to this frame. The 'clip'
- * property applies to HTML frames if they are absolutely positioned. The
- * 'clip' property applies to SVG frames regardless of the value of the
- * 'position' property.
+ * Returns the clip rect set via the 'clip' property, if the 'clip' property
+ * applies to this frame; otherwise returns Nothing(). The 'clip' property
+ * applies to HTML frames if they are absolutely positioned. The 'clip'
+ * property applies to SVG frames regardless of the value of the 'position'
+ * property.
*
- * If this method returns true, then we also set aRect to the computed clip
- * rect, with coordinates relative to this frame's origin. aRect must not be
- * null!
+ * The coordinates of the returned rectangle are relative to this frame's
+ * origin.
*/
- bool GetClipPropClipRect(const nsStyleDisplay* aDisp,
- const nsStyleEffects* aEffects,
- nsRect* aRect,
- const nsSize& aSize) const;
+ Maybe<nsRect> GetClipPropClipRect(const nsStyleDisplay* aDisp,
+ const nsStyleEffects* aEffects,
+ const nsSize& aSize) const;
/**
* Check if this frame is focusable and in the current tab order.
* Tabbable is indicated by a nonnegative tabindex & is a subset of focusable.
* For example, only the selected radio button in a group is in the
* tab order, unless the radio group has no selection in which case
* all of the visible, non-disabled radio buttons in the group are
* in the tab order. On the other hand, all of the visible, non-disabled
--- a/layout/generic/test/mochitest.ini
+++ b/layout/generic/test/mochitest.ini
@@ -96,16 +96,17 @@ skip-if = buildapp == 'b2g' #Bug 931116,
skip-if = buildapp == 'b2g' # b2g(Target should not have scrolled - got 114.10000610351562, expected 115.39999389648438) b2g-debug(Target should not have scrolled - got 114.10000610351562, expected 115.39999389648438) b2g-desktop(Target should not have scrolled - got 114.10000610351562, expected 115.39999389648438)
[test_bug831780.html]
[test_bug841361.html]
[test_bug904810.html]
[test_bug938772.html]
[test_bug970363.html]
[test_bug1062406.html]
[test_bug1174521.html]
+[test_bug1198135.html]
[test_contained_plugin_transplant.html]
skip-if = os=='win'
[test_image_selection.html]
[test_image_selection_2.html]
[test_invalidate_during_plugin_paint.html]
skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(plugins not supported) b2g-debug(plugins not supported) b2g-desktop(plugins not supported)
[test_intrinsic_size_on_loading.html]
[test_movement_by_characters.html]
new file mode 100644
--- /dev/null
+++ b/layout/generic/test/test_bug1198135.html
@@ -0,0 +1,84 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1198135
+-->
+<html><head>
+<title>Test for Bug 1198135</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1198135">Mozilla Bug 1198135</a>
+<style>
+.example {
+ perspective: 100px;
+ height: 300px;
+ width: 300px;
+ overflow-x: hidden;
+ overflow-y: auto;
+ border: 1px solid blue;
+}
+
+.example__group {
+ position: relative;
+ transform-style: preserve-3d;
+ height: 300px;
+ top: 0;
+}
+.example__layer {
+ position: absolute;
+ top:0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+.layer--a {
+ box-shadow: inset 0 0 0 1px red;
+}
+.layer--b {
+ box-shadow: inset 0 0 0 1px blue;
+ transform: translateZ(50px) scale(.45);
+}
+.layer--c {
+ box-shadow: inset 0 0 0 1px green;
+}
+.layer--d {
+ box-shadow: inset 00px 0 0px 1px orange;
+ transform: translateZ(50px) scale(.45);
+}
+.layer--e {
+ box-shadow: inset 00px 0 0px 1px orange;
+ transform: translateZ(50px) scale(.45) translateY(300px);
+}
+</style>
+</head>
+
+<body>
+
+<div class="example" id="first">
+ <div class="example__group">
+ <div class="example__layer layer--a"></div>
+ <div class="example__layer layer--b"></div>
+ </div>
+ <div class="example__group">
+ <div class="example__layer layer--c"></div>
+ <div class="example__layer layer--d"></div>
+ </div>
+</div>
+
+<div class="example" id="second">
+ <div class="example__group">
+ <div class="example__layer layer--a"></div>
+ <div class="example__layer layer--b"></div>
+ </div>
+ <div class="example__group">
+ <div class="example__layer layer--c"></div>
+ <div class="example__layer layer--e"></div>
+ </div>
+</div>
+
+<script>
+ is(document.getElementById("first").scrollHeight, 600, "Scroll height should be computed correctly");
+ // The true height is 727.5 and we don't always snap the same direction.
+ isfuzzy(document.getElementById("second").scrollHeight, 728, 1, "Scroll height should be computed correctly");
+</script>
+
+</body></html>
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -6026,47 +6026,48 @@ CSSParserImpl::ParsePseudoSelector(int32
if (parsingPseudoElement &&
!isPseudoElement &&
!isAnonBox) {
REPORT_UNEXPECTED_TOKEN(PEPseudoSelNotPE);
UngetToken();
return eSelectorParsingStatus_Error;
}
+ if (aSelector.IsPseudoElement()) {
+ CSSPseudoElementType type = aSelector.PseudoType();
+ if (!nsCSSPseudoElements::PseudoElementSupportsUserActionState(type)) {
+ // We only allow user action pseudo-classes on certain pseudo-elements.
+ REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ if (!isPseudoClass || !pseudoClassIsUserAction) {
+ // CSS 4 Selectors says that pseudo-elements can only be followed by
+ // a user action pseudo-class.
+ REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction);
+ UngetToken();
+ return eSelectorParsingStatus_Error;
+ }
+ }
+
if (!parsingPseudoElement &&
CSSPseudoClassType::negation == pseudoClassType) {
if (aIsNegated) { // :not() can't be itself negated
REPORT_UNEXPECTED_TOKEN(PEPseudoSelDoubleNot);
UngetToken();
return eSelectorParsingStatus_Error;
}
// CSS 3 Negation pseudo-class takes one simple selector as argument
nsSelectorParsingStatus parsingStatus =
ParseNegatedSimpleSelector(aDataMask, aSelector);
if (eSelectorParsingStatus_Continue != parsingStatus) {
return parsingStatus;
}
}
else if (!parsingPseudoElement && isPseudoClass) {
- if (aSelector.IsPseudoElement()) {
- CSSPseudoElementType type = aSelector.PseudoType();
- if (!nsCSSPseudoElements::PseudoElementSupportsUserActionState(type)) {
- // We only allow user action pseudo-classes on certain pseudo-elements.
- REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC);
- UngetToken();
- return eSelectorParsingStatus_Error;
- }
- if (!pseudoClassIsUserAction) {
- // CSS 4 Selectors says that pseudo-elements can only be followed by
- // a user action pseudo-class.
- REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction);
- UngetToken();
- return eSelectorParsingStatus_Error;
- }
- }
aDataMask |= SEL_MASK_PCLASS;
if (eCSSToken_Function == mToken.mType) {
nsSelectorParsingStatus parsingStatus;
if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) {
parsingStatus =
ParsePseudoClassWithIdentArg(aSelector, pseudoClassType);
}
else if (nsCSSPseudoClasses::HasNthPairArg(pseudoClassType)) {
@@ -6498,23 +6499,17 @@ CSSParserImpl::ParseSelector(nsCSSSelect
nsAutoPtr<nsAtomList> pseudoElementArgs;
CSSPseudoElementType pseudoElementType = CSSPseudoElementType::NotPseudo;
int32_t dataMask = 0;
nsSelectorParsingStatus parsingStatus =
ParseTypeOrUniversalSelector(dataMask, *selector, false);
while (parsingStatus == eSelectorParsingStatus_Continue) {
- if (eCSSToken_ID == mToken.mType) { // #id
- parsingStatus = ParseIDSelector(dataMask, *selector);
- }
- else if (mToken.IsSymbol('.')) { // .class
- parsingStatus = ParseClassSelector(dataMask, *selector);
- }
- else if (mToken.IsSymbol(':')) { // :pseudo
+ if (mToken.IsSymbol(':')) { // :pseudo
parsingStatus = ParsePseudoSelector(dataMask, *selector, false,
getter_AddRefs(pseudoElement),
getter_Transfers(pseudoElementArgs),
&pseudoElementType);
if (pseudoElement &&
pseudoElementType != CSSPseudoElementType::AnonBox) {
// Pseudo-elements other than anonymous boxes are represented with
// a special ':' combinator.
@@ -6522,16 +6517,27 @@ CSSParserImpl::ParseSelector(nsCSSSelect
aList->mWeight += selector->CalcWeight();
selector = aList->AddSelector(':');
selector->mLowercaseTag.swap(pseudoElement);
selector->mClassList = pseudoElementArgs.forget();
selector->SetPseudoType(pseudoElementType);
}
+ } else if (selector->IsPseudoElement()) {
+ // Once we parsed a pseudo-element, we can only parse
+ // pseudo-classes (and only a limited set, which
+ // ParsePseudoSelector knows how to handle).
+ parsingStatus = eSelectorParsingStatus_Done;
+ UngetToken();
+ break;
+ } else if (eCSSToken_ID == mToken.mType) { // #id
+ parsingStatus = ParseIDSelector(dataMask, *selector);
+ } else if (mToken.IsSymbol('.')) { // .class
+ parsingStatus = ParseClassSelector(dataMask, *selector);
}
else if (mToken.IsSymbol('[')) { // [attribute
parsingStatus = ParseAttributeSelector(dataMask, *selector);
if (eSelectorParsingStatus_Error == parsingStatus) {
SkipUntil(']');
}
}
else { // not a selector token, we're done
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -1259,16 +1259,42 @@ nsStyleContext::CalcStyleDifferenceInter
}
}
if (change) {
hint |= nsChangeHint_RepaintFrame;
}
}
+ if (hint & nsChangeHint_UpdateContainingBlock) {
+ // If a struct returned nsChangeHint_UpdateContainingBlock, that
+ // means that one property's influence on whether we're a containing
+ // block for abs-pos or fixed-pos elements has changed. However, we
+ // only need to return the hint if the overall computation of
+ // whether we establish a containing block has changed.
+
+ // This depends on data in nsStyleDisplay and nsStyleEffects, so we
+ // do it here.
+
+ // Note that it's perhaps good for this test to be last because it
+ // doesn't use Peek* functions to get the structs on the old
+ // context. But this isn't a big concern because these struct
+ // getters should be called during frame construction anyway.
+ if (StyleDisplay()->IsAbsPosContainingBlockForAppropriateFrame(this) ==
+ aNewContext->StyleDisplay()->
+ IsAbsPosContainingBlockForAppropriateFrame(aNewContext) &&
+ StyleDisplay()->IsFixedPosContainingBlockForAppropriateFrame(this) ==
+ aNewContext->StyleDisplay()->
+ IsFixedPosContainingBlockForAppropriateFrame(aNewContext)) {
+ // While some styles that cause the frame to be a containing block
+ // has changed, the overall result hasn't.
+ hint &= ~nsChangeHint_UpdateContainingBlock;
+ }
+ }
+
MOZ_ASSERT(NS_IsHintSubset(hint, nsChangeHint_AllHints),
"Added a new hint without bumping AllHints?");
return hint & ~nsChangeHint_NeutralChange;
}
nsChangeHint
nsStyleContext::CalcStyleDifference(nsStyleContext* aNewContext,
nsChangeHint aParentHintsNotHandledForDescendants,
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -3028,36 +3028,71 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
// value will be massaged to be something that makes sense for
// SVG text.
inline bool IsBlockInside(const nsIFrame* aContextFrame) const;
inline bool IsBlockOutside(const nsIFrame* aContextFrame) const;
inline bool IsInlineOutside(const nsIFrame* aContextFrame) const;
inline bool IsOriginalDisplayInlineOutside(const nsIFrame* aContextFrame) const;
inline uint8_t GetDisplay(const nsIFrame* aContextFrame) const;
inline bool IsFloating(const nsIFrame* aContextFrame) const;
- inline bool IsAbsPosContainingBlock(const nsIFrame* aContextFrame) const;
inline bool IsRelativelyPositioned(const nsIFrame* aContextFrame) const;
inline bool IsAbsolutelyPositioned(const nsIFrame* aContextFrame) const;
// These methods are defined in nsStyleStructInlines.h.
/**
+ * Returns whether the element is a containing block for its
+ * absolutely positioned descendants.
+ * aContextFrame is the frame for which this is the nsStyleDisplay.
+ */
+ inline bool IsAbsPosContainingBlock(const nsIFrame* aContextFrame) const;
+
+ /**
+ * The same as IsAbsPosContainingBlock, except skipping the tests that
+ * are based on the frame rather than the style context (thus
+ * potentially returning a false positive).
+ */
+ template<class StyleContextLike>
+ inline bool IsAbsPosContainingBlockForAppropriateFrame(
+ StyleContextLike* aStyleContext) const;
+
+ /**
* Returns true when the element has the transform property
* or a related property, and supports CSS transforms.
- * aContextFrame is the frame for which this is the nsStylePosition.
+ * aContextFrame is the frame for which this is the nsStyleDisplay.
*/
inline bool HasTransform(const nsIFrame* aContextFrame) const;
/**
* Returns true when the element is a containing block for its fixed-pos
* descendants.
- * aContextFrame is the frame for which this is the nsStylePosition.
+ * aContextFrame is the frame for which this is the nsStyleDisplay.
*/
inline bool IsFixedPosContainingBlock(const nsIFrame* aContextFrame) const;
+ /**
+ * The same as IsFixedPosContainingBlock, except skipping the tests that
+ * are based on the frame rather than the style context (thus
+ * potentially returning a false positive).
+ */
+ template<class StyleContextLike>
+ inline bool IsFixedPosContainingBlockForAppropriateFrame(
+ StyleContextLike* aStyleContext) const;
+
+private:
+ // Helpers for above functions, which do some but not all of the tests
+ // for them (since transform must be tested separately for each).
+ template<class StyleContextLike>
+ inline bool HasAbsPosContainingBlockStyleInternal(
+ StyleContextLike* aStyleContext) const;
+ template<class StyleContextLike>
+ inline bool HasFixedPosContainingBlockStyleInternal(
+ StyleContextLike* aStyleContext) const;
+
+public:
// Return the 'float' and 'clear' properties, with inline-{start,end} values
// resolved to {left,right} according to the given writing mode. These are
// defined in WritingModes.h.
inline mozilla::StyleFloat PhysicalFloats(mozilla::WritingMode aWM) const;
inline uint8_t PhysicalBreakType(mozilla::WritingMode aWM) const;
};
struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTable
--- a/layout/style/nsStyleStructInlines.h
+++ b/layout/style/nsStyleStructInlines.h
@@ -139,40 +139,90 @@ nsStyleDisplay::IsFloating(const nsIFram
// this function in comments.
bool
nsStyleDisplay::HasTransform(const nsIFrame* aContextFrame) const
{
NS_ASSERTION(aContextFrame->StyleDisplay() == this, "unexpected aContextFrame");
return HasTransformStyle() && aContextFrame->IsFrameOfType(nsIFrame::eSupportsCSSTransforms);
}
+template<class StyleContextLike>
+bool
+nsStyleDisplay::HasFixedPosContainingBlockStyleInternal(
+ StyleContextLike* aStyleContext) const
+{
+ // NOTE: Any CSS properties that influence the output of this function
+ // should have the CSS_PROPERTY_FIXPOS_CB set on them.
+ NS_ASSERTION(aStyleContext->StyleDisplay() == this,
+ "unexpected aStyleContext");
+ return IsContainPaint() ||
+ HasPerspectiveStyle() ||
+ (mWillChangeBitField & NS_STYLE_WILL_CHANGE_FIXPOS_CB) ||
+ aStyleContext->StyleEffects()->HasFilters();
+}
+
+template<class StyleContextLike>
+bool
+nsStyleDisplay::IsFixedPosContainingBlockForAppropriateFrame(
+ StyleContextLike* aStyleContext) const
+{
+ // NOTE: Any CSS properties that influence the output of this function
+ // should have the CSS_PROPERTY_FIXPOS_CB set on them.
+ return HasFixedPosContainingBlockStyleInternal(aStyleContext) ||
+ HasTransformStyle();
+}
+
bool
nsStyleDisplay::IsFixedPosContainingBlock(const nsIFrame* aContextFrame) const
{
// NOTE: Any CSS properties that influence the output of this function
// should have the CSS_PROPERTY_FIXPOS_CB set on them.
- NS_ASSERTION(aContextFrame->StyleDisplay() == this,
- "unexpected aContextFrame");
- return (IsContainPaint() || HasTransform(aContextFrame) ||
- HasPerspectiveStyle() ||
- (mWillChangeBitField & NS_STYLE_WILL_CHANGE_FIXPOS_CB) ||
- aContextFrame->StyleEffects()->HasFilters()) &&
- !aContextFrame->IsSVGText();
+ if (!HasFixedPosContainingBlockStyleInternal(aContextFrame->StyleContext()) &&
+ !HasTransform(aContextFrame)) {
+ return false;
+ }
+ return !aContextFrame->IsSVGText();
+}
+
+template<class StyleContextLike>
+bool
+nsStyleDisplay::HasAbsPosContainingBlockStyleInternal(
+ StyleContextLike* aStyleContext) const
+{
+ // NOTE: Any CSS properties that influence the output of this function
+ // should have the CSS_PROPERTY_ABSPOS_CB set on them.
+ NS_ASSERTION(aStyleContext->StyleDisplay() == this,
+ "unexpected aStyleContext");
+ return IsAbsolutelyPositionedStyle() ||
+ IsRelativelyPositionedStyle() ||
+ (mWillChangeBitField & NS_STYLE_WILL_CHANGE_ABSPOS_CB);
+}
+
+template<class StyleContextLike>
+bool
+nsStyleDisplay::IsAbsPosContainingBlockForAppropriateFrame(StyleContextLike* aStyleContext) const
+{
+ // NOTE: Any CSS properties that influence the output of this function
+ // should have the CSS_PROPERTY_ABSPOS_CB set on them.
+ return HasAbsPosContainingBlockStyleInternal(aStyleContext) ||
+ IsFixedPosContainingBlockForAppropriateFrame(aStyleContext);
}
bool
nsStyleDisplay::IsAbsPosContainingBlock(const nsIFrame* aContextFrame) const
{
- NS_ASSERTION(aContextFrame->StyleDisplay() == this,
- "unexpected aContextFrame");
- return ((IsAbsolutelyPositionedStyle() ||
- IsRelativelyPositionedStyle() ||
- (mWillChangeBitField & NS_STYLE_WILL_CHANGE_ABSPOS_CB)) &&
- !aContextFrame->IsSVGText()) ||
- IsFixedPosContainingBlock(aContextFrame);
+ // NOTE: Any CSS properties that influence the output of this function
+ // should have the CSS_PROPERTY_ABSPOS_CB set on them.
+ nsStyleContext* sc = aContextFrame->StyleContext();
+ if (!HasAbsPosContainingBlockStyleInternal(sc) &&
+ !HasFixedPosContainingBlockStyleInternal(sc) &&
+ !HasTransform(aContextFrame)) {
+ return false;
+ }
+ return !aContextFrame->IsSVGText();
}
bool
nsStyleDisplay::IsRelativelyPositioned(const nsIFrame* aContextFrame) const
{
NS_ASSERTION(aContextFrame->StyleDisplay() == this, "unexpected aContextFrame");
return IsRelativelyPositionedStyle() && !aContextFrame->IsSVGText();
}
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -145,16 +145,17 @@ support-files = file_bug829816.css
support-files = file_bug1055933_circle-xxl.png
[test_bug1089417.html]
support-files = file_bug1089417_iframe.html
[test_bug1112014.html]
[test_bug1203766.html]
[test_bug1232829.html]
[test_cascade.html]
[test_ch_ex_no_infloops.html]
+[test_change_hint_optimizations.html]
[test_clip-path_polygon.html]
[test_compute_data_with_start_struct.html]
skip-if = toolkit == 'android'
[test_computed_style.html]
[test_computed_style_no_pseudo.html]
[test_computed_style_prefs.html]
[test_condition_text.html]
[test_condition_text_assignment.html]
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_change_hint_optimizations.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for style change hint optimizations</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTests() {
+
+ /** Test for Bug 1251075 **/
+ function test_bug1251075_div(id) {
+ var utils = SpecialPowers.DOMWindowUtils;
+
+ var div = document.getElementById(id);
+ div.style.display = "";
+
+ var description = div.style.cssText;
+
+ div.firstElementChild.offsetTop;
+ var constructedBefore = utils.framesConstructed;
+
+ div.style.transform = "translateX(10px)";
+ div.firstElementChild.offsetTop;
+ is(utils.framesConstructed, constructedBefore,
+ "adding a transform style to an element with " + description +
+ " should not cause frame reconstruction even when the element " +
+ "has absolutely positioned descendants");
+
+ div.style.display = "none";
+ }
+
+ test_bug1251075_div("bug1251075_a");
+ test_bug1251075_div("bug1251075_b");
+
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="runTests()">
+<div id="bug1251075_a" style="will-change: transform; display:none">
+ <div style="position: absolute; top: 0; left: 0;"></div>
+ <div style="position: fixed; top: 0; left: 0;"></div>
+</div>
+<div id="bug1251075_b" style="filter: blur(3px); display:none">
+ <div style="position: absolute; top: 0; left: 0;"></div>
+ <div style="position: fixed; top: 0; left: 0;"></div>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/layout/style/test/test_selectors.html
+++ b/layout/style/test/test_selectors.html
@@ -1239,16 +1239,29 @@ function run() {
test_balanced_unparseable("div \\\n p");
test_balanced_unparseable("div\\\n p");
test_balanced_unparseable("div \\\np");
test_balanced_unparseable("div\\\np");
// Test that :-moz-placeholder is parsable.
test_parseable(":-moz-placeholder");
+ // Test that things other than user-action pseudo-classes are
+ // rejected after pseudo-elements. Some of these tests rely on
+ // using a pseudo-element that supports a user-action pseudo-class
+ // after it, so we need to use the prefixed ::-moz-color-swatch,
+ // which is one of the ones with
+ // CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE (none of which are
+ // unprefixed).
+ test_parseable("::-moz-color-swatch:hover");
+ test_balanced_unparseable("::-moz-color-swatch:not(.foo)");
+ test_balanced_unparseable("::-moz-color-swatch:first-child");
+ test_balanced_unparseable("::-moz-color-swatch:hover#foo");
+ test_balanced_unparseable(".foo::after:not(.bar) ~ h3");
+
run_deferred_tests();
}
var deferred_tests = [];
function defer_clonedoc_tests(docurl, onloadfunc)
{
deferred_tests.push( { docurl: docurl, onloadfunc: onloadfunc } );
--- a/layout/svg/SVGImageContext.h
+++ b/layout/svg/SVGImageContext.h
@@ -50,28 +50,30 @@ public:
bool IsPaintingForSVGImageElement() const {
return mIsPaintingSVGImageElement;
}
bool operator==(const SVGImageContext& aOther) const {
return mViewportSize == aOther.mViewportSize &&
mPreserveAspectRatio == aOther.mPreserveAspectRatio &&
- mGlobalOpacity == aOther.mGlobalOpacity;
+ mGlobalOpacity == aOther.mGlobalOpacity &&
+ mIsPaintingSVGImageElement == aOther.mIsPaintingSVGImageElement;
}
bool operator!=(const SVGImageContext& aOther) const {
return !(*this == aOther);
}
uint32_t Hash() const {
return HashGeneric(mViewportSize.width,
mViewportSize.height,
mPreserveAspectRatio.map(HashPAR).valueOr(0),
- HashBytes(&mGlobalOpacity, sizeof(gfxFloat)));
+ HashBytes(&mGlobalOpacity, sizeof(gfxFloat)),
+ mIsPaintingSVGImageElement);
}
private:
static uint32_t HashPAR(const SVGPreserveAspectRatio& aPAR) {
return aPAR.Hash();
}
CSSIntSize mViewportSize;
--- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.cc
+++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.cc
@@ -57,16 +57,19 @@ void RTCPPacketInformation::AddApplicati
uint8_t* oldData = applicationData;
uint16_t oldLength = applicationLength;
// Don't copy more than kRtcpAppCode_DATA_SIZE bytes.
uint16_t copySize = size;
if (size > kRtcpAppCode_DATA_SIZE) {
copySize = kRtcpAppCode_DATA_SIZE;
}
+ if (((uint32_t) applicationLength) + copySize > UINT16_MAX) {
+ return;
+ }
applicationLength += copySize;
applicationData = new uint8_t[applicationLength];
if (oldData)
{
memcpy(applicationData, oldData, oldLength);
memcpy(applicationData+oldLength, data, copySize);
--- a/mfbt/Maybe.h
+++ b/mfbt/Maybe.h
@@ -11,16 +11,17 @@
#include "mozilla/Alignment.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/Move.h"
#include "mozilla/TypeTraits.h"
#include <new> // for placement new
+#include <type_traits>
namespace mozilla {
struct Nothing { };
/*
* Maybe is a container class which contains either zero or one elements. It
* serves two roles. It can represent values which are *semantically* optional,
@@ -96,25 +97,66 @@ public:
Maybe(const Maybe& aOther)
: mIsSome(false)
{
if (aOther.mIsSome) {
emplace(*aOther);
}
}
+ /**
+ * Maybe<T*> can be copy-constructed from a Maybe<U*> if U* and T* are
+ * compatible, or from Maybe<decltype(nullptr)>.
+ */
+ template<typename U,
+ typename =
+ typename std::enable_if<std::is_pointer<T>::value &&
+ (std::is_same<U, decltype(nullptr)>::value ||
+ (std::is_pointer<U>::value &&
+ std::is_base_of<typename std::remove_pointer<T>::type,
+ typename std::remove_pointer<U>::type>::value))>::type>
+ MOZ_IMPLICIT
+ Maybe(const Maybe<U>& aOther)
+ : mIsSome(false)
+ {
+ if (aOther.isSome()) {
+ emplace(*aOther);
+ }
+ }
+
Maybe(Maybe&& aOther)
: mIsSome(false)
{
if (aOther.mIsSome) {
emplace(Move(*aOther));
aOther.reset();
}
}
+ /**
+ * Maybe<T*> can be move-constructed from a Maybe<U*> if U* and T* are
+ * compatible, or from Maybe<decltype(nullptr)>.
+ */
+ template<typename U,
+ typename =
+ typename std::enable_if<std::is_pointer<T>::value &&
+ (std::is_same<U, decltype(nullptr)>::value ||
+ (std::is_pointer<U>::value &&
+ std::is_base_of<typename std::remove_pointer<T>::type,
+ typename std::remove_pointer<U>::type>::value))>::type>
+ MOZ_IMPLICIT
+ Maybe(Maybe<U>&& aOther)
+ : mIsSome(false)
+ {
+ if (aOther.isSome()) {
+ emplace(Move(*aOther));
+ aOther.reset();
+ }
+ }
+
Maybe& operator=(const Maybe& aOther)
{
if (&aOther != this) {
if (aOther.mIsSome) {
if (mIsSome) {
// XXX(seth): The correct code for this branch, below, can't be used
// due to a bug in Visual Studio 2010. See bug 1052940.
/*
--- a/mfbt/WeakPtr.h
+++ b/mfbt/WeakPtr.h
@@ -71,16 +71,57 @@
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/RefCounted.h"
#include "mozilla/RefPtr.h"
#include "mozilla/TypeTraits.h"
#include <string.h>
+// Weak referencing is not implemeted as thread safe. When a WeakPtr
+// is created or dereferenced on thread A but the real object is just
+// being Released() on thread B, there is a possibility of a race
+// when the proxy object (detail::WeakReference) is notified about
+// the real object destruction just between when thread A is storing
+// the object pointer locally and is about to add a reference to it.
+//
+// Hence, a non-null weak proxy object is considered to have a single
+// "owning thread". It means that each query for a weak reference,
+// its dereference, and destruction of the real object must all happen
+// on a single thread. The following macros implement assertions for
+// checking these conditions.
+
+#if defined(DEBUG) || (defined(NIGHTLY_BUILD) && !defined(MOZ_PROFILING))
+
+#include <thread>
+#define MOZ_WEAKPTR_DECLARE_THREAD_SAFETY_CHECK \
+ std::thread::id _owningThread; \
+ bool _empty; // If it was initialized as a placeholder with mPtr = nullptr.
+#define MOZ_WEAKPTR_INIT_THREAD_SAFETY_CHECK() \
+ do { \
+ _owningThread = std::this_thread::get_id(); \
+ _empty = !p; \
+ } while (false)
+#define MOZ_WEAKPTR_ASSERT_THREAD_SAFETY() \
+ MOZ_DIAGNOSTIC_ASSERT(_empty || _owningThread == std::this_thread::get_id(), \
+ "WeakPtr used on multiple threads")
+#define MOZ_WEAKPTR_ASSERT_THREAD_SAFETY_DELEGATED(that) \
+ (that)->AssertThreadSafety();
+
+#define MOZ_WEAKPTR_THREAD_SAFETY_CHECKING 1
+
+#else
+
+#define MOZ_WEAKPTR_DECLARE_THREAD_SAFETY_CHECK
+#define MOZ_WEAKPTR_INIT_THREAD_SAFETY_CHECK() do { } while (false)
+#define MOZ_WEAKPTR_ASSERT_THREAD_SAFETY() do { } while (false)
+#define MOZ_WEAKPTR_ASSERT_THREAD_SAFETY_DELEGATED(that) do { } while (false)
+
+#endif
+
namespace mozilla {
template <typename T> class WeakPtr;
template <typename T> class SupportsWeakPtr;
#ifdef MOZ_REFCOUNTED_LEAK_CHECKING
#define MOZ_DECLARE_WEAKREFERENCE_TYPENAME(T) \
static const char* weakReferenceTypeName() { return "WeakReference<" #T ">"; }
@@ -91,36 +132,50 @@ template <typename T> class SupportsWeak
namespace detail {
// This can live beyond the lifetime of the class derived from
// SupportsWeakPtr.
template<class T>
class WeakReference : public ::mozilla::RefCounted<WeakReference<T> >
{
public:
- explicit WeakReference(T* p) : mPtr(p) {}
+ explicit WeakReference(T* p) : mPtr(p)
+ {
+ MOZ_WEAKPTR_INIT_THREAD_SAFETY_CHECK();
+ }
- T* get() const { return mPtr; }
+ T* get() const {
+ MOZ_WEAKPTR_ASSERT_THREAD_SAFETY();
+ return mPtr;
+ }
#ifdef MOZ_REFCOUNTED_LEAK_CHECKING
const char* typeName() const
{
// The first time this is called mPtr is null, so don't
// invoke any methods on mPtr.
return T::weakReferenceTypeName();
}
size_t typeSize() const { return sizeof(*this); }
#endif
+#ifdef MOZ_WEAKPTR_THREAD_SAFETY_CHECKING
+ void AssertThreadSafety() { MOZ_WEAKPTR_ASSERT_THREAD_SAFETY(); }
+#endif
+
private:
friend class mozilla::SupportsWeakPtr<T>;
- void detach() { mPtr = nullptr; }
+ void detach() {
+ MOZ_WEAKPTR_ASSERT_THREAD_SAFETY();
+ mPtr = nullptr;
+ }
T* MOZ_NON_OWNING_REF mPtr;
+ MOZ_WEAKPTR_DECLARE_THREAD_SAFETY_CHECK
};
} // namespace detail
template <typename T>
class SupportsWeakPtr
{
protected:
@@ -133,16 +188,18 @@ protected:
}
}
private:
const WeakPtr<T>& SelfReferencingWeakPtr()
{
if (!mSelfReferencingWeakPtr) {
mSelfReferencingWeakPtr.mRef = new detail::WeakReference<T>(static_cast<T*>(this));
+ } else {
+ MOZ_WEAKPTR_ASSERT_THREAD_SAFETY_DELEGATED(mSelfReferencingWeakPtr.mRef);
}
return mSelfReferencingWeakPtr;
}
const WeakPtr<const T>& SelfReferencingWeakPtr() const
{
const WeakPtr<T>& p = const_cast<SupportsWeakPtr*>(this)->SelfReferencingWeakPtr();
return reinterpret_cast<const WeakPtr<const T>&>(p);
@@ -158,38 +215,43 @@ template <typename T>
class WeakPtr
{
typedef detail::WeakReference<T> WeakReference;
public:
WeakPtr& operator=(const WeakPtr& aOther)
{
mRef = aOther.mRef;
+ MOZ_WEAKPTR_ASSERT_THREAD_SAFETY_DELEGATED(mRef);
return *this;
}
WeakPtr(const WeakPtr& aOther)
{
+ // The thread safety check is performed inside of the operator= method.
*this = aOther;
}
WeakPtr& operator=(T* aOther)
{
if (aOther) {
*this = aOther->SelfReferencingWeakPtr();
} else if (!mRef || mRef->get()) {
// Ensure that mRef is dereferenceable in the uninitialized state.
mRef = new WeakReference(nullptr);
}
+ // The thread safety check happens inside SelfReferencingWeakPtr
+ // or is initialized in the WeakReference constructor.
return *this;
}
MOZ_IMPLICIT WeakPtr(T* aOther)
{
*this = aOther;
+ MOZ_WEAKPTR_ASSERT_THREAD_SAFETY_DELEGATED(mRef);
}
// Ensure that mRef is dereferenceable in the uninitialized state.
WeakPtr() : mRef(new WeakReference(nullptr)) {}
operator T*() const { return mRef->get(); }
T& operator*() const { return *mRef->get(); }
--- a/mfbt/tests/TestMaybe.cpp
+++ b/mfbt/tests/TestMaybe.cpp
@@ -756,22 +756,105 @@ TestVirtualFunction() {
Maybe<MyDerivedClass> derived;
derived.emplace();
derived.reset();
// If this compiles successfully, we've passed.
return true;
}
+static Maybe<int*>
+ReturnSomeNullptr()
+{
+ return Some(nullptr);
+}
+
+struct D
+{
+ explicit D(Maybe<int*>) {}
+};
+
+static bool
+TestSomeNullptrConversion()
+{
+ Maybe<int*> m1 = Some(nullptr);
+ MOZ_RELEASE_ASSERT(m1.isSome());
+ MOZ_RELEASE_ASSERT(m1);
+ MOZ_RELEASE_ASSERT(!*m1);
+
+ auto m2 = ReturnSomeNullptr();
+ MOZ_RELEASE_ASSERT(m2.isSome());
+ MOZ_RELEASE_ASSERT(m2);
+ MOZ_RELEASE_ASSERT(!*m2);
+
+ Maybe<decltype(nullptr)> m3 = Some(nullptr);
+ MOZ_RELEASE_ASSERT(m3.isSome());
+ MOZ_RELEASE_ASSERT(m3);
+ MOZ_RELEASE_ASSERT(*m3 == nullptr);
+
+ D d(Some(nullptr));
+
+ return true;
+}
+
+struct Base {};
+struct Derived : Base {};
+
+static Maybe<Base*>
+ReturnDerivedPointer()
+{
+ Derived* d = nullptr;
+ return Some(d);
+}
+
+struct ExplicitConstructorBasePointer
+{
+ explicit ExplicitConstructorBasePointer(Maybe<Base*>) {}
+};
+
+static bool
+TestSomePointerConversion()
+{
+ Base base;
+ Derived derived;
+
+ Maybe<Base*> m1 = Some(&derived);
+ MOZ_RELEASE_ASSERT(m1.isSome());
+ MOZ_RELEASE_ASSERT(m1);
+ MOZ_RELEASE_ASSERT(*m1 == &derived);
+
+ auto m2 = ReturnDerivedPointer();
+ MOZ_RELEASE_ASSERT(m2.isSome());
+ MOZ_RELEASE_ASSERT(m2);
+ MOZ_RELEASE_ASSERT(*m2 == nullptr);
+
+ Maybe<Base*> m3 = Some(&base);
+ MOZ_RELEASE_ASSERT(m3.isSome());
+ MOZ_RELEASE_ASSERT(m3);
+ MOZ_RELEASE_ASSERT(*m3 == &base);
+
+ auto s1 = Some(&derived);
+ Maybe<Base*> c1(s1);
+ MOZ_RELEASE_ASSERT(c1.isSome());
+ MOZ_RELEASE_ASSERT(c1);
+ MOZ_RELEASE_ASSERT(*c1 == &derived);
+
+ ExplicitConstructorBasePointer ecbp(Some(&derived));
+
+ return true;
+}
+
int
main()
{
RUN_TEST(TestBasicFeatures);
RUN_TEST(TestCopyAndMove);
RUN_TEST(TestFunctionalAccessors);
RUN_TEST(TestApply);
RUN_TEST(TestMap);
RUN_TEST(TestToMaybe);
RUN_TEST(TestComparisonOperators);
RUN_TEST(TestVirtualFunction);
+ RUN_TEST(TestSomeNullptrConversion);
+ RUN_TEST(TestSomePointerConversion);
return 0;
}
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -166,16 +166,19 @@ pref("dom.enable_resource_timing", true)
pref("dom.enable_user_timing", true);
// Enable printing performance marks/measures to log
pref("dom.performance.enable_user_timing_logging", false);
// Enable notification of performance timing
pref("dom.performance.enable_notify_performance_timing", false);
+// Enable Permission API's .revoke() method
+pref("dom.permissions.revoke.enable", false);
+
// Enable Performance Observer API
#ifdef NIGHTLY_BUILD
pref("dom.enable_performance_observer", true);
#else
pref("dom.enable_performance_observer", false);
#endif
// Whether the Gamepad API is enabled
@@ -512,20 +515,20 @@ pref("media.navigator.audio.full_duplex"
#endif
#endif
#if !defined(ANDROID)
pref("media.getusermedia.screensharing.enabled", true);
#endif
#ifdef RELEASE_BUILD
-pref("media.getusermedia.screensharing.allowed_domains", "webex.com,*.webex.com,ciscospark.com,*.ciscospark.com,projectsquared.com,*.projectsquared.com,*.room.co,room.co,beta.talky.io,talky.io,*.clearslide.com,appear.in,*.appear.in,tokbox.com,*.tokbox.com,*.sso.francetelecom.fr,*.si.francetelecom.fr,*.sso.infra.ftgroup,*.multimedia-conference.orange-business.com,*.espacecollaboration.orange-business.com,free.gotomeeting.com,g2m.me,*.g2m.me,*.mypurecloud.com,*.mypurecloud.com.au,spreed.me,*.spreed.me,*.spreed.com,air.mozilla.org,*.circuit.com,*.yourcircuit.com,circuit.siemens.com,yourcircuit.siemens.com,circuitsandbox.net,*.unify.com,tandi.circuitsandbox.net,*.ericsson.net,*.cct.ericsson.net,*.opentok.com,*.conf.meetecho.com,meet.jit.si,*.meet.jit.si,web.stage.speakeasyapp.net,web.speakeasyapp.net,*.hipchat.me,*.beta-wspbx.com,*.wspbx.com,*.unifiedcloudit.com,*.smartboxuc.com,*.smartbox-uc.com,*.panterranetworks.com,pexipdemo.com,*.pexipdemo.com,pex.me,*.pex.me,*.rd.pexip.com,1click.io,*.1click.io,*.fuze.com,*.fuzemeeting.com,*.thinkingphones.com,gotomeeting.com,*.gotomeeting.com,gotowebinar.com,*.gotowebinar.com,gototraining.com,*.gototraining.com,citrix.com,*.citrix.com,expertcity.com,*.expertcity.com,citrixonline.com,*.citrixonline.com,g2m.me,*.g2m.me,gotomeet.me,*.gotomeet.me,gotomeet.at,*.gotomeet.at,miriadaxdes.miriadax.net,certificacion.miriadax.net,miriadax.net,*.wire.com");
+pref("media.getusermedia.screensharing.allowed_domains", "webex.com,*.webex.com,ciscospark.com,*.ciscospark.com,projectsquared.com,*.projectsquared.com,*.room.co,room.co,beta.talky.io,talky.io,*.clearslide.com,appear.in,*.appear.in,tokbox.com,*.tokbox.com,*.sso.francetelecom.fr,*.si.francetelecom.fr,*.sso.infra.ftgroup,*.multimedia-conference.orange-business.com,*.espacecollaboration.orange-business.com,free.gotomeeting.com,g2m.me,*.g2m.me,*.mypurecloud.com,*.mypurecloud.com.au,spreed.me,*.spreed.me,*.spreed.com,air.mozilla.org,*.circuit.com,*.yourcircuit.com,circuit.siemens.com,yourcircuit.siemens.com,circuitsandbox.net,*.unify.com,tandi.circuitsandbox.net,*.ericsson.net,*.cct.ericsson.net,*.opentok.com,*.conf.meetecho.com,meet.jit.si,*.meet.jit.si,web.stage.speakeasyapp.net,web.speakeasyapp.net,*.hipchat.me,*.beta-wspbx.com,*.wspbx.com,*.unifiedcloudit.com,*.smartboxuc.com,*.smartbox-uc.com,*.panterranetworks.com,pexipdemo.com,*.pexipdemo.com,pex.me,*.pex.me,*.rd.pexip.com,1click.io,*.1click.io,*.fuze.com,*.fuzemeeting.com,*.thinkingphones.com,gotomeeting.com,*.gotomeeting.com,gotowebinar.com,*.gotowebinar.com,gototraining.com,*.gototraining.com,citrix.com,*.citrix.com,expertcity.com,*.expertcity.com,citrixonline.com,*.citrixonline.com,g2m.me,*.g2m.me,gotomeet.me,*.gotomeet.me,gotomeet.at,*.gotomeet.at,miriadaxdes.miriadax.net,certificacion.miriadax.net,miriadax.net,*.wire.com,sylaps.com,*.sylaps.com");
#else
// includes Mozilla's test domain: mozilla.github.io (not intended for release)
-pref("media.getusermedia.screensharing.allowed_domains", "mozilla.github.io,webex.com,*.webex.com,ciscospark.com,*.ciscospark.com,projectsquared.com,*.projectsquared.com,*.room.co,room.co,beta.talky.io,talky.io,*.clearslide.com,appear.in,*.appear.in,tokbox.com,*.tokbox.com,*.sso.francetelecom.fr,*.si.francetelecom.fr,*.sso.infra.ftgroup,*.multimedia-conference.orange-business.com,*.espacecollaboration.orange-business.com,free.gotomeeting.com,g2m.me,*.g2m.me,*.mypurecloud.com,*.mypurecloud.com.au,spreed.me,*.spreed.me,*.spreed.com,air.mozilla.org,*.circuit.com,*.yourcircuit.com,circuit.siemens.com,yourcircuit.siemens.com,circuitsandbox.net,*.unify.com,tandi.circuitsandbox.net,*.ericsson.net,*.cct.ericsson.net,*.opentok.com,*.conf.meetecho.com,meet.jit.si,*.meet.jit.si,web.stage.speakeasyapp.net,web.speakeasyapp.net,*.hipchat.me,*.beta-wspbx.com,*.wspbx.com,*.unifiedcloudit.com,*.smartboxuc.com,*.smartbox-uc.com,*.panterranetworks.com,pexipdemo.com,*.pexipdemo.com,pex.me,*.pex.me,*.rd.pexip.com,1click.io,*.1click.io,*.fuze.com,*.fuzemeeting.com,*.thinkingphones.com,gotomeeting.com,*.gotomeeting.com,gotowebinar.com,*.gotowebinar.com,gototraining.com,*.gototraining.com,citrix.com,*.citrix.com,expertcity.com,*.expertcity.com,citrixonline.com,*.citrixonline.com,g2m.me,*.g2m.me,gotomeet.me,*.gotomeet.me,gotomeet.at,*.gotomeet.at,miriadaxdes.miriadax.net,certificacion.miriadax.net,miriadax.net,*.wire.com");
+pref("media.getusermedia.screensharing.allowed_domains", "mozilla.github.io,webex.com,*.webex.com,ciscospark.com,*.ciscospark.com,projectsquared.com,*.projectsquared.com,*.room.co,room.co,beta.talky.io,talky.io,*.clearslide.com,appear.in,*.appear.in,tokbox.com,*.tokbox.com,*.sso.francetelecom.fr,*.si.francetelecom.fr,*.sso.infra.ftgroup,*.multimedia-conference.orange-business.com,*.espacecollaboration.orange-business.com,free.gotomeeting.com,g2m.me,*.g2m.me,*.mypurecloud.com,*.mypurecloud.com.au,spreed.me,*.spreed.me,*.spreed.com,air.mozilla.org,*.circuit.com,*.yourcircuit.com,circuit.siemens.com,yourcircuit.siemens.com,circuitsandbox.net,*.unify.com,tandi.circuitsandbox.net,*.ericsson.net,*.cct.ericsson.net,*.opentok.com,*.conf.meetecho.com,meet.jit.si,*.meet.jit.si,web.stage.speakeasyapp.net,web.speakeasyapp.net,*.hipchat.me,*.beta-wspbx.com,*.wspbx.com,*.unifiedcloudit.com,*.smartboxuc.com,*.smartbox-uc.com,*.panterranetworks.com,pexipdemo.com,*.pexipdemo.com,pex.me,*.pex.me,*.rd.pexip.com,1click.io,*.1click.io,*.fuze.com,*.fuzemeeting.com,*.thinkingphones.com,gotomeeting.com,*.gotomeeting.com,gotowebinar.com,*.gotowebinar.com,gototraining.com,*.gototraining.com,citrix.com,*.citrix.com,expertcity.com,*.expertcity.com,citrixonline.com,*.citrixonline.com,g2m.me,*.g2m.me,gotomeet.me,*.gotomeet.me,gotomeet.at,*.gotomeet.at,miriadaxdes.miriadax.net,certificacion.miriadax.net,miriadax.net,*.wire.com,sylaps.com,*.sylaps.com");
#endif
// OS/X 10.6 and XP have screen/window sharing off by default due to various issues - Caveat emptor
pref("media.getusermedia.screensharing.allow_on_old_platforms", false);
pref("media.getusermedia.audiocapture.enabled", false);
// TextTrack support
pref("media.webvtt.enabled", true);
--- a/mozglue/build/WindowsDllBlocklist.cpp
+++ b/mozglue/build/WindowsDllBlocklist.cpp
@@ -759,18 +759,16 @@ DllBlocklist_Initialize()
if (sBlocklistInitAttempted) {
return;
}
sBlocklistInitAttempted = true;
if (GetModuleHandleA("user32.dll")) {
sUser32BeforeBlocklist = true;
}
- // Catch any missing DELAYLOADS for user32.dll
- MOZ_ASSERT(!sUser32BeforeBlocklist);
NtDllIntercept.Init("ntdll.dll");
ReentrancySentinel::InitializeStatics();
// We specifically use a detour, because there are cases where external
// code also tries to hook LdrLoadDll, and doesn't know how to relocate our
// nop space patches. (Bug 951827)
--- a/netwerk/protocol/http/HttpChannelParentListener.cpp
+++ b/netwerk/protocol/http/HttpChannelParentListener.cpp
@@ -66,18 +66,18 @@ HttpChannelParentListener::OnStartReques
return NS_ERROR_UNEXPECTED;
LOG(("HttpChannelParentListener::OnStartRequest [this=%p]\n", this));
return mNextListener->OnStartRequest(aRequest, aContext);
}
NS_IMETHODIMP
HttpChannelParentListener::OnStopRequest(nsIRequest *aRequest,
- nsISupports *aContext,
- nsresult aStatusCode)
+ nsISupports *aContext,
+ nsresult aStatusCode)
{
MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
"Cannot call OnStopRequest if suspended for diversion!");
if (!mNextListener)
return NS_ERROR_UNEXPECTED;
LOG(("HttpChannelParentListener::OnStopRequest: [this=%p status=%ul]\n",
@@ -89,20 +89,20 @@ HttpChannelParentListener::OnStopRequest
}
//-----------------------------------------------------------------------------
// HttpChannelParentListener::nsIStreamListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParentListener::OnDataAvailable(nsIRequest *aRequest,
- nsISupports *aContext,
- nsIInputStream *aInputStream,
- uint64_t aOffset,
- uint32_t aCount)
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
{
MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
"Cannot call OnDataAvailable if suspended for diversion!");
if (!mNextListener)
return NS_ERROR_UNEXPECTED;
LOG(("HttpChannelParentListener::OnDataAvailable [this=%p]\n", this));
--- a/netwerk/protocol/http/nsHttpRequestHead.cpp
+++ b/netwerk/protocol/http/nsHttpRequestHead.cpp
@@ -14,23 +14,54 @@
//-----------------------------------------------------------------------------
// nsHttpRequestHead
//-----------------------------------------------------------------------------
namespace mozilla {
namespace net {
+#ifdef MOZ_CRASHREPORTER
+
+void nsHttpRequestHead::DbgReentrantMonitorAutoEnter::DbgCheck(bool aIn)
+{
+ nsHttpAtom header;
+ if (!mInstance.mAnnotated && mInstance.mHeaders.Count() &&
+ !mInstance.mHeaders.PeekHeaderAt(0, header)) {
+ nsAutoCString str;
+ str.Append(nsPrintfCString("%s %s", aIn ? "in" : "out", mFunc));
+ // Output the content of the array header and the first nsEntry.
+ const uint8_t* p = reinterpret_cast<uint8_t*>
+ (mInstance.mHeaders.mHeaders.Elements()) - sizeof(nsTArrayHeader);
+ for (int i = 0; i < 28; ++i, ++p) {
+ str.Append(nsPrintfCString(" %02x", *p));
+ }
+ CrashReporter::AnnotateCrashReport(
+ NS_LITERAL_CSTRING("InvalidHttpHeaderArray"), str);
+ // Make sure we annotate only when we found it is invalid at the first
+ // time.
+ mInstance.mAnnotated = true;
+ }
+}
+
+#define ReentrantMonitorAutoEnter DbgReentrantMonitorAutoEnter
+#define mon(x) mon(*this, __func__)
+
+#endif
+
nsHttpRequestHead::nsHttpRequestHead()
: mMethod(NS_LITERAL_CSTRING("GET"))
, mVersion(NS_HTTP_VERSION_1_1)
, mParsedMethod(kMethod_Get)
, mHTTPS(false)
, mReentrantMonitor("nsHttpRequestHead.mReentrantMonitor")
, mInVisitHeaders(false)
+#ifdef MOZ_CRASHREPORTER
+ , mAnnotated(false)
+#endif
{
MOZ_COUNT_CTOR(nsHttpRequestHead);
}
nsHttpRequestHead::~nsHttpRequestHead()
{
MOZ_COUNT_DTOR(nsHttpRequestHead);
}
@@ -44,32 +75,16 @@ nsHttpRequestHead::Headers() const
curr.mReentrantMonitor.AssertCurrentThreadIn();
return mHeaders;
}
void
nsHttpRequestHead::SetHeaders(const nsHttpHeaderArray& aHeaders)
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-#ifdef MOZ_CRASHREPORTER
- nsHttpAtom header;
- if (mHeaders.Count() && !mHeaders.PeekHeaderAt(0, header)) {
- nsAutoCString str;
- const uint8_t* p = reinterpret_cast<uint8_t*>(mHeaders.mHeaders.Elements()) -
- sizeof(nsTArrayHeader);
- for (int i = 0; i < 48; ++i, ++p) {
- str.Append(nsPrintfCString("%02x ", *p));
- }
- CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("HttpHeaderArray"), str);
- if (header._val) {
- CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("HttpHeaderArray[0].header"),
- nsDependentCString(header._val));
- }
- }
-#endif
mHeaders = aHeaders;
}
void
nsHttpRequestHead::SetVersion(nsHttpVersion version)
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
mVersion = version;
@@ -133,17 +148,17 @@ nsHttpRequestHead::Path(nsACString &aPat
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
aPath = mPath.IsEmpty() ? mRequestURI : mPath;
}
void
nsHttpRequestHead::SetHTTPS(bool val)
{
- ReentrantMonitorAutoEnter monk(mReentrantMonitor);
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
mHTTPS = val;
}
void
nsHttpRequestHead::Origin(nsACString &aOrigin)
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
aOrigin = mOrigin;
@@ -379,10 +394,15 @@ nsHttpRequestHead::Flatten(nsACString &b
buf.AppendLiteral("1.0");
}
buf.AppendLiteral("\r\n");
mHeaders.Flatten(buf, pruneProxyHeaders, false);
}
+#ifdef MOZ_CRASHREPORTER
+#undef ReentrantMonitorAutoEnter
+#undef mon
+#endif
+
} // namespace net
} // namespace mozilla
--- a/netwerk/protocol/http/nsHttpRequestHead.h
+++ b/netwerk/protocol/http/nsHttpRequestHead.h
@@ -115,14 +115,41 @@ private:
bool mHTTPS;
// We are using ReentrantMonitor instead of a Mutex because VisitHeader
// function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
ReentrantMonitor mReentrantMonitor;
// During VisitHeader we sould not allow cal to SetHeader.
bool mInVisitHeaders;
+
+#ifdef MOZ_CRASHREPORTER
+ class DbgReentrantMonitorAutoEnter : ReentrantMonitorAutoEnter
+ {
+ public:
+ explicit DbgReentrantMonitorAutoEnter(nsHttpRequestHead& aInstance,
+ const char* aFunc)
+ : ReentrantMonitorAutoEnter(aInstance.mReentrantMonitor),
+ mInstance(aInstance),
+ mFunc(aFunc)
+ {
+ DbgCheck(true);
+ }
+ ~DbgReentrantMonitorAutoEnter(void)
+ {
+ DbgCheck(false);
+ }
+
+ private:
+ void DbgCheck(bool aIn);
+
+ nsHttpRequestHead& mInstance;
+ const char* mFunc;
+ };
+
+ bool mAnnotated;
+#endif
};
} // namespace net
} // namespace mozilla
#endif // nsHttpRequestHead_h__
--- a/security/sandbox/mac/Sandbox.h
+++ b/security/sandbox/mac/Sandbox.h
@@ -36,24 +36,26 @@ typedef struct _MacSandboxPluginInfo {
} MacSandboxPluginInfo;
typedef struct _MacSandboxInfo {
_MacSandboxInfo()
: type(MacSandboxType_Default), level(0) {}
_MacSandboxInfo(const struct _MacSandboxInfo& other)
: type(other.type), level(other.level), pluginInfo(other.pluginInfo),
appPath(other.appPath), appBinaryPath(other.appBinaryPath),
- appDir(other.appDir), appTempDir(other.appTempDir) {}
+ appDir(other.appDir), appTempDir(other.appTempDir),
+ profileDir(other.profileDir) {}
MacSandboxType type;
int32_t level;
MacSandboxPluginInfo pluginInfo;
std::string appPath;
std::string appBinaryPath;
std::string appDir;
std::string appTempDir;
+ std::string profileDir;
} MacSandboxInfo;
namespace mozilla {
bool StartMacSandbox(MacSandboxInfo aInfo, std::string &aErrorMessage);
} // namespace mozilla
--- a/security/sandbox/mac/Sandbox.mm
+++ b/security/sandbox/mac/Sandbox.mm
@@ -152,16 +152,17 @@ static const char contentSandboxRules[]
"(version 1)\n"
"\n"
"(define sandbox-level %d)\n"
"(define macosMinorVersion %d)\n"
"(define appPath \"%s\")\n"
"(define appBinaryPath \"%s\")\n"
"(define appDir \"%s\")\n"
"(define appTempDir \"%s\")\n"
+ "(define profileDir \"%s\")\n"
"(define home-path \"%s\")\n"
"\n"
"; Allow read access to standard system paths.\n"
"(allow file-read*\n"
" (require-all (file-mode #o0004)\n"
" (require-any (subpath \"/Library/Filesystems/NetFSPlugins\")\n"
" (subpath \"/System\")\n"
" (subpath \"/private/var/db/dyld\")\n"
@@ -227,16 +228,19 @@ static const char contentSandboxRules[]
"\n"
" (define (home-regex home-relative-regex)\n"
" (resolving-regex (string-append \"^\" (regex-quote home-path) home-relative-regex)))\n"
" (define (home-subpath home-relative-subpath)\n"
" (resolving-subpath (string-append home-path home-relative-subpath)))\n"
" (define (home-literal home-relative-literal)\n"
" (resolving-literal (string-append home-path home-relative-literal)))\n"
"\n"
+ " (define (profile-subpath profile-relative-subpath)\n"
+ " (resolving-subpath (string-append profileDir profile-relative-subpath)))\n"
+ "\n"
" (define (container-regex container-relative-regex)\n"
" (resolving-regex (string-append \"^\" (regex-quote container-path) container-relative-regex)))\n"
" (define (container-subpath container-relative-subpath)\n"
" (resolving-subpath (string-append container-path container-relative-subpath)))\n"
" (define (container-literal container-relative-literal)\n"
" (resolving-literal (string-append container-path container-relative-literal)))\n"
"\n"
" (define (var-folders-regex var-folders-relative-regex)\n"
@@ -366,26 +370,27 @@ static const char contentSandboxRules[]
" (allow file-read*\n"
" (var-folders2-regex \"/com\\.apple\\.IconServices/\")\n"
" (var-folders2-regex \"/[^/]+\\.mozrunner/extensions/[^/]+/chrome/[^/]+/content/[^/]+\\.j(s|ar)$\"))\n"
"\n"
" (allow file-write* (var-folders2-regex \"/org\\.chromium\\.[a-zA-Z0-9]*$\"))\n"
" (allow file-read*\n"
" (home-regex \"/Library/Application Support/[^/]+/Extensions/[^/]/\")\n"
" (resolving-regex \"/Library/Application Support/[^/]+/Extensions/[^/]/\")\n"
- " (home-regex \"/Library/Application Support/Firefox/Profiles/[^/]+/extensions/\")\n"
- " (home-regex \"/Library/Application Support/Firefox/Profiles/[^/]+/weave/\"))\n"
+ " (profile-subpath \"/extensions\")\n"
+ " (profile-subpath \"/weave\"))\n"
"\n"
- "; the following rules should be removed when printing and \n"
+ "; the following rules should be removed when printing and\n"
"; opening a file from disk are brokered through the main process\n"
" (if\n"
" (< sandbox-level 2)\n"
" (allow file*\n"
- " (require-not\n"
- " (home-subpath \"/Library\")))\n"
+ " (require-all\n"
+ " (require-not (home-subpath \"/Library\"))\n"
+ " (require-not (subpath profileDir))))\n"
" (allow file*\n"
" (require-all\n"
" (subpath home-path)\n"
" (require-not\n"
" (home-subpath \"/Library\")))))\n"
"\n"
"; printing\n"
" (allow authorization-right-obtain\n"
@@ -492,16 +497,17 @@ bool StartMacSandbox(MacSandboxInfo aInf
MOZ_ASSERT(aInfo.level >= 1);
if (aInfo.level >= 1) {
asprintf(&profile, contentSandboxRules, aInfo.level,
OSXVersion::OSXVersionMinor(),
aInfo.appPath.c_str(),
aInfo.appBinaryPath.c_str(),
aInfo.appDir.c_str(),
aInfo.appTempDir.c_str(),
+ aInfo.profileDir.c_str(),
getenv("HOME"));
} else {
fprintf(stderr,
"Content sandbox disabled due to sandbox level setting\n");
return false;
}
}
else {
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -37700,16 +37700,22 @@
"url": "/web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html"
}
],
"web-animations/timing-model/animations/updating-the-finished-state.html": [
{
"path": "web-animations/timing-model/animations/updating-the-finished-state.html",
"url": "/web-animations/timing-model/animations/updating-the-finished-state.html"
}
+ ],
+ "workers/Worker_ErrorEvent_error.htm": [
+ {
+ "path": "workers/Worker_ErrorEvent_error.htm",
+ "url": "/workers/Worker_ErrorEvent_error.htm"
+ }
]
}
},
"reftest_nodes": {
"html/semantics/grouping-content/the-ol-element/reversed-1a.html": [
{
"path": "html/semantics/grouping-content/the-ol-element/reversed-1a.html",
"references": [
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/send-after-setting-document-domain.htm.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[send-after-setting-document-domain.htm]
- type: testharness
- expected: ERROR
- [loading documents from the origin document.domain was set to should throw]
- expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/send-authentication-basic-cors-not-enabled.htm.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[send-authentication-basic-cors-not-enabled.htm]
- type: testharness
- [XMLHttpRequest: send() - "Basic" authenticated CORS requests with user name and password passed to open() (asserts failure)]
- expected: FAIL
-
--- a/testing/web-platform/meta/XMLHttpRequest/send-non-same-origin.sub.htm.ini
+++ b/testing/web-platform/meta/XMLHttpRequest/send-non-same-origin.sub.htm.ini
@@ -1,17 +1,4 @@
[send-non-same-origin.sub.htm]
type: testharness
- [XMLHttpRequest: send() - non same-origin (mailto:test@example.org)]
- expected: FAIL
-
[XMLHttpRequest: send() - non same-origin (tel:+31600000000)]
expected: FAIL
-
- [XMLHttpRequest: send() - non same-origin (http://www2.web-platform.test:8000/)]
- expected: FAIL
-
- [XMLHttpRequest: send() - non same-origin (javascript:alert('FAIL'))]
- expected: FAIL
-
- [XMLHttpRequest: send() - non same-origin (folder.txt)]
- expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/send-redirect-bogus-sync.htm.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[send-redirect-bogus-sync.htm]
- type: testharness
- [XMLHttpRequest: send() - Redirects (bogus Location header; sync) (301: foobar://abcd)]
- expected: FAIL
-
- [XMLHttpRequest: send() - Redirects (bogus Location header; sync) (302: http://z)]
- expected: FAIL
-
- [XMLHttpRequest: send() - Redirects (bogus Location header; sync) (302: mailto:someone@example.org)]
- expected: FAIL
-
- [XMLHttpRequest: send() - Redirects (bogus Location header; sync) (303: http://z)]
- expected: FAIL
-
- [XMLHttpRequest: send() - Redirects (bogus Location header; sync) (303: tel:1234567890)]
- expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/send-redirect-infinite-sync.htm.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[send-redirect-infinite-sync.htm]
- type: testharness
- [XMLHttpRequest: send() - Redirects (infinite loop; sync) (301)]
- expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/xmlhttprequest-network-error-sync.htm.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[xmlhttprequest-network-error-sync.htm]
- type: testharness
- [XMLHttpRequest: members during network errors (sync)]
- expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/tests/XMLHttpRequest/event-error.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>XMLHttpRequest Test: event - error</title>
-<link rel="author" title="Intel" href="http://www.intel.com">
-<meta name="assert" content="Check if event onerror is fired When the request has failed.">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-
-<div id="log"></div>
-
-<script>
-
-async_test(function (t) {
- var client = new XMLHttpRequest();
- client.onerror = t.step_func(function(e) {
- assert_true(e instanceof ProgressEvent);
- assert_equals(e.type, "error");
- t.done();
- });
-
- client.open("GET", "http://nonexistent-origin.{{host}}:{{ports[http][0]}}");
- client.send("null");
-}, document.title);
-
-</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/resources/send-after-setting-document-domain-window-1.htm
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+ <head>
+ <title>XMLHttpRequest: send() with document.domain set: loading documents from original origin after setting document.domain</title>
+ <script src="send-after-setting-document-domain-window-helper.js"></script>
+ <link rel="help" href="https://xhr.spec.whatwg.org/#the-open()-method" data-tested-assertations="following::ol[1]/li[2]/ol[1]/li[3]" />
+ </head>
+ <body>
+ <script>
+ run_test(function() {
+ document.domain = document.domain; // this is not a noop, it does actually change the security context
+ var client = new XMLHttpRequest();
+ client.open("GET", "status.py?content=hello", false);
+ client.send(null);
+ assert_equals(client.responseText, "hello");
+ document.domain = document.domain.replace(/^\w+\./, "");
+ client.open("GET", "status.py?content=hello2", false);
+ client.send(null);
+ assert_equals(client.responseText, "hello2");
+ }, "loading documents from original origin after setting document.domain");
+ </script>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/resources/send-after-setting-document-domain-window-2.htm
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <title>XMLHttpRequest: send() with document.domain set: loading documents from the origin document.domain was set to should throw</title>
+ <script src="send-after-setting-document-domain-window-helper.js"></script>
+ <link rel="help" href="https://xhr.spec.whatwg.org/#the-open()-method" data-tested-assertations="following::ol[1]/li[2]/ol[1]/li[3]" />
+ </head>
+ <body>
+ <script>
+ run_test(function() {
+ document.domain = document.domain.replace(/^\w+\./, "");
+ var client = new XMLHttpRequest();
+ client.open("GET", location.protocol + "//" + document.domain + location.pathname.replace(/[^\/]*$/, "") + "status.py?content=hello3", false);
+ assert_throws("NetworkError", function() {
+ client.send(null);
+ });
+ }, "loading documents from the origin document.domain was set to should throw");
+ </script>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/resources/send-after-setting-document-domain-window-helper.js
@@ -0,0 +1,29 @@
+function assert_equals(value, expected) {
+ if (value != expected) {
+ throw "Got wrong value.\nExpected '" + expected + "',\ngot '" + value + "'";
+ }
+}
+
+function assert_throws(expected_exc, func) {
+ try {
+ func.call(this);
+ } catch(e) {
+ var actual = e.name || e.type;
+ if (actual != expected_exc) {
+ throw "Got wrong exception.\nExpected '" + expected_exc + "',\ngot '" + actual + "'.";
+ }
+ return;
+ }
+ throw "Expected exception, but none was thrown";
+}
+
+function run_test(test, name) {
+ var result = {passed: true, message: null, name: name};
+ try {
+ test();
+ } catch(e) {
+ result.passed = false;
+ result.message = e + "";
+ }
+ opener.postMessage(result, "*");
+}
--- a/testing/web-platform/tests/XMLHttpRequest/send-after-setting-document-domain.htm
+++ b/testing/web-platform/tests/XMLHttpRequest/send-after-setting-document-domain.htm
@@ -1,38 +1,39 @@
<!doctype html>
<html>
<head>
<title>XMLHttpRequest: send() with document.domain set</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
- <!-- The spec doesn't seem to explicitly cover this case (as of June 2013) -->
<link rel="help" href="https://xhr.spec.whatwg.org/#the-open()-method" data-tested-assertations="following::ol[1]/li[2]/ol[1]/li[3]" />
</head>
<body>
<div id="log"></div>
<script>
- // first make sure we actually run off a domain with at least three parts, in order to be able to shorten it..
- if (location.hostname.split(/\./).length < 3) {
- location.href = location.protocol+'//www2.'+location.host+location.pathname
- }
+ var test_base_url = location.protocol+'//www2.'+location.host+"/XMLHttpRequest/resources/",
+ test_windows = [
+ window.open(test_base_url + "send-after-setting-document-domain-window-1.htm"),
+ window.open(test_base_url + "send-after-setting-document-domain-window-2.htm"),
+ ],
+ num_tests_left = test_windows.length;
- test(function() {
- document.domain = document.domain // this is not a noop, it does actually change the security context
- var client = new XMLHttpRequest()
- client.open("GET", "resources/status.py?content=hello", false)
- client.send(null)
- assert_equals(client.responseText, "hello")
- document.domain = document.domain.replace(/^\w+\./, '')
- client.open("GET", "resources/status.py?content=hello2", false)
- client.send(null)
- assert_equals(client.responseText, "hello2")
- }, "loading documents from original origin after setting document.domain")
- // try to load a document from the origin document.domain was set to
- test(function () {
- var client = new XMLHttpRequest()
- client.open("GET", location.protocol + '//' + document.domain + location.pathname.replace(/[^\/]*$/, '') + "resources/status.py?content=hello3", false)
- // AFAIK this should throw
- assert_throws('NetworkError', function(){client.send(null)})
- }, "loading documents from the origin document.domain was set to should throw")
+ async_test(function(wrapper_test) {
+ window.addEventListener("message", function(evt) {
+ // run a shadow test that just forwards the results
+ async_test(function(test) {
+ assert_true(evt.data.passed, evt.data.message);
+ test.done();
+ }, evt.data.name);
+
+ // after last result comes in, close all test
+ // windows and complete the wrapper test.
+ if (--num_tests_left == 0) {
+ for (var i=0; i<test_windows.length; ++i) {
+ test_windows[i].close();
+ }
+ wrapper_test.done();
+ }
+ }, false);
+ }, "All tests ran");
</script>
</body>
</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/workers/Worker_ErrorEvent_error.htm
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+var t1 = async_test("Error handler outside the worker should not see the error value");
+var t2 = async_test("Error handlers inside a worker should see the error value");
+
+test(function() {
+ var worker = new Worker("support/ErrorEvent-error.js");
+ worker.onerror = t1.step_func_done(function(e) {
+ assert_true(/hello/.test(e.message));
+ assert_equals(e.error, null);
+ });
+
+ var messages = 0;
+ worker.onmessage = t2.step_func(function(e) {
+ ++messages;
+ var data = e.data;
+ assert_true(data.source == "onerror" ||
+ data.source == "event listener");
+ assert_equals(data.value, "hello");
+ if (messages == 2) {
+ t2.done();
+ }
+ });
+});
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/workers/support/ErrorEvent-error.js
@@ -0,0 +1,9 @@
+onerror = function(message, location, line, col, error) {
+ postMessage({ source: "onerror", value: error });
+}
+
+addEventListener("error", function(e) {
+ postMessage({ source: "event listener", value: e.error });
+});
+
+throw "hello";
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -29,33 +29,38 @@ this.SelectParentHelper = {
menulist.hidden = false;
currentBrowser = browser;
closedWithEnter = false;
this._registerListeners(browser, menulist.menupopup);
let win = browser.ownerDocument.defaultView;
// Set the maximum height to show exactly MAX_ROWS items.
- let firstItem = menulist.getItemAtIndex(0);
+ let menupopup = menulist.menupopup;
+ let firstItem = menupopup.firstChild;
+ while (firstItem && firstItem.hidden) {
+ firstItem = firstItem.nextSibling;
+ }
+
if (firstItem) {
let itemHeight = firstItem.getBoundingClientRect().height;
// Include the padding and border on the popup.
- let cs = win.getComputedStyle(menulist.menupopup);
+ let cs = win.getComputedStyle(menupopup);
let bpHeight = parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth) +
parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
- menulist.menupopup.style.maxHeight = (itemHeight * MAX_ROWS + bpHeight) + "px";
+ menupopup.style.maxHeight = (itemHeight * MAX_ROWS + bpHeight) + "px";
}
let constraintRect = browser.getBoundingClientRect();
constraintRect = new win.DOMRect(constraintRect.left + win.mozInnerScreenX,
constraintRect.top + win.mozInnerScreenY,
constraintRect.width, constraintRect.height);
- menulist.menupopup.setConstraintRect(constraintRect);
- menulist.menupopup.openPopupAtScreenRect("after_start", rect.left, rect.top, rect.width, rect.height, false, false);
+ menupopup.setConstraintRect(constraintRect);
+ menupopup.openPopupAtScreenRect("after_start", rect.left, rect.top, rect.width, rect.height, false, false);
},
hide: function(menulist, browser) {
if (currentBrowser == browser) {
menulist.menupopup.hidePopup();
}
},
@@ -136,17 +141,17 @@ this.SelectParentHelper = {
browser.ownerDocument.defaultView.removeEventListener("keydown", this, true);
browser.ownerDocument.defaultView.removeEventListener("fullscreen", this, true);
browser.messageManager.removeMessageListener("Forms:UpdateDropDown", this);
},
};
function populateChildren(menulist, options, selectedIndex, zoom,
- isInGroup = false, isGroupDisabled = false, adjustedTextSize = -1) {
+ parentElement = null, isGroupDisabled = false, adjustedTextSize = -1) {
let element = menulist.menupopup;
// -1 just means we haven't calculated it yet. When we recurse through this function
// we will pass in adjustedTextSize to save on recalculations.
if (adjustedTextSize == -1) {
let win = element.ownerDocument.defaultView;
// Grab the computed text size and multiply it by the remote browser's fullZoom to ensure
@@ -158,30 +163,30 @@ function populateChildren(menulist, opti
for (let option of options) {
let isOptGroup = (option.tagName == 'OPTGROUP');
let item = element.ownerDocument.createElement(isOptGroup ? "menucaption" : "menuitem");
item.setAttribute("label", option.textContent);
item.style.direction = option.textDirection;
item.style.fontSize = adjustedTextSize;
- item.style.display = option.display;
+ item.hidden = option.display == "none" || (parentElement && parentElement.hidden);
item.setAttribute("tooltiptext", option.tooltip);
element.appendChild(item);
// A disabled optgroup disables all of its child options.
let isDisabled = isGroupDisabled || option.disabled;
if (isDisabled) {
item.setAttribute("disabled", "true");
}
if (isOptGroup) {
populateChildren(menulist, option.children, selectedIndex, zoom,
- true, isDisabled, adjustedTextSize);
+ item, isDisabled, adjustedTextSize);
} else {
if (option.index == selectedIndex) {
// We expect the parent element of the popup to be a <xul:menulist> that
// has the popuponly attribute set to "true". This is necessary in order
// for a <xul:menupopup> to act like a proper <html:select> dropdown, as
// the <xul:menulist> does things like remember state and set the
// _moz-menuactive attribute on the selected <xul:menuitem>.
menulist.selectedItem = item;
@@ -191,14 +196,14 @@ function populateChildren(menulist, opti
// may have been removed from the selected item. Since that's normally only
// set for the initially selected on popupshowing for the menulist, and we
// don't want to close and re-open the popup, we manually set it here.
menulist.menuBoxObject.activeChild = item;
}
item.setAttribute("value", option.index);
- if (isInGroup) {
+ if (parentElement) {
item.classList.add("contentSelectDropdown-ingroup")
}
}
}
}
--- a/toolkit/mozapps/update/common/errors.h
+++ b/toolkit/mozapps/update/common/errors.h
@@ -78,18 +78,19 @@
#define WRITE_ERROR_PATCH_FILE 64
#define WRITE_ERROR_APPLY_DIR_PATH 65
#define WRITE_ERROR_CALLBACK_PATH 66
#define WRITE_ERROR_FILE_ACCESS_DENIED 67
#define WRITE_ERROR_DIR_ACCESS_DENIED 68
#define WRITE_ERROR_DELETE_BACKUP 69
#define WRITE_ERROR_EXTRACT 70
#define REMOVE_FILE_SPEC_ERROR 71
-#define INVALID_STAGED_PARENT_ERROR 72
+#define INVALID_APPLYTO_DIR_STAGED_ERROR 72
#define LOCK_ERROR_PATCH_FILE 73
+#define INVALID_APPLYTO_DIR_ERROR 74
// Error codes 80 through 99 are reserved for nsUpdateService.js
// The following error codes are only used by updater.exe
// when a fallback key exists for tests.
#define FALLBACKKEY_UNKNOWN_ERROR 100
#define FALLBACKKEY_REGPATH_ERROR 101
#define FALLBACKKEY_NOKEY_ERROR 102
--- a/toolkit/mozapps/update/tests/chrome/utils.js
+++ b/toolkit/mozapps/update/tests/chrome/utils.js
@@ -616,17 +616,17 @@ function verifyTestsRan() {
}
/**
* Creates a backup of files the tests need to modify so they can be restored to
* the original file when the test has finished and then modifies the files.
*/
function setupFiles() {
// Backup the updater-settings.ini file if it exists by moving it.
- let baseAppDir = getAppBaseDir();
+ let baseAppDir = getGREDir();
let updateSettingsIni = baseAppDir.clone();
updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
if (updateSettingsIni.exists()) {
updateSettingsIni.moveTo(baseAppDir, FILE_UPDATE_SETTINGS_INI_BAK);
}
updateSettingsIni = baseAppDir.clone();
updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS);
@@ -771,17 +771,17 @@ function setupPrefs() {
Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false);
}
/**
* Restores files that were backed up for the tests and general file cleanup.
*/
function resetFiles() {
// Restore the backed up updater-settings.ini if it exists.
- let baseAppDir = getAppBaseDir();
+ let baseAppDir = getGREDir();
let updateSettingsIni = baseAppDir.clone();
updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI_BAK);
if (updateSettingsIni.exists()) {
updateSettingsIni.moveTo(baseAppDir, FILE_UPDATE_SETTINGS_INI);
}
// Not being able to remove the "updated" directory will not adversely affect
// subsequent tests so wrap it in a try block and don't test whether its
--- a/toolkit/mozapps/update/tests/data/sharedUpdateXML.js
+++ b/toolkit/mozapps/update/tests/data/sharedUpdateXML.js
@@ -30,37 +30,43 @@ const STATE_PENDING = "pending";
const STATE_PENDING_SVC = "pending-service";
const STATE_APPLYING = "applying";
const STATE_APPLIED = "applied";
const STATE_APPLIED_SVC = "applied-service";
const STATE_SUCCEEDED = "succeeded";
const STATE_DOWNLOAD_FAILED = "download-failed";
const STATE_FAILED = "failed";
-const LOADSOURCE_ERROR_WRONG_SIZE = 2;
-const CRC_ERROR = 4;
-const READ_ERROR = 6;
-const WRITE_ERROR = 7;
-const MAR_CHANNEL_MISMATCH_ERROR = 22;
-const VERSION_DOWNGRADE_ERROR = 23;
+const LOADSOURCE_ERROR_WRONG_SIZE = 2;
+const CRC_ERROR = 4;
+const READ_ERROR = 6;
+const WRITE_ERROR = 7;
+const MAR_CHANNEL_MISMATCH_ERROR = 22;
+const VERSION_DOWNGRADE_ERROR = 23;
+const INVALID_APPLYTO_DIR_STAGED_ERROR = 72;
+const INVALID_APPLYTO_DIR_ERROR = 74;
const STATE_FAILED_DELIMETER = ": ";
const STATE_FAILED_LOADSOURCE_ERROR_WRONG_SIZE =
STATE_FAILED + STATE_FAILED_DELIMETER + LOADSOURCE_ERROR_WRONG_SIZE;
const STATE_FAILED_CRC_ERROR =
STATE_FAILED + STATE_FAILED_DELIMETER + CRC_ERROR;
const STATE_FAILED_READ_ERROR =
STATE_FAILED + STATE_FAILED_DELIMETER + READ_ERROR;
const STATE_FAILED_WRITE_ERROR =
STATE_FAILED + STATE_FAILED_DELIMETER + WRITE_ERROR;
const STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR =
STATE_FAILED + STATE_FAILED_DELIMETER + MAR_CHANNEL_MISMATCH_ERROR;
const STATE_FAILED_VERSION_DOWNGRADE_ERROR =
STATE_FAILED + STATE_FAILED_DELIMETER + VERSION_DOWNGRADE_ERROR;
+const STATE_FAILED_INVALID_APPLYTO_DIR_STAGED_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_APPLYTO_DIR_STAGED_ERROR;
+const STATE_FAILED_INVALID_APPLYTO_DIR_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_APPLYTO_DIR_ERROR;
/**
* Constructs a string representing a remote update xml file.
*
* @param aUpdates
* The string representing the update elements.
* @return The string representing a remote update xml file.
*/
--- a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
+++ b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
@@ -150,16 +150,18 @@ var gStatusResult;
var gProcess;
var gAppTimer;
var gHandle;
var gGREDirOrig;
var gGREBinDirOrig;
var gAppDirOrig;
+var gApplyToDirOverride;
+
var gServiceLaunchedCallbackLog = null;
var gServiceLaunchedCallbackArgs = null;
// Variables are used instead of contants so tests can override these values if
// necessary.
var gCallbackBinFile = "callback_app" + BIN_SUFFIX;
var gCallbackArgs = ["./", "callback.log", "Test Arg 2", "Test Arg 3"];
var gPostUpdateBinFile = "postup_app" + BIN_SUFFIX;
@@ -1133,16 +1135,28 @@ function getAppVersion() {
MSG_SHOULD_EXIST + getMsgPath(iniFile.path));
let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
getService(Ci.nsIINIParserFactory).
createINIParser(iniFile);
return iniParser.getString("App", "Version");
}
/**
+ * Override the apply-to directory parameter to be passed to the updater.
+ * This ought to cause the updater to fail when using any value that isn't the
+ * default, automatically computed one.
+ *
+ * @param dir
+ * Complete string to use as the apply-to directory parameter.
+ */
+function overrideApplyToDir(dir) {
+ gApplyToDirOverride = dir;
+}
+
+/**
* Helper function for getting the relative path to the directory where the
* application binary is located (e.g. <test_file_leafname>/dir.app/).
*
* Note: The dir.app subdirectory under <test_file_leafname> is needed for
* platforms other than Mac OS X so the tests can run in parallel due to
* update staging creating a lock file named moz_update_in_progress.lock in
* the parent directory of the installation directory.
*
@@ -1701,20 +1715,20 @@ function runUpdateUsingUpdater(aExpected
let callbackApp = getApplyDirFile(DIR_RESOURCES + gCallbackBinFile);
callbackApp.permissions = PERMS_DIRECTORY;
setAppBundleModTime();
let args = [updatesDirPath, applyToDirPath];
if (aSwitchApp) {
- args[2] = stageDirPath;
+ args[2] = gApplyToDirOverride || stageDirPath;
args[3] = "0/replace";
} else {
- args[2] = applyToDirPath;
+ args[2] = gApplyToDirOverride || applyToDirPath;
args[3] = "0";
}
args = args.concat([callbackApp.parent.path, callbackApp.path]);
args = args.concat(gCallbackArgs);
debugDump("running the updater: " + updateBin.path + " " + args.join(" "));
// See bug 1279108.
// nsIProcess doesn't have an API to pass a separate environment to the
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marWrongApplyToDirFailure_win.js
@@ -0,0 +1,39 @@
+/* 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/.
+ */
+
+/* Test trying to use an apply-to directory different from the install
+ * directory, which should fail.
+ */
+
+function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+ overrideApplyToDir(getApplyDirPath() + "/../NoSuchDir");
+ // If execv is used the updater process will turn into the callback process
+ // and the updater's return code will be that of the callback process.
+ runUpdateUsingUpdater(STATE_FAILED_INVALID_APPLYTO_DIR_ERROR, false,
+ (USE_EXECV ? 0 : 1));
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ waitForFilesInUse();
+}
--- a/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
+++ b/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
@@ -99,8 +99,11 @@ reason = Windows only test and bug 12919
skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
reason = Windows only test and bug 1291985
[marAppApplyUpdateSuccess.js]
skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') || toolkit == 'gonk'
reason = bug 1291985 and bug 1164150
[marAppApplyUpdateStageSuccess.js]
skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') || toolkit == 'gonk'
reason = bug 1291985 and bug 1164150
+[marWrongApplyToDirFailure_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -54,16 +54,17 @@
#include "updatelogging.h"
#ifdef XP_MACOSX
#include "updaterfileutils_osx.h"
#endif // XP_MACOSX
#include "mozilla/Compiler.h"
#include "mozilla/Types.h"
+#include "mozilla/UniquePtr.h"
// Amount of the progress bar to use in each of the 3 update stages,
// should total 100.0.
#define PROGRESS_PREPARE_SIZE 20.0f
#define PROGRESS_EXECUTE_SIZE 75.0f
#define PROGRESS_FINISH_SIZE 5.0f
// Amount of time in ms to wait for the parent process to close
@@ -315,16 +316,17 @@ static bool sReplaceRequest = false;
static bool sUsingService = false;
static bool sIsOSUpdate = false;
#ifdef XP_WIN
// The current working directory specified in the command line.
static NS_tchar* gDestPath;
static NS_tchar gCallbackRelPath[MAXPATHLEN];
static NS_tchar gCallbackBackupPath[MAXPATHLEN];
+static NS_tchar gDeleteDirPath[MAXPATHLEN];
#endif
static const NS_tchar kWhitespace[] = NS_T(" \t");
static const NS_tchar kNL[] = NS_T("\r\n");
static const NS_tchar kQuote[] = NS_T("\"");
static inline size_t
mmin(size_t a, size_t b)
@@ -375,44 +377,76 @@ mstrtok(const NS_tchar *delims, NS_tchar
static bool
EnvHasValue(const char *name)
{
const char *val = getenv(name);
return (val && *val);
}
-#ifdef XP_WIN
/**
- * Coverts a relative update path to a full path for Windows.
+ * Coverts a relative update path to a full path.
*
* @param relpath
* The relative path to convert to a full path.
* @return valid filesystem full path or nullptr if memory allocation fails.
*/
static NS_tchar*
get_full_path(const NS_tchar *relpath)
{
- size_t lendestpath = NS_tstrlen(gDestPath);
+ NS_tchar *destpath = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
+ size_t lendestpath = NS_tstrlen(destpath);
size_t lenrelpath = NS_tstrlen(relpath);
- NS_tchar *s = (NS_tchar *) malloc((lendestpath + lenrelpath + 1) * sizeof(NS_tchar));
- if (!s)
+ NS_tchar *s = new NS_tchar[lendestpath + lenrelpath + 2];
+ if (!s) {
return nullptr;
+ }
NS_tchar *c = s;
- NS_tstrcpy(c, gDestPath);
+ NS_tstrcpy(c, destpath);
c += lendestpath;
+ NS_tstrcat(c, NS_T("/"));
+ c++;
+
NS_tstrcat(c, relpath);
c += lenrelpath;
*c = NS_T('\0');
- c++;
return s;
}
+
+/**
+ * Converts a full update path into a relative path; reverses get_full_path.
+ *
+ * @param fullpath
+ * The absolute path to convert into a relative path.
+ * return pointer to the location within fullpath where the relative path starts
+ * or fullpath itself if it already looks relative.
+ */
+static const NS_tchar*
+get_relative_path(const NS_tchar *fullpath)
+{
+ // If the path isn't absolute, just return it as-is.
+#ifdef XP_WIN
+ if (fullpath[1] != ':' && fullpath[2] != '\\') {
+#else
+ if (fullpath[0] != '/') {
#endif
+ return fullpath;
+ }
+
+ NS_tchar *prefix = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
+
+ // If the path isn't long enough to be absolute, return it as-is.
+ if (NS_tstrlen(fullpath) <= NS_tstrlen(prefix)) {
+ return fullpath;
+ }
+
+ return fullpath + NS_tstrlen(prefix) + 1;
+}
/**
* Gets the platform specific path and performs simple checks to the path. If
* the path checks don't pass nullptr will be returned.
*
* @param line
* The line from the manifest that contains the path.
* @param isdir
@@ -963,81 +997,90 @@ static int backup_create(const NS_tchar
NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
NS_T("%s") BACKUP_EXT, path);
return rename_file(path, backup);
}
// Rename the backup of the specified file that was created by renaming it back
// to the original file.
-static int backup_restore(const NS_tchar *path)
+static int backup_restore(const NS_tchar *path, const NS_tchar *relPath)
{
NS_tchar backup[MAXPATHLEN];
NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
NS_T("%s") BACKUP_EXT, path);
+ NS_tchar relBackup[MAXPATHLEN];
+ NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
+ NS_T("%s") BACKUP_EXT, relPath);
+
if (NS_taccess(backup, F_OK)) {
- LOG(("backup_restore: backup file doesn't exist: " LOG_S, backup));
+ LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup));
return OK;
}
return rename_file(backup, path);
}
// Discard the backup of the specified file that was created by renaming it.
-static int backup_discard(const NS_tchar *path)
+static int backup_discard(const NS_tchar *path, const NS_tchar *relPath)
{
NS_tchar backup[MAXPATHLEN];
NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
NS_T("%s") BACKUP_EXT, path);
+ NS_tchar relBackup[MAXPATHLEN];
+ NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
+ NS_T("%s") BACKUP_EXT, relPath);
+
// Nothing to discard
if (NS_taccess(backup, F_OK)) {
return OK;
}
int rv = ensure_remove(backup);
#if defined(XP_WIN)
if (rv && !sStagedUpdate && !sReplaceRequest) {
- LOG(("backup_discard: unable to remove: " LOG_S, backup));
+ LOG(("backup_discard: unable to remove: " LOG_S, relBackup));
NS_tchar path[MAXPATHLEN];
- GetTempFileNameW(DELETE_DIR, L"moz", 0, path);
+ GetTempFileNameW(gDeleteDirPath, L"moz", 0, path);
if (rename_file(backup, path)) {
LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S,
- backup, path));
+ relBackup, relPath));
return WRITE_ERROR_DELETE_BACKUP;
}
// The MoveFileEx call to remove the file on OS reboot will fail if the
// process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
// but this is ok since the installer / uninstaller will delete the
// directory containing the file along with its contents after an update is
// applied, on reinstall, and on uninstall.
if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
LOG(("backup_discard: file renamed and will be removed on OS " \
- "reboot: " LOG_S, path));
+ "reboot: " LOG_S, relPath));
} else {
LOG(("backup_discard: failed to schedule OS reboot removal of " \
- "file: " LOG_S, path));
+ "file: " LOG_S, relPath));
}
}
#else
if (rv)
return WRITE_ERROR_DELETE_BACKUP;
#endif
return OK;
}
// Helper function for post-processing a temporary backup.
-static void backup_finish(const NS_tchar *path, int status)
+static void backup_finish(const NS_tchar *path, const NS_tchar *relPath,
+ int status)
{
if (status == OK)
- backup_discard(path);
+ backup_discard(path, relPath);
else
- backup_restore(path);
+ backup_restore(path, relPath);
}
//-----------------------------------------------------------------------------
static int DoUpdate();
class Action
{
@@ -1065,71 +1108,81 @@ private:
Action* mNext;
friend class ActionList;
};
class RemoveFile : public Action
{
public:
- RemoveFile() : mFile(nullptr), mSkip(0) { }
+ RemoveFile() : mSkip(0) { }
int Parse(NS_tchar *line);
int Prepare();
int Execute();
void Finish(int status);
private:
- const NS_tchar *mFile;
+ mozilla::UniquePtr<NS_tchar[]> mFile;
+ mozilla::UniquePtr<NS_tchar[]> mRelPath;
int mSkip;
};
int
RemoveFile::Parse(NS_tchar *line)
{
// format "<deadfile>"
- mFile = get_valid_path(&line);
- if (!mFile)
+ NS_tchar * validPath = get_valid_path(&line);
+ if (!validPath) {
return PARSE_ERROR;
+ }
+
+ mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mRelPath.get(), validPath);
+
+ mFile.reset(get_full_path(validPath));
+ if (!mFile) {
+ return PARSE_ERROR;
+ }
return OK;
}
int
RemoveFile::Prepare()
{
// Skip the file if it already doesn't exist.
- int rv = NS_taccess(mFile, F_OK);
+ int rv = NS_taccess(mFile.get(), F_OK);
if (rv) {
mSkip = 1;
mProgressCost = 0;
return OK;
}
- LOG(("PREPARE REMOVEFILE " LOG_S, mFile));
+ LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get()));
// Make sure that we're actually a file...
struct NS_tstat_t fileInfo;
- rv = NS_tstat(mFile, &fileInfo);
+ rv = NS_tstat(mFile.get(), &fileInfo);
if (rv) {
- LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
+ LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(),
errno));
return READ_ERROR;
}
if (!S_ISREG(fileInfo.st_mode)) {
- LOG(("path present, but not a file: " LOG_S, mFile));
+ LOG(("path present, but not a file: " LOG_S, mFile.get()));
return DELETE_ERROR_EXPECTED_FILE;
}
- NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile, NS_T('/'));
+ NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile.get(), NS_T('/'));
if (slash) {
*slash = NS_T('\0');
- rv = NS_taccess(mFile, W_OK);
+ rv = NS_taccess(mFile.get(), W_OK);
*slash = NS_T('/');
} else {
rv = NS_taccess(NS_T("."), W_OK);
}
if (rv) {
LOG(("access failed: %d", errno));
return WRITE_ERROR_FILE_ACCESS_DENIED;
@@ -1139,259 +1192,278 @@ RemoveFile::Prepare()
}
int
RemoveFile::Execute()
{
if (mSkip)
return OK;
- LOG(("EXECUTE REMOVEFILE " LOG_S, mFile));
+ LOG(("EXECUTE REMOVEFILE " LOG_S, mRelPath.get()));
// The file is checked for existence here and in Prepare since it might have
// been removed by a separate instruction: bug 311099.
- int rv = NS_taccess(mFile, F_OK);
+ int rv = NS_taccess(mFile.get(), F_OK);
if (rv) {
LOG(("file cannot be removed because it does not exist; skipping"));
mSkip = 1;
return OK;
}
// Rename the old file. It will be removed in Finish.
- rv = backup_create(mFile);
+ rv = backup_create(mFile.get());
if (rv) {
LOG(("backup_create failed: %d", rv));
return rv;
}
return OK;
}
void
RemoveFile::Finish(int status)
{
if (mSkip)
return;
- LOG(("FINISH REMOVEFILE " LOG_S, mFile));
-
- backup_finish(mFile, status);
+ LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get()));
+
+ backup_finish(mFile.get(), mRelPath.get(), status);
}
class RemoveDir : public Action
{
public:
- RemoveDir() : mDir(nullptr), mSkip(0) { }
+ RemoveDir() : mSkip(0) { }
virtual int Parse(NS_tchar *line);
virtual int Prepare(); // check that the source dir exists
virtual int Execute();
virtual void Finish(int status);
private:
- const NS_tchar *mDir;
+ mozilla::UniquePtr<NS_tchar[]> mDir;
+ mozilla::UniquePtr<NS_tchar[]> mRelPath;
int mSkip;
};
int
RemoveDir::Parse(NS_tchar *line)
{
// format "<deaddir>/"
- mDir = get_valid_path(&line, true);
- if (!mDir)
+ NS_tchar * validPath = get_valid_path(&line, true);
+ if (!validPath) {
return PARSE_ERROR;
+ }
+
+ mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mRelPath.get(), validPath);
+
+ mDir.reset(get_full_path(validPath));
+ if (!mDir) {
+ return PARSE_ERROR;
+ }
return OK;
}
int
RemoveDir::Prepare()
{
// We expect the directory to exist if we are to remove it.
- int rv = NS_taccess(mDir, F_OK);
+ int rv = NS_taccess(mDir.get(), F_OK);
if (rv) {
mSkip = 1;
mProgressCost = 0;
return OK;
}
- LOG(("PREPARE REMOVEDIR " LOG_S "/", mDir));
+ LOG(("PREPARE REMOVEDIR " LOG_S "/", mRelPath.get()));
// Make sure that we're actually a dir.
struct NS_tstat_t dirInfo;
- rv = NS_tstat(mDir, &dirInfo);
+ rv = NS_tstat(mDir.get(), &dirInfo);
if (rv) {
- LOG(("failed to read directory status info: " LOG_S ", err: %d", mDir,
+ LOG(("failed to read directory status info: " LOG_S ", err: %d", mRelPath.get(),
errno));
return READ_ERROR;
}
if (!S_ISDIR(dirInfo.st_mode)) {
- LOG(("path present, but not a directory: " LOG_S, mDir));
+ LOG(("path present, but not a directory: " LOG_S, mRelPath.get()));
return DELETE_ERROR_EXPECTED_DIR;
}
- rv = NS_taccess(mDir, W_OK);
+ rv = NS_taccess(mDir.get(), W_OK);
if (rv) {
LOG(("access failed: %d, %d", rv, errno));
return WRITE_ERROR_DIR_ACCESS_DENIED;
}
return OK;
}
int
RemoveDir::Execute()
{
if (mSkip)
return OK;
- LOG(("EXECUTE REMOVEDIR " LOG_S "/", mDir));
+ LOG(("EXECUTE REMOVEDIR " LOG_S "/", mRelPath.get()));
// The directory is checked for existence at every step since it might have
// been removed by a separate instruction: bug 311099.
- int rv = NS_taccess(mDir, F_OK);
+ int rv = NS_taccess(mDir.get(), F_OK);
if (rv) {
LOG(("directory no longer exists; skipping"));
mSkip = 1;
}
return OK;
}
void
RemoveDir::Finish(int status)
{
if (mSkip || status != OK)
return;
- LOG(("FINISH REMOVEDIR " LOG_S "/", mDir));
+ LOG(("FINISH REMOVEDIR " LOG_S "/", mRelPath.get()));
// The directory is checked for existence at every step since it might have
// been removed by a separate instruction: bug 311099.
- int rv = NS_taccess(mDir, F_OK);
+ int rv = NS_taccess(mDir.get(), F_OK);
if (rv) {
LOG(("directory no longer exists; skipping"));
return;
}
if (status == OK) {
- if (NS_trmdir(mDir)) {
+ if (NS_trmdir(mDir.get())) {
LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d",
- mDir, rv, errno));
+ mRelPath.get(), rv, errno));
}
}
}
class AddFile : public Action
{
public:
- AddFile() : mFile(nullptr)
- , mAdded(false)
- { }
+ AddFile() : mAdded(false) { }
virtual int Parse(NS_tchar *line);
virtual int Prepare();
virtual int Execute();
virtual void Finish(int status);
private:
- const NS_tchar *mFile;
+ mozilla::UniquePtr<NS_tchar[]> mFile;
+ mozilla::UniquePtr<NS_tchar[]> mRelPath;
bool mAdded;
};
int
AddFile::Parse(NS_tchar *line)
{
// format "<newfile>"
- mFile = get_valid_path(&line);
- if (!mFile)
+ NS_tchar * validPath = get_valid_path(&line);
+ if (!validPath) {
return PARSE_ERROR;
+ }
+
+ mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mRelPath.get(), validPath);
+
+ mFile.reset(get_full_path(validPath));
+ if (!mFile) {
+ return PARSE_ERROR;
+ }
return OK;
}
int
AddFile::Prepare()
{
- LOG(("PREPARE ADD " LOG_S, mFile));
+ LOG(("PREPARE ADD " LOG_S, mRelPath.get()));
return OK;
}
int
AddFile::Execute()
{
- LOG(("EXECUTE ADD " LOG_S, mFile));
+ LOG(("EXECUTE ADD " LOG_S, mRelPath.get()));
int rv;
// First make sure that we can actually get rid of any existing file.
- rv = NS_taccess(mFile, F_OK);
+ rv = NS_taccess(mFile.get(), F_OK);
if (rv == 0) {
- rv = backup_create(mFile);
+ rv = backup_create(mFile.get());
if (rv)
return rv;
} else {
- rv = ensure_parent_dir(mFile);
+ rv = ensure_parent_dir(mFile.get());
if (rv)
return rv;
}
#ifdef XP_WIN
char sourcefile[MAXPATHLEN];
- if (!WideCharToMultiByte(CP_UTF8, 0, mFile, -1, sourcefile, MAXPATHLEN,
- nullptr, nullptr)) {
+ if (!WideCharToMultiByte(CP_UTF8, 0, mRelPath.get(), -1, sourcefile,
+ MAXPATHLEN, nullptr, nullptr)) {
LOG(("error converting wchar to utf8: %d", GetLastError()));
return STRING_CONVERSION_ERROR;
}
- rv = gArchiveReader.ExtractFile(sourcefile, mFile);
+ rv = gArchiveReader.ExtractFile(sourcefile, mFile.get());
#else
- rv = gArchiveReader.ExtractFile(mFile, mFile);
+ rv = gArchiveReader.ExtractFile(mRelPath.get(), mFile.get());
#endif
if (!rv) {
mAdded = true;
}
return rv;
}
void
AddFile::Finish(int status)
{
- LOG(("FINISH ADD " LOG_S, mFile));
+ LOG(("FINISH ADD " LOG_S, mRelPath.get()));
// When there is an update failure and a file has been added it is removed
// here since there might not be a backup to replace it.
if (status && mAdded)
- NS_tremove(mFile);
- backup_finish(mFile, status);
+ NS_tremove(mFile.get());
+ backup_finish(mFile.get(), mRelPath.get(), status);
}
class PatchFile : public Action
{
public:
- PatchFile() : mPatchIndex(-1), buf(nullptr) { }
+ PatchFile() : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr) { }
virtual ~PatchFile();
virtual int Parse(NS_tchar *line);
virtual int Prepare(); // should check for patch file and for checksum here
virtual int Execute();
virtual void Finish(int status);
private:
int LoadSourceFile(FILE* ofile);
static int sPatchIndex;
const NS_tchar *mPatchFile;
- const NS_tchar *mFile;
+ mozilla::UniquePtr<NS_tchar[]> mFile;
+ mozilla::UniquePtr<NS_tchar[]> mFileRelPath;
int mPatchIndex;
MBSPatchHeader header;
unsigned char *buf;
NS_tchar spath[MAXPATHLEN];
AutoFile mPatchStream;
};
int PatchFile::sPatchIndex = 0;
@@ -1420,17 +1492,17 @@ PatchFile::~PatchFile()
int
PatchFile::LoadSourceFile(FILE* ofile)
{
struct stat os;
int rv = fstat(fileno((FILE *)ofile), &os);
if (rv) {
LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \
- "err: %d", mFile, errno));
+ "err: %d", mFileRelPath.get(), errno));
return READ_ERROR;
}
if (uint32_t(os.st_size) != header.slen) {
LOG(("LoadSourceFile: destination file size %d does not match expected size %d",
uint32_t(os.st_size), header.slen));
return LOADSOURCE_ERROR_WRONG_SIZE;
}
@@ -1442,17 +1514,17 @@ PatchFile::LoadSourceFile(FILE* ofile)
size_t r = header.slen;
unsigned char *rb = buf;
while (r) {
const size_t count = mmin(SSIZE_MAX, r);
size_t c = fread(rb, 1, count, ofile);
if (c != count) {
LOG(("LoadSourceFile: error reading destination file: " LOG_S,
- mFile));
+ mFileRelPath.get()));
return READ_ERROR;
}
r -= c;
rb += c;
}
// Verify that the contents of the source file correspond to what we expect.
@@ -1480,28 +1552,36 @@ PatchFile::Parse(NS_tchar *line)
}
// consume whitespace between args
NS_tchar *q = mstrtok(kQuote, &line);
if (!q) {
return PARSE_ERROR;
}
- mFile = get_valid_path(&line);
+ NS_tchar * validPath = get_valid_path(&line);
+ if (!validPath) {
+ return PARSE_ERROR;
+ }
+
+ mFileRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mFileRelPath.get(), validPath);
+
+ mFile.reset(get_full_path(validPath));
if (!mFile) {
return PARSE_ERROR;
}
return OK;
}
int
PatchFile::Prepare()
{
- LOG(("PREPARE PATCH " LOG_S, mFile));
+ LOG(("PREPARE PATCH " LOG_S, mFileRelPath.get()));
// extract the patch to a temporary file
mPatchIndex = sPatchIndex++;
NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]),
NS_T("%s/updating/%d.patch"), gWorkingDirPath, mPatchIndex);
NS_tremove(spath);
@@ -1532,118 +1612,119 @@ PatchFile::Prepare()
#endif
return rv;
}
int
PatchFile::Execute()
{
- LOG(("EXECUTE PATCH " LOG_S, mFile));
+ LOG(("EXECUTE PATCH " LOG_S, mFileRelPath.get()));
fseek(mPatchStream, 0, SEEK_SET);
int rv = MBS_ReadHeader(mPatchStream, &header);
if (rv) {
return rv;
}
FILE *origfile = nullptr;
#ifdef XP_WIN
- if (NS_tstrcmp(mFile, gCallbackRelPath) == 0) {
+ if (NS_tstrcmp(mFileRelPath.get(), gCallbackRelPath) == 0) {
// Read from the copy of the callback when patching since the callback can't
// be opened for reading to prevent the application from being launched.
origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb"));
} else {
- origfile = NS_tfopen(mFile, NS_T("rb"));
+ origfile = NS_tfopen(mFile.get(), NS_T("rb"));
}
#else
- origfile = NS_tfopen(mFile, NS_T("rb"));
+ origfile = NS_tfopen(mFile.get(), NS_T("rb"));
#endif
if (!origfile) {
- LOG(("unable to open destination file: " LOG_S ", err: %d", mFile,
- errno));
+ LOG(("unable to open destination file: " LOG_S ", err: %d",
+ mFileRelPath.get(), errno));
return READ_ERROR;
}
rv = LoadSourceFile(origfile);
fclose(origfile);
if (rv) {
LOG(("LoadSourceFile failed"));
return rv;
}
// Rename the destination file if it exists before proceeding so it can be
// used to restore the file to its original state if there is an error.
struct NS_tstat_t ss;
- rv = NS_tstat(mFile, &ss);
+ rv = NS_tstat(mFile.get(), &ss);
if (rv) {
- LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
- errno));
+ LOG(("failed to read file status info: " LOG_S ", err: %d",
+ mFileRelPath.get(), errno));
return READ_ERROR;
}
- rv = backup_create(mFile);
+ rv = backup_create(mFile.get());
if (rv) {
return rv;
}
#if defined(HAVE_POSIX_FALLOCATE)
- AutoFile ofile(ensure_open(mFile, NS_T("wb+"), ss.st_mode));
+ AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
posix_fallocate(fileno((FILE *)ofile), 0, header.dlen);
#elif defined(XP_WIN)
bool shouldTruncate = true;
// Creating the file, setting the size, and then closing the file handle
// lessens fragmentation more than any other method tested. Other methods that
// have been tested are:
// 1. _chsize / _chsize_s reduced fragmentation though not completely.
// 2. _get_osfhandle and then setting the size reduced fragmentation though
// not completely. There are also reports of _get_osfhandle failing on
// mingw.
- HANDLE hfile = CreateFileW(mFile,
+ HANDLE hfile = CreateFileW(mFile.get(),
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (hfile != INVALID_HANDLE_VALUE) {
if (SetFilePointer(hfile, header.dlen,
nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER &&
SetEndOfFile(hfile) != 0) {
shouldTruncate = false;
}
CloseHandle(hfile);
}
- AutoFile ofile(ensure_open(mFile, shouldTruncate ? NS_T("wb+") : NS_T("rb+"),
+ AutoFile ofile(ensure_open(mFile.get(), shouldTruncate ? NS_T("wb+") : NS_T("rb+"),
ss.st_mode));
#elif defined(XP_MACOSX)
- AutoFile ofile(ensure_open(mFile, NS_T("wb+"), ss.st_mode));
+ AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
// Modified code from FileUtils.cpp
fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen};
// Try to get a continous chunk of disk space
rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
if (rv == -1) {
// OK, perhaps we are too fragmented, allocate non-continuous
store.fst_flags = F_ALLOCATEALL;
rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
}
if (rv != -1) {
ftruncate(fileno((FILE *)ofile), header.dlen);
}
#else
- AutoFile ofile(ensure_open(mFile, NS_T("wb+"), ss.st_mode));
+ AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
#endif
if (ofile == nullptr) {
- LOG(("unable to create new file: " LOG_S ", err: %d", mFile, errno));
+ LOG(("unable to create new file: " LOG_S ", err: %d", mFileRelPath.get(),
+ errno));
return WRITE_ERROR_OPEN_PATCH_FILE;
}
#ifdef XP_WIN
if (!shouldTruncate) {
fseek(ofile, 0, SEEK_SET);
}
#endif
@@ -1665,57 +1746,57 @@ PatchFile::Execute()
buf = nullptr;
return rv;
}
void
PatchFile::Finish(int status)
{
- LOG(("FINISH PATCH " LOG_S, mFile));
-
- backup_finish(mFile, status);
+ LOG(("FINISH PATCH " LOG_S, mFileRelPath.get()));
+
+ backup_finish(mFile.get(), mFileRelPath.get(), status);
}
class AddIfFile : public AddFile
{
public:
- AddIfFile() : mTestFile(nullptr) { }
-
virtual int Parse(NS_tchar *line);
virtual int Prepare();
virtual int Execute();
virtual void Finish(int status);
protected:
- const NS_tchar *mTestFile;
+ mozilla::UniquePtr<NS_tchar[]> mTestFile;
};
int
AddIfFile::Parse(NS_tchar *line)
{
// format "<testfile>" "<newfile>"
- mTestFile = get_valid_path(&line);
- if (!mTestFile)
+ mTestFile.reset(get_full_path(get_valid_path(&line)));
+ if (!mTestFile) {
return PARSE_ERROR;
+ }
// consume whitespace between args
NS_tchar *q = mstrtok(kQuote, &line);
- if (!q)
+ if (!q) {
return PARSE_ERROR;
+ }
return AddFile::Parse(line);
}
int
AddIfFile::Prepare()
{
// If the test file does not exist, then skip this action.
- if (NS_taccess(mTestFile, F_OK)) {
+ if (NS_taccess(mTestFile.get(), F_OK)) {
mTestFile = nullptr;
return OK;
}
return AddFile::Prepare();
}
int
@@ -1734,49 +1815,49 @@ AddIfFile::Finish(int status)
return;
AddFile::Finish(status);
}
class AddIfNotFile : public AddFile
{
public:
- AddIfNotFile() : mTestFile(NULL) { }
-
virtual int Parse(NS_tchar *line);
virtual int Prepare();
virtual int Execute();
virtual void Finish(int status);
protected:
- const NS_tchar *mTestFile;
+ mozilla::UniquePtr<NS_tchar[]> mTestFile;
};
int
AddIfNotFile::Parse(NS_tchar *line)
{
// format "<testfile>" "<newfile>"
- mTestFile = get_valid_path(&line);
- if (!mTestFile)
+ mTestFile.reset(get_full_path(get_valid_path(&line)));
+ if (!mTestFile) {
return PARSE_ERROR;
+ }
// consume whitespace between args
NS_tchar *q = mstrtok(kQuote, &line);
- if (!q)
+ if (!q) {
return PARSE_ERROR;
+ }
return AddFile::Parse(line);
}
int
AddIfNotFile::Prepare()
{
// If the test file exists, then skip this action.
- if (!NS_taccess(mTestFile, F_OK)) {
+ if (!NS_taccess(mTestFile.get(), F_OK)) {
mTestFile = NULL;
return OK;
}
return AddFile::Prepare();
}
int
@@ -1795,49 +1876,49 @@ AddIfNotFile::Finish(int status)
return;
AddFile::Finish(status);
}
class PatchIfFile : public PatchFile
{
public:
- PatchIfFile() : mTestFile(nullptr) { }
-
virtual int Parse(NS_tchar *line);
virtual int Prepare(); // should check for patch file and for checksum here
virtual int Execute();
virtual void Finish(int status);
private:
- const NS_tchar *mTestFile;
+ mozilla::UniquePtr<NS_tchar[]> mTestFile;
};
int
PatchIfFile::Parse(NS_tchar *line)
{
// format "<testfile>" "<patchfile>" "<filetopatch>"
- mTestFile = get_valid_path(&line);
- if (!mTestFile)
+ mTestFile.reset(get_full_path(get_valid_path(&line)));
+ if (!mTestFile) {
return PARSE_ERROR;
+ }
// consume whitespace between args
NS_tchar *q = mstrtok(kQuote, &line);
- if (!q)
+ if (!q) {
return PARSE_ERROR;
+ }
return PatchFile::Parse(line);
}
int
PatchIfFile::Prepare()
{
// If the test file does not exist, then skip this action.
- if (NS_taccess(mTestFile, F_OK)) {
+ if (NS_taccess(mTestFile.get(), F_OK)) {
mTestFile = nullptr;
return OK;
}
return PatchFile::Prepare();
}
int
@@ -2510,18 +2591,17 @@ UpdateThreadFunc(void *param)
NS_T("%s/updating"), gWorkingDirPath);
ensure_remove_recursive(updatingDir);
}
}
if (rv && (sReplaceRequest || sStagedUpdate)) {
#ifdef XP_WIN
// On Windows, the current working directory of the process should be changed
- // so that it's not locked. The working directory for staging an update was
- // already changed earlier.
+ // so that it's not locked.
if (sStagedUpdate) {
NS_tchar sysDir[MAX_PATH + 1] = { L'\0' };
if (GetSystemDirectoryW(sysDir, MAX_PATH + 1)) {
NS_tchdir(sysDir);
}
}
#endif
ensure_remove_recursive(gWorkingDirPath);
@@ -2846,31 +2926,40 @@ int NS_main(int argc, NS_tchar **argv)
LOG(("Performing a replace request"));
}
LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath));
LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath));
LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath));
#if defined(XP_WIN)
- if (sReplaceRequest || sStagedUpdate) {
- NS_tchar stagedParent[MAX_PATH];
- NS_tsnprintf(stagedParent, sizeof(stagedParent)/sizeof(stagedParent[0]),
+ if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0) {
+ if (!sStagedUpdate && !sReplaceRequest) {
+ WriteStatusFile(INVALID_APPLYTO_DIR_ERROR);
+ LOG(("Installation directory and working directory must be the same "
+ "for non-staged updates. Exiting."));
+ LogFinish();
+ return 1;
+ }
+
+ NS_tchar workingDirParent[MAX_PATH];
+ NS_tsnprintf(workingDirParent,
+ sizeof(workingDirParent) / sizeof(workingDirParent[0]),
NS_T("%s"), gWorkingDirPath);
- if (!PathRemoveFileSpecW(stagedParent)) {
+ if (!PathRemoveFileSpecW(workingDirParent)) {
WriteStatusFile(REMOVE_FILE_SPEC_ERROR);
LOG(("Error calling PathRemoveFileSpecW: %d", GetLastError()));
LogFinish();
return 1;
}
- if (_wcsnicmp(stagedParent, gInstallDirPath, MAX_PATH) != 0) {
- WriteStatusFile(INVALID_STAGED_PARENT_ERROR);