diff options
author | Pale Moon <git-repo@palemoon.org> | 2017-11-01 18:34:25 +0100 |
---|---|---|
committer | Pale Moon <git-repo@palemoon.org> | 2017-11-01 18:34:25 +0100 |
commit | e696002451dc80420ee293a73758956a7a506e3b (patch) | |
tree | a632be211c046bfc49ed66cfc28679f5b4d41177 | |
parent | 9a44165b281d3f6eab019e18dd64ba6dc19bf286 (diff) | |
parent | 0c11ba193b70b6acfa89e9100eec426a735ee7bd (diff) | |
download | palemoon-e696002451dc80420ee293a73758956a7a506e3b.tar.gz |
Merge branch 'master' into 27.6_RelBranch
39 files changed, 1779 insertions, 153 deletions
diff --git a/browser/base/content/safeMode.js b/browser/base/content/safeMode.js index 9cd6104d1..e1e5c7285 100644 --- a/browser/base/content/safeMode.js +++ b/browser/base/content/safeMode.js @@ -1,7 +1,6 @@ -# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -# 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/. +/* 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/. */ const Cc = Components.classes, Ci = Components.interfaces, diff --git a/browser/base/content/safeMode.xul b/browser/base/content/safeMode.xul index 6dccd14f6..656df6eaf 100644 --- a/browser/base/content/safeMode.xul +++ b/browser/base/content/safeMode.xul @@ -1,9 +1,9 @@ <?xml version="1.0"?> -# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -# 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/. +<!-- +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/. --> <!DOCTYPE prefwindow [ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" > diff --git a/browser/base/jar.mn b/browser/base/jar.mn index 49d83da4a..9031f3beb 100644 --- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -99,7 +99,7 @@ browser.jar: content/browser/openLocation.js (content/openLocation.js) content/browser/openLocation.xul (content/openLocation.xul) content/browser/safeMode.css (content/safeMode.css) -* content/browser/safeMode.js (content/safeMode.js) + content/browser/safeMode.js (content/safeMode.js) * content/browser/safeMode.xul (content/safeMode.xul) * content/browser/sanitize.js (content/sanitize.js) * content/browser/sanitize.xul (content/sanitize.xul) diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index c94e3cf23..9d819ab95 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -1363,6 +1363,21 @@ Element::GetElementsByClassName(const nsAString& aClassNames, return NS_OK; } +/** + * Returns the count of descendants (inclusive of aContent) in + * the uncomposed document that are explicitly set as editable. + */ +static uint32_t +EditableInclusiveDescendantCount(nsIContent* aContent) +{ + auto htmlElem = nsGenericHTMLElement::FromContent(aContent); + if (htmlElem) { + return htmlElem->EditableInclusiveDescendantCount(); + } + + return aContent->EditableDescendantCount(); +} + nsresult Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, @@ -1435,6 +1450,8 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, bool hadForceXBL = HasFlag(NODE_FORCE_XBL_BINDINGS); + bool hadParent = !!GetParentNode(); + // Now set the parent and set the "Force attach xbl" flag if needed. if (aParent) { if (!GetParent()) { @@ -1526,6 +1543,8 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, SetDirOnBind(this, aParent); } + uint32_t editableDescendantCount = 0; + // If NODE_FORCE_XBL_BINDINGS was set we might have anonymous children // that also need to be told that they are moving. nsresult rv; @@ -1542,6 +1561,8 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, child = child->GetNextSibling()) { rv = child->BindToTree(aDocument, this, this, allowScripts); NS_ENSURE_SUCCESS(rv, rv); + + editableDescendantCount += EditableInclusiveDescendantCount(child); } } } @@ -1554,6 +1575,28 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, rv = child->BindToTree(aDocument, this, aBindingParent, aCompileEventHandlers); NS_ENSURE_SUCCESS(rv, rv); + + editableDescendantCount += EditableInclusiveDescendantCount(child); + } + + if (aDocument) { + // Update our editable descendant count because we don't keep track of it + // for content that is not in the uncomposed document. + MOZ_ASSERT(EditableDescendantCount() == 0); + ChangeEditableDescendantCount(editableDescendantCount); + + if (!hadParent) { + uint32_t editableDescendantChange = EditableInclusiveDescendantCount(this); + if (editableDescendantChange != 0) { + // If we are binding a subtree root to the document, we need to update + // the editable descendant count of all the ancestors. + nsIContent* parent = GetParent(); + while (parent) { + parent->ChangeEditableDescendantCount(editableDescendantChange); + parent = parent->GetParent(); + } + } + } } nsNodeUtils::ParentChainChanged(this); @@ -1657,6 +1700,20 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent) if (HasPointerLock()) { nsIDocument::UnlockPointer(); } + + if (GetParent() && GetParent()->IsInUncomposedDoc()) { + // Update the editable descendant count in the ancestors before we + // lose the reference to the parent. + int32_t editableDescendantChange = -1 * EditableInclusiveDescendantCount(this); + if (editableDescendantChange != 0) { + nsIContent* parent = GetParent(); + while (parent) { + parent->ChangeEditableDescendantCount(editableDescendantChange); + parent = parent->GetParent(); + } + } + } + if (GetParent()) { nsINode* p = mParent; mParent = nullptr; @@ -1668,6 +1725,10 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent) } ClearInDocument(); + // Editable descendant count only counts descendants that + // are in the uncomposed document. + ResetEditableDescendantCount(); + if (aNullParent || !mParent->IsInShadowTree()) { UnsetFlags(NODE_IS_IN_SHADOW_TREE); diff --git a/dom/base/nsGlobalWindowCommands.cpp b/dom/base/nsGlobalWindowCommands.cpp index 743e1b8cb..29b3b1656 100644 --- a/dom/base/nsGlobalWindowCommands.cpp +++ b/dom/base/nsGlobalWindowCommands.cpp @@ -486,7 +486,8 @@ nsClipboardCommand::IsCommandEnabled(const char* aCommandName, nsISupports *aCon // Careful: inverse logic. if (strcmp(aCommandName, "cmd_copy") && strcmp(aCommandName, "cmd_copyAndCollapseToEnd") && - strcmp(aCommandName, "cmd_cut")) + strcmp(aCommandName, "cmd_cut") && + strcmp(aCommandName, "cmd_paste")) return NS_OK; nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aContext); @@ -494,11 +495,13 @@ nsClipboardCommand::IsCommandEnabled(const char* aCommandName, nsISupports *aCon nsCOMPtr<nsIDocument> doc = window->GetExtantDoc(); if (doc->IsHTMLOrXHTML()) { - // In HTML and XHTML documents, we always want cut and copy commands to be enabled. + // In HTML and XHTML documents, we always want cut, copy and paste + // commands to be enabled. *outCmdEnabled = true; } else { // Cut isn't enabled in xul documents which use nsClipboardCommand - if (strcmp(aCommandName, "cmd_cut")) { + if (strcmp(aCommandName, "cmd_copy") == 0 || + strcmp(aCommandName, "cmd_copyAndCollapseToEnd") == 0) { *outCmdEnabled = nsCopySupport::CanCopy(doc); } } @@ -511,7 +514,8 @@ nsClipboardCommand::DoCommand(const char *aCommandName, nsISupports *aContext) // Careful: inverse logic. if (strcmp(aCommandName, "cmd_cut") && strcmp(aCommandName, "cmd_copy") && - strcmp(aCommandName, "cmd_copyAndCollapseToEnd")) + strcmp(aCommandName, "cmd_copyAndCollapseToEnd") && + strcmp(aCommandName, "cmd_paste")) return NS_OK; nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aContext); @@ -526,12 +530,17 @@ nsClipboardCommand::DoCommand(const char *aCommandName, nsISupports *aContext) int32_t eventType = NS_COPY; if (strcmp(aCommandName, "cmd_cut") == 0) { eventType = NS_CUT; + } else if (strcmp(aCommandName, "cmd_paste") == 0) { + eventType = NS_PASTE; } bool actionTaken = false; - nsCopySupport::FireClipboardEvent(eventType, nsIClipboard::kGlobalClipboard, presShell, nullptr, &actionTaken); + bool notCancelled = + nsCopySupport::FireClipboardEvent(eventType, + nsIClipboard::kGlobalClipboard, + presShell, nullptr, &actionTaken); - if (!strcmp(aCommandName, "cmd_copyAndCollapseToEnd")) { + if (notCancelled && !strcmp(aCommandName, "cmd_copyAndCollapseToEnd")) { dom::Selection *sel = presShell->GetCurrentSelection(nsISelectionController::SELECTION_NORMAL); NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE); diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index 75c03eef5..d6859c44e 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -1320,6 +1320,38 @@ nsINode::GetOwnerGlobal() const return OwnerDoc()->GetScriptHandlingObject(dummy); } +void +nsINode::ChangeEditableDescendantCount(int32_t aDelta) +{ + if (aDelta == 0) { + return; + } + + nsSlots* s = Slots(); + MOZ_ASSERT(aDelta > 0 || + s->mEditableDescendantCount >= (uint32_t) (-1 * aDelta)); + s->mEditableDescendantCount += aDelta; +} + +void +nsINode::ResetEditableDescendantCount() +{ + nsSlots* s = GetExistingSlots(); + if (s) { + s->mEditableDescendantCount = 0; + } +} + +uint32_t +nsINode::EditableDescendantCount() +{ + nsSlots* s = GetExistingSlots(); + if (s) { + return s->mEditableDescendantCount; + } + return 0; +} + bool nsINode::UnoptimizableCCNode() const { diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h index 3d9d5cb8d..cbe946b4a 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -920,9 +920,10 @@ public: class nsSlots { public: - nsSlots() - : mChildNodes(nullptr), - mWeakReference(nullptr) + nsSlots() + : mChildNodes(nullptr), + mWeakReference(nullptr), + mEditableDescendantCount(0) { } @@ -951,6 +952,12 @@ public: * Weak reference to this node */ nsNodeWeakReference* mWeakReference; + + /** + * Number of descendant nodes in the uncomposed document that have been + * explicitly set as editable. + */ + uint32_t mEditableDescendantCount; }; /** @@ -986,6 +993,22 @@ public: nsWrapperCache::UnsetFlags(aFlagsToUnset); } + void ChangeEditableDescendantCount(int32_t aDelta); + + /** + * Returns the count of descendant nodes in the uncomposed + * document that are explicitly set as editable. + */ + uint32_t EditableDescendantCount(); + + /** + * Sets the editable descendant count to 0. The editable + * descendant count only counts explicitly editable nodes + * that are in the uncomposed document so this method + * should be called when nodes are are removed from it. + */ + void ResetEditableDescendantCount(); + void SetEditableFlag(bool aEditable) { if (aEditable) { diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index e81e5f652..e7045e3af 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -526,6 +526,14 @@ nsGenericHTMLElement::IntrinsicState() const return state; } +uint32_t +nsGenericHTMLElement::EditableInclusiveDescendantCount() +{ + bool isEditable = IsInUncomposedDoc() && HasFlag(NODE_IS_EDITABLE) && + GetContentEditableValue() == eTrue; + return EditableDescendantCount() + isEditable; +} + nsresult nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, @@ -549,6 +557,7 @@ nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, aDocument-> AddToNameTable(this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue()); } + if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue) { nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(aDocument); if (htmlDocument) { @@ -2912,6 +2921,12 @@ nsGenericHTMLElement::ChangeEditableState(int32_t aChange) if (htmlDocument) { htmlDocument->ChangeContentEditableCount(this, aChange); } + + nsIContent* parent = GetParent(); + while (parent) { + parent->ChangeEditableDescendantCount(aChange); + parent = parent->GetParent(); + } } if (document->HasFlag(NODE_IS_EDITABLE)) { diff --git a/dom/html/nsGenericHTMLElement.h b/dom/html/nsGenericHTMLElement.h index 2a198ac3a..f7717f785 100644 --- a/dom/html/nsGenericHTMLElement.h +++ b/dom/html/nsGenericHTMLElement.h @@ -215,6 +215,13 @@ public: } return false; } + + /** + * Returns the count of descendants (inclusive of this node) in + * the uncomposed document that are explicitly set as editable. + */ + uint32_t EditableInclusiveDescendantCount(); + mozilla::dom::HTMLMenuElement* GetContextMenu() const; bool Spellcheck(); void SetSpellcheck(bool aSpellcheck, mozilla::ErrorResult& aError) diff --git a/editor/libeditor/tests/mochitest.ini b/editor/libeditor/tests/mochitest.ini index 96ffb7da9..bce1fb759 100644 --- a/editor/libeditor/tests/mochitest.ini +++ b/editor/libeditor/tests/mochitest.ini @@ -156,4 +156,6 @@ skip-if = toolkit == 'android' [test_bug1068979.html] [test_bug1109465.html] [test_bug1162952.html] +[test_bug1181130-1.html] +[test_bug1181130-2.html] [test_bug1352799.html] diff --git a/editor/libeditor/tests/test_bug1181130-1.html b/editor/libeditor/tests/test_bug1181130-1.html new file mode 100644 index 000000000..eb27526a3 --- /dev/null +++ b/editor/libeditor/tests/test_bug1181130-1.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1181130 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1181130</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181130">Mozilla Bug 1181130</a> +<p id="display"></p> +<div id="container" contenteditable="true"> + editable div + <div id="noneditable" contenteditable="false"> + non-editable div + <div id="editable" contenteditable="true">nested editable div</div> + </div> +</div> +<script type="application/javascript"> +/** Test for Bug 1181130 **/ +var container = document.getElementById("container"); +var noneditable = document.getElementById("noneditable"); +var editable = document.getElementById("editable"); + +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(function() { + synthesizeMouseAtCenter(noneditable, {}); + ok(!document.getSelection().toString().includes("nested editable div"), + "Selection should not include non-editable content"); + + synthesizeMouseAtCenter(container, {}); + ok(!document.getSelection().toString().includes("nested editable div"), + "Selection should not include non-editable content"); + + synthesizeMouseAtCenter(editable, {}); + ok(!document.getSelection().toString().includes("nested editable div"), + "Selection should not include non-editable content"); + + SimpleTest.finish(); +}); +</script> +<pre id="test"> +</pre> +</body> +</html> diff --git a/editor/libeditor/tests/test_bug1181130-2.html b/editor/libeditor/tests/test_bug1181130-2.html new file mode 100644 index 000000000..edb380e98 --- /dev/null +++ b/editor/libeditor/tests/test_bug1181130-2.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1181130 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1181130</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1181130">Mozilla Bug 1181130</a> +<p id="display"></p> +<div id="container" contenteditable="true"> + editable div + <div id="noneditable" contenteditable="false"> + non-editable div + </div> +</div> +<script type="application/javascript"> +/** Test for Bug 1181130 **/ +var container = document.getElementById("container"); +var noneditable = document.getElementById("noneditable"); + +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(function() { + var nonHTMLElement = document.createElementNS("http://www.example.com", "element"); + nonHTMLElement.innerHTML = '<div contenteditable="true">nested editable div</div>'; + noneditable.appendChild(nonHTMLElement); + + synthesizeMouseAtCenter(noneditable, {}); + ok(!document.getSelection().toString().includes("nested editable div"), + "Selection should not include non-editable content"); + + SimpleTest.finish(); +}); +</script> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/base/tests/bug1132768-1-ref.html b/layout/base/tests/bug1132768-1-ref.html new file mode 100644 index 000000000..0fea14cf0 --- /dev/null +++ b/layout/base/tests/bug1132768-1-ref.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> + <script> + function test() { + focus(); + getSelection().selectAllChildren(document.querySelector("span")); + } + </script> + <body onload="test()"> + <div>foo<span>bar</span>baz</div> + </body> +</html> diff --git a/layout/base/tests/bug1132768-1.html b/layout/base/tests/bug1132768-1.html new file mode 100644 index 000000000..dafa3aa3e --- /dev/null +++ b/layout/base/tests/bug1132768-1.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html class="reftest-wait"> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script> + function test() { + focus(); + synthesizeMouseAtCenter(document.querySelector("span"), {}); + } + function focused() { + document.documentElement.removeAttribute("class"); + } + </script> + <body onload="setTimeout(test, 0)"> + <div contenteditable spellcheck="false" onfocus="focused()" + style="outline: none">foo<span contenteditable=false>bar</span>baz</div> + </body> +</html> diff --git a/layout/base/tests/mochitest.ini b/layout/base/tests/mochitest.ini index 54adc0aec..06563356a 100644 --- a/layout/base/tests/mochitest.ini +++ b/layout/base/tests/mochitest.ini @@ -29,6 +29,8 @@ support-files = bug1123067-2.html bug1123067-3.html bug1123067-ref.html + bug1132768-1.html + bug1132768-1-ref.html selection-utils.js multi-range-user-select.html multi-range-user-select-ref.html diff --git a/layout/base/tests/test_reftests_with_caret.html b/layout/base/tests/test_reftests_with_caret.html index 4962b81dc..77a9502d1 100644 --- a/layout/base/tests/test_reftests_with_caret.html +++ b/layout/base/tests/test_reftests_with_caret.html @@ -108,9 +108,11 @@ var tests = [ [ 'bug1082486-1.html', 'bug1082486-1-ref.html'] , [ 'bug1082486-2.html', 'bug1082486-2-ref.html'] , // The following test cases are all involving with one sending - // synthesizeKey(), the other without. They ought to be failed - // when touch caret preference on. Test them with preference off. + // synthesizeKey(), the other without. They fail when the touch + // or selection caret is enabled. Test them with these preferences off. function() {SpecialPowers.pushPrefEnv({'set': [['touchcaret.enabled', false]]}, nextTest);} , + function() {SpecialPowers.pushPrefEnv({'set': [['selectioncaret.enabled', false]]}, nextTest);} , + [ 'bug240933-1.html' , 'bug240933-1-ref.html' ] , [ 'bug240933-1.html' , 'bug240933-1-ref.html' ] , [ 'bug240933-2.html' , 'bug240933-1-ref.html' ] , [ 'bug389321-1.html' , 'bug389321-1-ref.html' ] , @@ -153,7 +155,9 @@ var tests = [ // [ 'bug1123067-1.html' , 'bug1123067-ref.html' ] , TODO: bug 1129205 [ 'bug1123067-2.html' , 'bug1123067-ref.html' ] , [ 'bug1123067-3.html' , 'bug1123067-ref.html' ] , + [ 'bug1132768-1.html' , 'bug1132768-1-ref.html'] , function() {SpecialPowers.pushPrefEnv({'clear': [['touchcaret.enabled']]}, nextTest);} , + function() {SpecialPowers.pushPrefEnv({'clear': [['selectioncaret.enabled']]}, nextTest);} , ]; if (navigator.appVersion.indexOf("Android") == -1 && diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index fb1788fd2..37b3349cf 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -2708,23 +2708,35 @@ nsFrame::IsSelectable(bool* aSelectable, uint8_t* aSelectStyle) const // are present in the frame hierarchy, aSelectStyle returns the style of the // topmost parent that has either 'none' or '-moz-all'. // + // The -moz-text value acts as a way to override an ancestor's all/-moz-all value. + // // For instance, if the frame hierarchy is: - // AUTO -> _MOZ_ALL -> NONE -> TEXT, the returned value is _MOZ_ALL - // TEXT -> NONE -> AUTO -> _MOZ_ALL, the returned value is TEXT - // _MOZ_ALL -> TEXT -> AUTO -> AUTO, the returned value is _MOZ_ALL - // AUTO -> CELL -> TEXT -> AUTO, the returned value is TEXT + // AUTO -> _MOZ_ALL -> NONE -> TEXT, the returned value is ALL + // AUTO -> _MOZ_ALL -> NONE -> _MOZ_TEXT, the returned value is TEXT. + // TEXT -> NONE -> AUTO -> _MOZ_ALL, the returned value is TEXT + // _MOZ_ALL -> TEXT -> AUTO -> AUTO, the returned value is ALL + // _MOZ_ALL -> _MOZ_TEXT -> AUTO -> AUTO, the returned value is TEXT. + // AUTO -> CELL -> TEXT -> AUTO, the returned value is TEXT // uint8_t selectStyle = NS_STYLE_USER_SELECT_AUTO; nsIFrame* frame = const_cast<nsFrame*>(this); + bool containsEditable = false; while (frame) { const nsStyleUIReset* userinterface = frame->StyleUIReset(); switch (userinterface->mUserSelect) { case NS_STYLE_USER_SELECT_ALL: case NS_STYLE_USER_SELECT_MOZ_ALL: + { // override the previous values - selectStyle = userinterface->mUserSelect; + if (selectStyle != NS_STYLE_USER_SELECT_MOZ_TEXT) { + selectStyle = userinterface->mUserSelect; + } + nsIContent* frameContent = frame->GetContent(); + containsEditable = frameContent && + frameContent->EditableDescendantCount() > 0; break; + } default: // otherwise return the first value which is not 'auto' if (selectStyle == NS_STYLE_USER_SELECT_AUTO) { @@ -2736,19 +2748,28 @@ nsFrame::IsSelectable(bool* aSelectable, uint8_t* aSelectStyle) const } // convert internal values to standard values - if (selectStyle == NS_STYLE_USER_SELECT_AUTO) + if (selectStyle == NS_STYLE_USER_SELECT_AUTO || + selectStyle == NS_STYLE_USER_SELECT_MOZ_TEXT) selectStyle = NS_STYLE_USER_SELECT_TEXT; else if (selectStyle == NS_STYLE_USER_SELECT_MOZ_ALL) selectStyle = NS_STYLE_USER_SELECT_ALL; + // If user tries to select all of a non-editable content, + // prevent selection if it contains editable content. + bool allowSelection = true; + if (selectStyle == NS_STYLE_USER_SELECT_ALL) { + allowSelection = !containsEditable; + } + // return stuff if (aSelectStyle) *aSelectStyle = selectStyle; if (mState & NS_FRAME_GENERATED_CONTENT) *aSelectable = false; else - *aSelectable = (selectStyle != NS_STYLE_USER_SELECT_NONE); + *aSelectable = allowSelection && + (selectStyle != NS_STYLE_USER_SELECT_NONE); return NS_OK; } @@ -3740,7 +3761,13 @@ static nsIFrame* AdjustFrameForSelectionStyles(nsIFrame* aFrame) { { // These are the conditions that make all children not able to handle // a cursor. - if (frame->StyleUIReset()->mUserSelect == NS_STYLE_USER_SELECT_ALL || + uint8_t userSelect = frame->StyleUIReset()->mUserSelect; + if (userSelect == NS_STYLE_USER_SELECT_MOZ_TEXT) { + // If we see a -moz-text element, we shouldn't look further up the parent + // chain! + break; + } + if (userSelect == NS_STYLE_USER_SELECT_ALL || frame->IsGeneratedContentFrame()) { adjustedFrame = frame; } diff --git a/layout/style/contenteditable.css b/layout/style/contenteditable.css index 76e02f0a9..efabc661b 100644 --- a/layout/style/contenteditable.css +++ b/layout/style/contenteditable.css @@ -9,10 +9,15 @@ cursor: text; } -*|*:focus:-moz-read-write :-moz-read-only { +*|*:-moz-read-write :-moz-read-only { -moz-user-select: all !important; } +*|*:-moz-read-only > :-moz-read-write { + /* override the above -moz-user-select: all rule. */ + -moz-user-select: -moz-text; +} + input:-moz-read-write > .anonymous-div:-moz-read-only, textarea:-moz-read-write > .anonymous-div:-moz-read-only { -moz-user-select: text !important; diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index 73636e061..28ecc0012 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -115,6 +115,7 @@ CSS_KEY(-moz-scrollbars-horizontal, _moz_scrollbars_horizontal) CSS_KEY(-moz-scrollbars-none, _moz_scrollbars_none) CSS_KEY(-moz-scrollbars-vertical, _moz_scrollbars_vertical) CSS_KEY(-moz-stack, _moz_stack) +CSS_KEY(-moz-text, _moz_text) CSS_KEY(-moz-use-system-font, _moz_use_system_font) CSS_KEY(-moz-use-text-color, _moz_use_text_color) CSS_KEY(-moz-visitedhyperlinktext, _moz_visitedhyperlinktext) diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index 0f82aa9d8..27a897d7f 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1808,6 +1808,7 @@ const KTableValue nsCSSProps::kUserSelectKTable[] = { eCSSKeyword_tri_state, NS_STYLE_USER_SELECT_TRI_STATE, eCSSKeyword__moz_all, NS_STYLE_USER_SELECT_MOZ_ALL, eCSSKeyword__moz_none, NS_STYLE_USER_SELECT_NONE, + eCSSKeyword__moz_text, NS_STYLE_USER_SELECT_MOZ_TEXT, eCSSKeyword_UNKNOWN,-1 }; diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h index e6d194ca2..d81cd5101 100644 --- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -105,6 +105,7 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) { #define NS_STYLE_USER_SELECT_AUTO 7 // internal value - please use nsFrame::IsSelectable() #define NS_STYLE_USER_SELECT_MOZ_ALL 8 // force selection of all children, unless an ancestor has NONE set - bug 48096 #define NS_STYLE_USER_SELECT_MOZ_NONE 9 // Like NONE, but doesn't change selection behavior for descendants whose user-select is not AUTO. +#define NS_STYLE_USER_SELECT_MOZ_TEXT 10 // Like TEXT, except that it won't get overridden by ancestors having ALL. // user-input #define NS_STYLE_USER_INPUT_NONE 0 diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp index ce8b276b3..7384bd1d6 100644 --- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/src/cubeb_wasapi.cpp @@ -370,14 +370,12 @@ double stream_to_mix_samplerate_ratio(cubeb_stream * stream) return double(stream->stream_params.rate) / stream->mix_params.rate; } -/* Upmix function, copies a mono channel in two interleaved - * stereo channel. |out| has to be twice as long as |in| */ +/* Channel upmix function, copies a mono channel into L and R */ template<typename T> void -mono_to_stereo(T * in, long insamples, T * out) +mono_to_stereo(T * in, long insamples, T * out, int32_t out_channels) { - int j = 0; - for (int i = 0; i < insamples; ++i, j += 2) { + for (long i = 0, j = 0; i < insamples; ++i, j += out_channels) { out[j] = out[j + 1] = in[i]; } } @@ -386,22 +384,31 @@ template<typename T> void upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels) { - XASSERT(out_channels >= in_channels); + XASSERT(out_channels >= in_channels && in_channels > 0); + /* If we have 2 or more channels, the first two are always L and R. */ /* If we are playing a mono stream over stereo speakers, copy the data over. */ - if (in_channels == 1 && out_channels == 2) { - mono_to_stereo(in, inframes, out); + if (in_channels == 1 && out_channels >= 2) { + mono_to_stereo(in, inframes, out, out_channels); + } else { + /* Copy through. */ + for (long i = 0, o = 0; i < inframes * in_channels; + i += in_channels, o += out_channels) { + for (int j = 0; j < in_channels; ++j) { + out[o + j] = in[i + j]; + } + } + } + + /* Check if more channels. */ + if (out_channels <= 2) { return; } - /* Otherwise, put silence in other channels. */ - long out_index = 0; - for (long i = 0; i < inframes * in_channels; i += in_channels) { - for (int j = 0; j < in_channels; ++j) { - out[out_index + j] = in[i + j]; - } - for (int j = in_channels; j < out_channels; ++j) { - out[out_index + j] = 0.0; + + /* Put silence in remaining channels. */ + for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) { + for (int j = 2; j < out_channels; ++j) { + out[o + j] = 0.0; } - out_index += out_channels; } } diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 2b20314b7..38303726b 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -2754,14 +2754,22 @@ this.AddonManager = { // The combination of all scopes. SCOPE_ALL: 15, - // 1-15 are different built-in views for the add-on type + // Add-on type is expected to be displayed in the UI in a list. VIEW_TYPE_LIST: "list", + // Constants describing how add-on types behave. + + // If no add-ons of a type are installed, then the category for that add-on + // type should be hidden in the UI. TYPE_UI_HIDE_EMPTY: 16, // Indicates that this add-on type supports the ask-to-activate state. // That is, add-ons of this type can be set to be optionally enabled // on a case-by-case basis. TYPE_SUPPORTS_ASK_TO_ACTIVATE: 32, + // The add-on type natively supports undo for restartless uninstalls. + // If this flag is not specified, the UI is expected to handle this via + // disabling the add-on, and performing the actual uninstall at a later time. + TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL: 64, // Constants for Addon.applyBackgroundUpdates. // Indicates that the Addon should not update automatically. diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js index 083b49069..a799eeebb 100644 --- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -1651,7 +1651,7 @@ function doPendingUninstalls(aListBox) { var listitem = aListBox.firstChild; while (listitem) { if (listitem.getAttribute("pending") == "uninstall" && - !listitem.isPending("uninstall")) + !(listitem.opRequiresRestart("uninstall"))) items.push(listitem.mAddon); listitem = listitem.nextSibling; } diff --git a/toolkit/mozapps/extensions/content/extensions.xml b/toolkit/mozapps/extensions/content/extensions.xml index 6490e9983..9c15902b5 100644 --- a/toolkit/mozapps/extensions/content/extensions.xml +++ b/toolkit/mozapps/extensions/content/extensions.xml @@ -785,6 +785,16 @@ ]]></body> </method> + <method name="typeHasFlag"> + <parameter name="aFlag"/> + <body><![CDATA[ + let flag = AddonManager["TYPE_" + aFlag]; + let type = AddonManager.addonTypes[this.mAddon.type]; + + return !!(type.flags & flag); + ]]></body> + </method> + <method name="isPending"> <parameter name="aAction"/> <body><![CDATA[ @@ -1368,8 +1378,7 @@ this._preferencesBtn.hidden = (!this.mAddon.optionsURL) || this.mAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO; - let addonType = AddonManager.addonTypes[this.mAddon.type]; - if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) { + if (this.typeHasFlag("SUPPORTS_ASK_TO_ACTIVATE")) { this._enableBtn.disabled = true; this._disableBtn.disabled = true; this._askToActivateMenuitem.disabled = !this.hasPermission("ask_to_activate"); @@ -1602,9 +1611,11 @@ <method name="uninstall"> <body><![CDATA[ - // If uninstalling does not require a restart then just disable it - // and show the undo UI. - if (!this.opRequiresRestart("uninstall")) { + // If uninstalling does not require a restart and the type doesn't + // support undoing of restartless uninstalls, then we fake it by + // just disabling it it, and doing the real uninstall later. + if (!this.opRequiresRestart("uninstall") && + !this.typeHasFlag("SUPPORTS_UNDO_RESTARTLESS_UNINSTALL")) { this.setAttribute("wasDisabled", this.mAddon.userDisabled); // We must set userDisabled to true first, this will call @@ -1614,7 +1625,7 @@ // This won't update any other add-on manager views (bug 582002) this.setAttribute("pending", "uninstall"); } else { - this.mAddon.uninstall(); + this.mAddon.uninstall(true); } ]]></body> </method> @@ -1856,7 +1867,7 @@ [this.mAddon.name], 1); - if (!this.isPending("uninstall")) + if (!this.opRequiresRestart("uninstall")) this._restartBtn.setAttribute("hidden", true); gEventManager.registerAddonListener(this, this.mAddon.id); diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index e95c67a65..613903a44 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -2597,6 +2597,19 @@ this.XPIProvider = { // install location. if (!manifest.exists()) { logger.debug("Processing uninstall of " + id + " in " + aLocation.name); + + try { + let addonFile = aLocation.getLocationForID(id); + let addonToUninstall = loadManifestFromFile(addonFile, aLocation); + if (addonToUninstall.bootstrap) { + this.callBootstrapMethod(addonToUninstall, addonToUninstall._sourceBundle, + "uninstall", BOOTSTRAP_REASONS.ADDON_UNINSTALL); + } + } + catch (e) { + logger.warn("Failed to call uninstall for " + id, e); + } + try { aLocation.uninstallAddon(id); seenFiles.push(stageDirEntry.leafName); @@ -4617,10 +4630,14 @@ this.XPIProvider = { * * @param aAddon * The DBAddonInternal to uninstall + * @param aForcePending + * Force this addon into the pending uninstall state, even if + * it isn't marked as requiring a restart (used e.g. while the + * add-on manager is open and offering an "undo" button) * @throws if the addon cannot be uninstalled because it is in an install * location that does not allow it */ - uninstallAddon: function XPI_uninstallAddon(aAddon) { + uninstallAddon: function XPI_uninstallAddon(aAddon, aForcePending) { if (!(aAddon.inDatabase)) throw new Error("Cannot uninstall addon " + aAddon.id + " because it is not installed"); @@ -4628,6 +4645,16 @@ this.XPIProvider = { throw new Error("Cannot uninstall addon " + aAddon.id + " from locked install location " + aAddon._installLocation.name); + // Inactive add-ons don't require a restart to uninstall + let requiresRestart = this.uninstallRequiresRestart(aAddon); + + // if makePending is true, we don't actually apply the uninstall, + // we just mark the addon as having a pending uninstall + let makePending = aForcePending || requiresRestart; + + if (makePending && aAddon.pendingUninstall) + throw new Error("Add-on is already marked to be uninstalled"); + if ("_hasResourceCache" in aAddon) aAddon._hasResourceCache = new Map(); @@ -4636,12 +4663,11 @@ this.XPIProvider = { aAddon._updateCheck.cancel(); } - // Inactive add-ons don't require a restart to uninstall - let requiresRestart = this.uninstallRequiresRestart(aAddon); + let wasPending = aAddon.pendingUninstall; - if (requiresRestart) { - // We create an empty directory in the staging directory to indicate that - // an uninstall is necessary on next startup. + if (makePending) { + // We create an empty directory in the staging directory to indicate + // that an uninstall is necessary on next startup. let stage = aAddon._installLocation.getStagingDir(); stage.append(aAddon.id); if (!stage.exists()) @@ -4665,8 +4691,16 @@ this.XPIProvider = { return; let wrapper = createWrapper(aAddon); - AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, - requiresRestart); + + // If the add-on wasn't already pending uninstall then notify listeners. + if (!wasPending) { + // Passing makePending as the requiresRestart parameter is a little + // strange as in some cases this operation can complete without a restart + // so really this is now saying that the uninstall isn't going to happen + // immediately but will happen later. + AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, + makePending); + } // Reveal the highest priority add-on with the same ID function revealAddon(aAddon) { @@ -4705,7 +4739,7 @@ this.XPIProvider = { } } - if (!requiresRestart) { + if (!makePending) { if (aAddon.bootstrap) { let file = aAddon._installLocation.getLocationForID(aAddon.id); if (aAddon.active) { @@ -4725,6 +4759,12 @@ this.XPIProvider = { findAddonAndReveal(aAddon.id); } + else if (aAddon.bootstrap && aAddon.active && !this.disableRequiresRestart(aAddon)) { + this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown", + BOOTSTRAP_REASONS.ADDON_UNINSTALL); + this.unloadBootstrapScope(aAddon.id); + XPIDatabase.updateAddonActive(aAddon, false); + } // Notify any other providers that a new theme has been enabled if (aAddon.type == "theme" && aAddon.active) @@ -4741,6 +4781,9 @@ this.XPIProvider = { if (!(aAddon.inDatabase)) throw new Error("Can only cancel uninstall for installed addons."); + if (!aAddon.pendingUninstall) + throw new Error("Add-on is not marked to be uninstalled"); + aAddon._installLocation.cleanStagingDir([aAddon.id]); XPIDatabase.setAddonProperties(aAddon, { @@ -4756,6 +4799,12 @@ this.XPIProvider = { let wrapper = createWrapper(aAddon); AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper); + if (aAddon.bootstrap && !aAddon.disabled && !this.enableRequiresRestart(aAddon)) { + this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup", + BOOTSTRAP_REASONS.ADDON_INSTALL); + XPIDatabase.updateAddonActive(aAddon, true); + } + // Notify any other providers that this theme is now enabled again. if (aAddon.type == "theme" && aAddon.active) AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false); @@ -5739,12 +5788,17 @@ AddonInstall.prototype = { let installedUnpacked = 0; yield this.installLocation.requestStagingDir(); + // Remove any staged items for this add-on + stagedAddon.append(this.addon.id); + yield removeAsync(stagedAddon); + stagedAddon.leafName = this.addon.id + ".xpi"; + yield removeAsync(stagedAddon); + // First stage the file regardless of whether restarting is necessary if (this.addon.unpack || Preferences.get(PREF_XPI_UNPACK, false)) { logger.debug("Addon " + this.addon.id + " will be installed as " + "an unpacked directory"); - stagedAddon.append(this.addon.id); - yield removeAsync(stagedAddon); + stagedAddon.leafName = this.addon.id; yield OS.File.makeDir(stagedAddon.path); yield ZipUtils.extractFilesAsync(this.file, stagedAddon); installedUnpacked = 1; @@ -5752,8 +5806,7 @@ AddonInstall.prototype = { else { logger.debug("Addon " + this.addon.id + " will be installed as " + "a packed xpi"); - stagedAddon.append(this.addon.id + ".xpi"); - yield removeAsync(stagedAddon); + stagedAddon.leafName = this.addon.id + ".xpi"; yield OS.File.copy(this.file.path, stagedAddon.path); } @@ -7041,19 +7094,11 @@ function AddonWrapper(aAddon) { return aAddon.isCompatibleWith(aAppVersion, aPlatformVersion); }; - this.uninstall = function AddonWrapper_uninstall() { - if (!(aAddon.inDatabase)) - throw new Error("Cannot uninstall an add-on that isn't installed"); - if (aAddon.pendingUninstall) - throw new Error("Add-on is already marked to be uninstalled"); - XPIProvider.uninstallAddon(aAddon); + this.uninstall = function AddonWrapper_uninstall(alwaysAllowUndo) { + XPIProvider.uninstallAddon(aAddon, alwaysAllowUndo); }; this.cancelUninstall = function AddonWrapper_cancelUninstall() { - if (!(aAddon.inDatabase)) - throw new Error("Cannot cancel uninstall for an add-on that isn't installed"); - if (!aAddon.pendingUninstall) - throw new Error("Add-on is not marked to be uninstalled"); XPIProvider.cancelUninstallAddon(aAddon); }; @@ -7787,18 +7832,19 @@ WinRegInstallLocation.prototype = { let addonTypes = [ new AddonManagerPrivate.AddonType("extension", URI_EXTENSION_STRINGS, STRING_TYPE_NAME, - AddonManager.VIEW_TYPE_LIST, 4000), + AddonManager.VIEW_TYPE_LIST, 4000, + AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL), new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS, STRING_TYPE_NAME, AddonManager.VIEW_TYPE_LIST, 5000), new AddonManagerPrivate.AddonType("dictionary", URI_EXTENSION_STRINGS, STRING_TYPE_NAME, AddonManager.VIEW_TYPE_LIST, 7000, - AddonManager.TYPE_UI_HIDE_EMPTY), + AddonManager.TYPE_UI_HIDE_EMPTY | AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL), new AddonManagerPrivate.AddonType("locale", URI_EXTENSION_STRINGS, STRING_TYPE_NAME, AddonManager.VIEW_TYPE_LIST, 8000, - AddonManager.TYPE_UI_HIDE_EMPTY), + AddonManager.TYPE_UI_HIDE_EMPTY | AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL), ]; // We only register experiments support if the application supports them. @@ -7811,7 +7857,7 @@ if (Preferences.get("experiments.supported", false)) { URI_EXTENSION_STRINGS, STRING_TYPE_NAME, AddonManager.VIEW_TYPE_LIST, 11000, - AddonManager.TYPE_UI_HIDE_EMPTY)); + AddonManager.TYPE_UI_HIDE_EMPTY | AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL)); } AddonManagerPrivate.registerProvider(XPIProvider, addonTypes); diff --git a/toolkit/mozapps/extensions/test/addons/test_undoincompatible/bootstrap.js b/toolkit/mozapps/extensions/test/addons/test_undoincompatible/bootstrap.js new file mode 100644 index 000000000..1666f2972 --- /dev/null +++ b/toolkit/mozapps/extensions/test/addons/test_undoincompatible/bootstrap.js @@ -0,0 +1 @@ +Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this); diff --git a/toolkit/mozapps/extensions/test/addons/test_undoincompatible/install.rdf b/toolkit/mozapps/extensions/test/addons/test_undoincompatible/install.rdf new file mode 100644 index 000000000..b038ebc51 --- /dev/null +++ b/toolkit/mozapps/extensions/test/addons/test_undoincompatible/install.rdf @@ -0,0 +1,28 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>incompatible@tests.mozilla.org</em:id> + <em:version>1.0</em:version> + <em:bootstrap>true</em:bootstrap> + + <!-- Front End MetaData --> + <em:name>Incompatible Addon</em:name> + <em:description>I am incompatible</em:description> + + <em:iconURL>chrome://foo/skin/icon.png</em:iconURL> + <em:aboutURL>chrome://foo/content/about.xul</em:aboutURL> + <em:optionsURL>chrome://foo/content/options.xul</em:optionsURL> + + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>2</em:minVersion> + <em:maxVersion>2</em:maxVersion> + </Description> + </em:targetApplication> + + </Description> +</RDF> diff --git a/toolkit/mozapps/extensions/test/addons/test_undouninstall1/bootstrap.js b/toolkit/mozapps/extensions/test/addons/test_undouninstall1/bootstrap.js new file mode 100644 index 000000000..1666f2972 --- /dev/null +++ b/toolkit/mozapps/extensions/test/addons/test_undouninstall1/bootstrap.js @@ -0,0 +1 @@ +Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this); diff --git a/toolkit/mozapps/extensions/test/addons/test_undouninstall1/install.rdf b/toolkit/mozapps/extensions/test/addons/test_undouninstall1/install.rdf new file mode 100644 index 000000000..4178fe929 --- /dev/null +++ b/toolkit/mozapps/extensions/test/addons/test_undouninstall1/install.rdf @@ -0,0 +1,28 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>undouninstall1@tests.mozilla.org</em:id> + <em:version>1.0</em:version> + <em:bootstrap>true</em:bootstrap> + + <!-- Front End MetaData --> + <em:name>Test Bootstrap 1</em:name> + <em:description>Test Description</em:description> + + <em:iconURL>chrome://foo/skin/icon.png</em:iconURL> + <em:aboutURL>chrome://foo/content/about.xul</em:aboutURL> + <em:optionsURL>chrome://foo/content/options.xul</em:optionsURL> + + <em:targetApplication> + <Description> + <em:id>xpcshell@tests.mozilla.org</em:id> + <em:minVersion>1</em:minVersion> + <em:maxVersion>1</em:maxVersion> + </Description> + </em:targetApplication> + + </Description> +</RDF> diff --git a/toolkit/mozapps/extensions/test/browser/browser_bug596336.js b/toolkit/mozapps/extensions/test/browser/browser_bug596336.js index edfcd7d07..935820613 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_bug596336.js +++ b/toolkit/mozapps/extensions/test/browser/browser_bug596336.js @@ -126,8 +126,7 @@ add_test(function() { // Force XBL to apply item.clientTop; - ok(aAddon.userDisabled, "Add-on should be disabled"); - ok(!aAddon.pendingUninstall, "Add-on should not be pending uninstall"); + ok(!!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should be pending uninstall"); is_element_visible(get_class_node(item, "pending"), "Pending message should be visible"); install_addon("browser_bug596336_2", function() { @@ -161,8 +160,7 @@ add_test(function() { // Force XBL to apply item.clientTop; - ok(aAddon.userDisabled, "Add-on should be disabled"); - ok(!aAddon.pendingUninstall, "Add-on should not be pending uninstall"); + ok(!!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should be pending uninstall"); is_element_visible(get_class_node(item, "pending"), "Pending message should be visible"); install_addon("browser_bug596336_2", function() { diff --git a/toolkit/mozapps/extensions/test/browser/browser_uninstalling.js b/toolkit/mozapps/extensions/test/browser/browser_uninstalling.js index ef67b04fe..9fcb9de66 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_uninstalling.js +++ b/toolkit/mozapps/extensions/test/browser/browser_uninstalling.js @@ -128,7 +128,7 @@ add_test(function() { // Force XBL to apply item.clientTop; - ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall"); + ok(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL, "Add-on should be pending uninstall"); button = gDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn"); isnot(button, null, "Should have a remove button"); ok(!button.disabled, "Button should not be disabled"); @@ -221,7 +221,7 @@ add_test(function() { is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); - ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall"); + ok(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL, "Add-on should be pending uninstall"); ok(!aAddon.isActive, "Add-on should be inactive"); var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn"); @@ -342,7 +342,7 @@ add_test(function() { is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); - ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall"); + ok(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL, "Add-on should be pending uninstall"); ok(!aAddon.isActive, "Add-on should be inactive"); var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn"); @@ -405,7 +405,7 @@ add_test(function() { is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); - ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall"); + ok(!!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should be pending uninstall"); ok(!aAddon.isActive, "Add-on should be inactive"); var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn"); @@ -531,7 +531,7 @@ add_test(function() { isnot(item, null, "Should have found the add-on in the list"); is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); - ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall"); + ok(!!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should be pending uninstall"); ok(!aAddon.isActive, "Add-on should be inactive"); // Force XBL to apply @@ -598,7 +598,7 @@ add_test(function() { isnot(item, null, "Should have found the add-on in the list"); is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); - ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall"); + ok(!!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should be pending uninstall"); ok(!aAddon.isActive, "Add-on should be inactive"); // Force XBL to apply @@ -809,7 +809,7 @@ add_test(function() { is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); - ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall"); + ok(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL, "Add-on should be pending uninstall"); ok(!aAddon.isActive, "Add-on should be inactive"); button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn"); @@ -888,7 +888,7 @@ add_test(function() { is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); - ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall"); + ok(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL, "Add-on should be pending uninstall"); ok(!aAddon.isActive, "Add-on should be inactive"); button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn"); @@ -964,7 +964,7 @@ add_test(function() { is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); - ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall"); + ok(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL, "Add-on should be pending uninstall"); ok(!aAddon.isActive, "Add-on should be inactive"); button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn"); @@ -1046,7 +1046,7 @@ add_test(function() { is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); - ok(!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should not be pending uninstall"); + ok(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL, "Add-on should be pending uninstall"); ok(!aAddon.isActive, "Add-on should be inactive"); var button = gDocument.getAnonymousElementByAttribute(item, "anonid", "restart-btn"); diff --git a/toolkit/mozapps/extensions/test/browser/head.js b/toolkit/mozapps/extensions/test/browser/head.js index b5e2c8da8..8e96b9b3f 100644 --- a/toolkit/mozapps/extensions/test/browser/head.js +++ b/toolkit/mozapps/extensions/test/browser/head.js @@ -635,7 +635,8 @@ function MockProvider(aUseAsyncCallbacks, aTypes) { id: "extension", name: "Extensions", uiPriority: 4000, - flags: AddonManager.TYPE_UI_VIEW_LIST + flags: AddonManager.TYPE_UI_VIEW_LIST | + AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL, }] : aTypes; var self = this; @@ -1088,7 +1089,8 @@ function MockAddon(aId, aName, aType, aOperationsRequiringRestart) { MockAddon.prototype = { get shouldBeActive() { - return !this.appDisabled && !this._userDisabled; + return !this.appDisabled && !this._userDisabled && + !(this.pendingOperations & AddonManager.PENDING_UNINSTALL); }, get appDisabled() { @@ -1160,16 +1162,19 @@ MockAddon.prototype = { // Tests can implement this if they need to }, - uninstall: function() { - if (this.pendingOperations & AddonManager.PENDING_UNINSTALL) + uninstall: function(aAlwaysAllowUndo = false) { + if ((this.operationsRequiringRestart & AddonManager.OP_NEED_RESTART_UNINSTALL) + && this.pendingOperations & AddonManager.PENDING_UNINSTALL) throw Components.Exception("Add-on is already pending uninstall"); - var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL); + var needsRestart = aAlwaysAllowUndo || !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL); this.pendingOperations |= AddonManager.PENDING_UNINSTALL; AddonManagerPrivate.callAddonListeners("onUninstalling", this, needsRestart); if (!needsRestart) { this.pendingOperations -= AddonManager.PENDING_UNINSTALL; this._provider.removeAddon(this); + } else if (!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE)) { + this.isActive = false; } }, @@ -1178,6 +1183,7 @@ MockAddon.prototype = { throw Components.Exception("Add-on is not pending uninstall"); this.pendingOperations -= AddonManager.PENDING_UNINSTALL; + this.isActive = this.shouldBeActive; AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); }, diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 4f8ffa1df..60259944e 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -1707,7 +1707,7 @@ function callback_soon(aFunction) { * its callback. */ function promiseAddonsByIDs(list) { - return new Promise((resolve, reject) => AddonManager.getAddonsByIDs(list, resolve)); + return new Promise(resolve => AddonManager.getAddonsByIDs(list, resolve)); } /** @@ -1718,7 +1718,20 @@ function promiseAddonsByIDs(list) { * @resolve {AddonWrapper} The corresponding add-on, or null. */ function promiseAddonByID(aId) { - return new Promise((resolve, reject) => AddonManager.getAddonByID(aId, resolve)); + return new Promise(resolve => AddonManager.getAddonByID(aId, resolve)); +} + +/** + * A promise-based variant of AddonManager.getAddonsWithOperationsByTypes + * + * @param {array} aTypes The first argument to + * AddonManager.getAddonsWithOperationsByTypes + * @return {promise} + * @resolve {array} The list of add-ons sent by + * AddonManaget.getAddonsWithOperationsByTypes to its callback. + */ +function promiseAddonsWithOperationsByTypes(aTypes) { + return new Promise(resolve => AddonManager.getAddonsWithOperationsByTypes(aTypes, resolve)); } /** diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js b/toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js new file mode 100644 index 000000000..c804b3bd6 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js @@ -0,0 +1,421 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that forcing undo for uninstall works for themes +Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm"); + +const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; + +var defaultTheme = { + id: "default@tests.mozilla.org", + version: "1.0", + name: "Test 1", + internalName: "classic/1.0", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +var theme1 = { + id: "theme1@tests.mozilla.org", + version: "1.0", + name: "Test 1", + internalName: "theme1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +function dummyLWTheme(id) { + return { + id: id || Math.random().toString(), + name: Math.random().toString(), + headerURL: "http://lwttest.invalid/a.png", + footerURL: "http://lwttest.invalid/b.png", + textcolor: Math.random().toString(), + accentcolor: Math.random().toString() + }; +} + +// Sets up the profile by installing an add-on. +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + startupManager(); + do_register_cleanup(promiseShutdownManager); + + run_next_test(); +} + +add_task(function* checkDefault() { + writeInstallRDFForExtension(defaultTheme, profileDir); + yield promiseRestartManager(); + + let d = yield promiseAddonByID("default@tests.mozilla.org"); + + do_check_neq(d, null); + do_check_true(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); +}); + +// Tests that uninstalling an enabled theme offers the option to undo +add_task(function* uninstallEnabledOffersUndo() { + writeInstallRDFForExtension(theme1, profileDir); + + yield promiseRestartManager(); + + let t1 = yield promiseAddonByID("theme1@tests.mozilla.org"); + + do_check_neq(t1, null); + do_check_true(t1.userDisabled); + + t1.userDisabled = false; + + yield promiseRestartManager(); + + let d = null; + [ t1, d ] = yield promiseAddonsByIDs(["theme1@tests.mozilla.org", + "default@tests.mozilla.org"]); + do_check_neq(d, null); + do_check_false(d.isActive); + do_check_true(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_true(t1.isActive); + do_check_false(t1.userDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1"); + + prepare_test({ + "default@tests.mozilla.org": [ + "onEnabling" + ], + "theme1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + t1.uninstall(true); + ensure_test_completed(); + + do_check_neq(d, null); + do_check_false(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_ENABLE); + + do_check_true(t1.isActive); + do_check_false(t1.userDisabled); + do_check_true(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL)); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1"); + + yield promiseRestartManager(); + + [ t1, d ] = yield promiseAddonsByIDs(["theme1@tests.mozilla.org", + "default@tests.mozilla.org"]); + do_check_neq(d, null); + do_check_true(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_eq(t1, null); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); +}); + +//Tests that uninstalling an enabled theme can be undone +add_task(function* canUndoUninstallEnabled() { + writeInstallRDFForExtension(theme1, profileDir); + + yield promiseRestartManager(); + + let t1 = yield promiseAddonByID("theme1@tests.mozilla.org"); + + do_check_neq(t1, null); + do_check_true(t1.userDisabled); + + t1.userDisabled = false; + + yield promiseRestartManager(); + + let d = null; + [ t1, d ] = yield promiseAddonsByIDs(["theme1@tests.mozilla.org", + "default@tests.mozilla.org"]); + + do_check_neq(d, null); + do_check_false(d.isActive); + do_check_true(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_true(t1.isActive); + do_check_false(t1.userDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1"); + + prepare_test({ + "default@tests.mozilla.org": [ + "onEnabling" + ], + "theme1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + t1.uninstall(true); + ensure_test_completed(); + + do_check_neq(d, null); + do_check_false(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_ENABLE); + + do_check_true(t1.isActive); + do_check_false(t1.userDisabled); + do_check_true(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL)); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1"); + + prepare_test({ + "default@tests.mozilla.org": [ + "onOperationCancelled" + ], + "theme1@tests.mozilla.org": [ + "onOperationCancelled" + ] + }); + t1.cancelUninstall(); + ensure_test_completed(); + + do_check_neq(d, null); + do_check_false(d.isActive); + do_check_true(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_true(t1.isActive); + do_check_false(t1.userDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + + yield promiseRestartManager(); + + [ t1, d ] = yield promiseAddonsByIDs(["theme1@tests.mozilla.org", + "default@tests.mozilla.org"]); + + do_check_neq(d, null); + do_check_false(d.isActive); + do_check_true(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_true(t1.isActive); + do_check_false(t1.userDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "theme1"); + + t1.uninstall(); + yield promiseRestartManager(); +}); + +//Tests that uninstalling a disabled theme offers the option to undo +add_task(function* uninstallDisabledOffersUndo() { + writeInstallRDFForExtension(theme1, profileDir); + + yield promiseRestartManager(); + + let [ t1, d ] = yield promiseAddonsByIDs(["theme1@tests.mozilla.org", + "default@tests.mozilla.org"]); + + do_check_neq(d, null); + do_check_true(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); + + prepare_test({ + "theme1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + t1.uninstall(true); + ensure_test_completed(); + + do_check_neq(d, null); + do_check_true(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_true(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL)); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); + + yield promiseRestartManager(); + + [ t1, d ] = yield promiseAddonsByIDs(["theme1@tests.mozilla.org", + "default@tests.mozilla.org"]); + + do_check_neq(d, null); + do_check_true(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_eq(t1, null); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); +}); + +//Tests that uninstalling a disabled theme can be undone +add_task(function* canUndoUninstallDisabled() { + writeInstallRDFForExtension(theme1, profileDir); + + yield promiseRestartManager(); + + let [ t1, d ] = yield promiseAddonsByIDs(["theme1@tests.mozilla.org", + "default@tests.mozilla.org"]); + + do_check_neq(d, null); + do_check_true(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); + + prepare_test({ + "theme1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + t1.uninstall(true); + ensure_test_completed(); + + do_check_neq(d, null); + do_check_true(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_true(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL)); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); + + prepare_test({ + "theme1@tests.mozilla.org": [ + "onOperationCancelled" + ] + }); + t1.cancelUninstall(); + ensure_test_completed(); + + do_check_neq(d, null); + do_check_true(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + + yield promiseRestartManager(); + + [ t1, d ] = yield promiseAddonsByIDs(["theme1@tests.mozilla.org", + "default@tests.mozilla.org"]); + + do_check_neq(d, null); + do_check_true(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_false(t1.isActive); + do_check_true(t1.userDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); + + t1.uninstall(); + yield promiseRestartManager(); +}); + +//Tests that uninstalling an enabled lightweight theme offers the option to undo +add_task(function* uninstallLWTOffersUndo() { + // skipped since lightweight themes don't support undoable uninstall yet + return; + LightweightThemeManager.currentTheme = dummyLWTheme("theme1"); + + let [ t1, d ] = yield promiseAddonsByIDs(["theme1@personas.mozilla.org", + "default@tests.mozilla.org"]); + + do_check_neq(d, null); + do_check_false(d.isActive); + do_check_true(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_neq(t1, null); + do_check_true(t1.isActive); + do_check_false(t1.userDisabled); + do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); + + prepare_test({ + "default@tests.mozilla.org": [ + "onEnabling" + ], + "theme1@personas.mozilla.org": [ + "onUninstalling" + ] + }); + t1.uninstall(true); + ensure_test_completed(); + + do_check_neq(d, null); + do_check_false(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_ENABLE); + + do_check_true(t1.isActive); + do_check_false(t1.userDisabled); + do_check_true(hasFlag(t1.pendingOperations, AddonManager.PENDING_UNINSTALL)); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); + + yield promiseRestartManager(); + + [ t1, d ] = yield promiseAddonsByIDs(["theme1@personas.mozilla.org", + "default@tests.mozilla.org"]); + + do_check_neq(d, null); + do_check_true(d.isActive); + do_check_false(d.userDisabled); + do_check_eq(d.pendingOperations, AddonManager.PENDING_NONE); + + do_check_eq(t1, null); + + do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "classic/1.0"); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_undouninstall.js b/toolkit/mozapps/extensions/test/xpcshell/test_undouninstall.js new file mode 100644 index 000000000..a589361b6 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_undouninstall.js @@ -0,0 +1,792 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This verifies that forcing undo for uninstall works + +const APP_STARTUP = 1; +const APP_SHUTDOWN = 2; +const ADDON_ENABLE = 3; +const ADDON_DISABLE = 4; +const ADDON_INSTALL = 5; +const ADDON_UNINSTALL = 6; +const ADDON_UPGRADE = 7; +const ADDON_DOWNGRADE = 8; + +const ID = "undouninstall1@tests.mozilla.org"; +const INCOMPAT_ID = "incompatible@tests.mozilla.org"; + +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "1.0", + name: "Test 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +BootstrapMonitor.init(); + +function getStartupReason(id) { + let info = BootstrapMonitor.started.get(id); + return info ? info.reason : undefined; +} + +function getShutdownReason(id) { + let info = BootstrapMonitor.stopped.get(id); + return info ? info.reason : undefined; +} + +function getInstallReason(id) { + let info = BootstrapMonitor.installed.get(id); + return info ? info.reason : undefined; +} + +function getUninstallReason(id) { + let info = BootstrapMonitor.uninstalled.get(id); + return info ? info.reason : undefined; +} + +function getStartupOldVersion(id) { + let info = BootstrapMonitor.started.get(id); + return info ? info.data.oldVersion : undefined; +} + +function getShutdownNewVersion(id) { + let info = BootstrapMonitor.stopped.get(id); + return info ? info.data.newVersion : undefined; +} + +function getInstallOldVersion(id) { + let info = BootstrapMonitor.installed.get(id); + return info ? info.data.oldVersion : undefined; +} + +function getUninstallNewVersion(id) { + let info = BootstrapMonitor.uninstalled.get(id); + return info ? info.data.newVersion : undefined; +} + +// Sets up the profile by installing an add-on. +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + startupManager(); + do_register_cleanup(promiseShutdownManager); + + run_next_test(); +} + +add_task(function* installAddon() { + let olda1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + + do_check_eq(olda1, null); + + writeInstallRDFForExtension(addon1, profileDir); + yield promiseRestartManager(); + + let a1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.pendingOperations, 0); + do_check_in_crash_annotation(addon1.id, addon1.version); +}); + +// Uninstalling an add-on should work. +add_task(function* uninstallAddon() { + prepare_test({ + "addon1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + + let a1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + + do_check_eq(a1.pendingOperations, 0); + do_check_neq(a1.operationsRequiringRestart & + AddonManager.OP_NEEDS_RESTART_UNINSTALL, 0); + a1.uninstall(true); + do_check_true(hasFlag(a1.pendingOperations, AddonManager.PENDING_UNINSTALL)); + do_check_in_crash_annotation(addon1.id, addon1.version); + + ensure_test_completed(); + + let list = yield promiseAddonsWithOperationsByTypes(null); + + do_check_eq(list.length, 1); + do_check_eq(list[0].id, "addon1@tests.mozilla.org"); + + yield promiseRestartManager(); + + a1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + + do_check_eq(a1, null); + do_check_false(isExtensionInAddonsList(profileDir, "addon1@tests.mozilla.org")); + do_check_not_in_crash_annotation(addon1.id, addon1.version); + + var dest = profileDir.clone(); + dest.append(do_get_expected_addon_name("addon1@tests.mozilla.org")); + do_check_false(dest.exists()); + writeInstallRDFForExtension(addon1, profileDir); + yield promiseRestartManager(); +}); + +// Cancelling the uninstall should send onOperationCancelled +add_task(function* cancelUninstall() { + prepare_test({ + "addon1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + + let a1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); + do_check_eq(a1.pendingOperations, 0); + a1.uninstall(true); + do_check_true(hasFlag(a1.pendingOperations, AddonManager.PENDING_UNINSTALL)); + + ensure_test_completed(); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onOperationCancelled" + ] + }); + a1.cancelUninstall(); + do_check_eq(a1.pendingOperations, 0); + + ensure_test_completed(); + yield promiseRestartManager(); + + a1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + + do_check_neq(a1, null); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + do_check_true(isExtensionInAddonsList(profileDir, a1.id)); +}); + +// Uninstalling an item pending disable should still require a restart +add_task(function* pendingDisableRequestRestart() { + let a1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onDisabling" + ] + }); + a1.userDisabled = true; + ensure_test_completed(); + + do_check_true(hasFlag(AddonManager.PENDING_DISABLE, a1.pendingOperations)); + do_check_true(a1.isActive); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + a1.uninstall(true); + + ensure_test_completed(); + + a1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + + do_check_neq(a1, null); + do_check_true(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations)); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onOperationCancelled" + ] + }); + a1.cancelUninstall(); + ensure_test_completed(); + do_check_true(hasFlag(AddonManager.PENDING_DISABLE, a1.pendingOperations)); + + yield promiseRestartManager(); +}); + +// Test that uninstalling an inactive item should still allow cancelling +add_task(function* uninstallInactiveIsCancellable() { + let a1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + + do_check_neq(a1, null); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + do_check_false(isExtensionInAddonsList(profileDir, a1.id)); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + a1.uninstall(true); + ensure_test_completed(); + + a1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + + do_check_neq(a1, null); + do_check_true(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations)); + + prepare_test({ + "addon1@tests.mozilla.org": [ + "onOperationCancelled" + ] + }); + a1.cancelUninstall(); + ensure_test_completed(); + + yield promiseRestartManager(); +}); + +//Test that an inactive item can be uninstalled +add_task(function* uninstallInactive() { + let a1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + + do_check_neq(a1, null); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + do_check_false(isExtensionInAddonsList(profileDir, a1.id)); + + prepare_test({ + "addon1@tests.mozilla.org": [ + [ "onUninstalling", false ], + "onUninstalled" + ] + }); + a1.uninstall(); + ensure_test_completed(); + + a1 = yield promiseAddonByID("addon1@tests.mozilla.org"); + do_check_eq(a1, null); +}); + +// Tests that an enabled restartless add-on can be uninstalled and goes away +// when the uninstall is committed +add_task(function* uninstallRestartless() { + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + ["onInstalling", false], + "onInstalled" + ] + }, [ + "onNewInstall", + "onInstallStarted", + "onInstallEnded" + ]); + yield promiseInstallAllFiles([do_get_addon("test_undouninstall1")]); + ensure_test_completed(); + + let a1 = yield promiseAddonByID(ID); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(getInstallReason(ID), ADDON_INSTALL); + do_check_eq(getStartupReason(ID), ADDON_INSTALL); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + a1.uninstall(true); + ensure_test_completed(); + + a1 = yield promiseAddonByID(ID); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonInstalled(ID); + BootstrapMonitor.checkAddonNotStarted(ID); + do_check_eq(getShutdownReason(ID), ADDON_UNINSTALL); + do_check_true(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations)); + do_check_false(a1.isActive); + do_check_false(a1.userDisabled); + + // complete the uinstall + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onUninstalled" + ] + }); + a1.uninstall(); + ensure_test_completed(); + + a1 = yield promiseAddonByID(ID); + + do_check_eq(a1, null); + BootstrapMonitor.checkAddonNotStarted(ID); +}); + +//Tests that an enabled restartless add-on can be uninstalled and then cancelled +add_task(function* cancelUninstallOfRestartless() { + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + ["onInstalling", false], + "onInstalled" + ] + }, [ + "onNewInstall", + "onInstallStarted", + "onInstallEnded" + ]); + yield promiseInstallAllFiles([do_get_addon("test_undouninstall1")]); + ensure_test_completed(); + + a1 = yield promiseAddonByID(ID); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(getInstallReason(ID), ADDON_INSTALL); + do_check_eq(getStartupReason(ID), ADDON_INSTALL); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + a1.uninstall(true); + ensure_test_completed(); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonInstalled(ID); + BootstrapMonitor.checkAddonNotStarted(ID); + do_check_eq(getShutdownReason(ID), ADDON_UNINSTALL); + do_check_true(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations)); + do_check_false(a1.isActive); + do_check_false(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onOperationCancelled" + ] + }); + a1.cancelUninstall(); + ensure_test_completed(); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(getStartupReason(ID), ADDON_INSTALL); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + + shutdownManager(); + + do_check_eq(getShutdownReason(ID), APP_SHUTDOWN); + do_check_eq(getShutdownNewVersion(ID), undefined); + + startupManager(false); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(getStartupReason(ID), APP_STARTUP); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + + a1.uninstall(); +}); + +// Tests that reinstalling an enabled restartless add-on waiting to be +// uninstalled aborts the uninstall and leaves the add-on enabled +add_task(function* reinstallAddonAwaitingUninstall() { + yield promiseInstallAllFiles([do_get_addon("test_undouninstall1")]); + + let a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(getInstallReason(ID), ADDON_INSTALL); + do_check_eq(getStartupReason(ID), ADDON_INSTALL); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + a1.uninstall(true); + ensure_test_completed(); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonInstalled(ID); + BootstrapMonitor.checkAddonNotStarted(ID); + do_check_eq(getShutdownReason(ID), ADDON_UNINSTALL); + do_check_true(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations)); + do_check_false(a1.isActive); + do_check_false(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + ["onInstalling", false], + "onInstalled" + ] + }, [ + "onNewInstall", + "onInstallStarted", + "onInstallEnded" + ]); + + yield promiseInstallAllFiles([do_get_addon("test_undouninstall1")]); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + ensure_test_completed(); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(getUninstallReason(ID), ADDON_DOWNGRADE); + do_check_eq(getInstallReason(ID), ADDON_DOWNGRADE); + do_check_eq(getStartupReason(ID), ADDON_DOWNGRADE); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + + shutdownManager(); + + do_check_eq(getShutdownReason(ID), APP_SHUTDOWN); + + startupManager(false); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(getStartupReason(ID), APP_STARTUP); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + + a1.uninstall(); +}); + +// Tests that a disabled restartless add-on can be uninstalled and goes away +// when the uninstall is committed +add_task(function* uninstallDisabledRestartless() { + yield promiseInstallAllFiles([do_get_addon("test_undouninstall1")]); + + let a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(getInstallReason(ID), ADDON_INSTALL); + do_check_eq(getStartupReason(ID), ADDON_INSTALL); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + + a1.userDisabled = true; + BootstrapMonitor.checkAddonNotStarted(ID); + do_check_eq(getShutdownReason(ID), ADDON_DISABLE); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + a1.uninstall(true); + ensure_test_completed(); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonNotStarted(ID); + do_check_true(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations)); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + + // commit the uninstall + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onUninstalled" + ] + }); + a1.uninstall(); + ensure_test_completed(); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_eq(a1, null); + BootstrapMonitor.checkAddonNotStarted(ID); + BootstrapMonitor.checkAddonNotInstalled(ID); + do_check_eq(getUninstallReason(ID), ADDON_UNINSTALL); +}); + +//Tests that a disabled restartless add-on can be uninstalled and then cancelled +add_task(function* cancelUninstallDisabledRestartless() { + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + ["onInstalling", false], + "onInstalled" + ] + }, [ + "onNewInstall", + "onInstallStarted", + "onInstallEnded" + ]); + yield promiseInstallAllFiles([do_get_addon("test_undouninstall1")]); + ensure_test_completed(); + + let a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(getInstallReason(ID), ADDON_INSTALL); + do_check_eq(getStartupReason(ID), ADDON_INSTALL); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + ["onDisabling", false], + "onDisabled" + ] + }); + a1.userDisabled = true; + ensure_test_completed(); + + BootstrapMonitor.checkAddonNotStarted(ID); + do_check_eq(getShutdownReason(ID), ADDON_DISABLE); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + a1.uninstall(true); + ensure_test_completed(); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonNotStarted(ID); + BootstrapMonitor.checkAddonInstalled(ID); + do_check_true(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations)); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onOperationCancelled" + ] + }); + a1.cancelUninstall(); + ensure_test_completed(); + + BootstrapMonitor.checkAddonNotStarted(ID); + BootstrapMonitor.checkAddonInstalled(ID); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + + yield promiseRestartManager(); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonNotStarted(ID); + BootstrapMonitor.checkAddonInstalled(ID); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + + a1.uninstall(); +}); + +//Tests that reinstalling a disabled restartless add-on waiting to be +//uninstalled aborts the uninstall and leaves the add-on disabled +add_task(function* reinstallDisabledAddonAwaitingUninstall() { + yield promiseInstallAllFiles([do_get_addon("test_undouninstall1")]); + + let a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(getInstallReason(ID), ADDON_INSTALL); + do_check_eq(getStartupReason(ID), ADDON_INSTALL); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + + a1.userDisabled = true; + BootstrapMonitor.checkAddonNotStarted(ID); + do_check_eq(getShutdownReason(ID), ADDON_DISABLE); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + a1.uninstall(true); + ensure_test_completed(); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonNotStarted(ID); + do_check_true(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations)); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + ["onInstalling", false], + "onInstalled" + ] + }, [ + "onNewInstall", + "onInstallStarted", + "onInstallEnded" + ]); + + yield promiseInstallAllFiles([do_get_addon("test_undouninstall1")]); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + ensure_test_completed(); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonNotStarted(ID, "1.0"); + do_check_eq(getUninstallReason(ID), ADDON_DOWNGRADE); + do_check_eq(getInstallReason(ID), ADDON_DOWNGRADE); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + + yield promiseRestartManager(); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonNotStarted(ID, "1.0"); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_false(a1.isActive); + do_check_true(a1.userDisabled); + + a1.uninstall(); +}); + + +// Test that uninstalling a temporary addon can be canceled +add_task(function* cancelUninstallTemporary() { + yield AddonManager.installTemporaryAddon(do_get_addon("test_undouninstall1")); + + let a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + do_check_neq(a1, null); + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(getInstallReason(ID), ADDON_INSTALL); + do_check_eq(getStartupReason(ID), ADDON_ENABLE); + do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE); + do_check_true(a1.isActive); + do_check_false(a1.userDisabled); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onUninstalling" + ] + }); + a1.uninstall(true); + ensure_test_completed(); + + BootstrapMonitor.checkAddonNotStarted(ID, "1.0"); + do_check_true(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations)); + + prepare_test({ + "undouninstall1@tests.mozilla.org": [ + "onOperationCancelled" + ] + }); + a1.cancelUninstall(); + ensure_test_completed(); + + a1 = yield promiseAddonByID("undouninstall1@tests.mozilla.org"); + + do_check_neq(a1, null); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + do_check_eq(a1.pendingOperations, 0); + + yield promiseRestartManager(); +}); + +// Tests that cancelling the uninstall of an incompatible restartless addon +// does not start the addon +add_task(function* cancelUninstallIncompatibleRestartless() { + yield promiseInstallAllFiles([do_get_addon("test_undoincompatible")]); + + let a1 = yield promiseAddonByID(INCOMPAT_ID); + do_check_neq(a1, null); + BootstrapMonitor.checkAddonNotStarted(INCOMPAT_ID); + do_check_false(a1.isActive); + + prepare_test({ + "incompatible@tests.mozilla.org": [ + "onUninstalling" + ] + }); + a1.uninstall(true); + ensure_test_completed(); + + a1 = yield promiseAddonByID(INCOMPAT_ID); + do_check_neq(a1, null); + do_check_true(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations)); + do_check_false(a1.isActive); + + prepare_test({ + "incompatible@tests.mozilla.org": [ + "onOperationCancelled" + ] + }); + a1.cancelUninstall(); + ensure_test_completed(); + + a1 = yield promiseAddonByID(INCOMPAT_ID); + do_check_neq(a1, null); + BootstrapMonitor.checkAddonNotStarted(INCOMPAT_ID); + do_check_eq(a1.pendingOperations, 0); + do_check_false(a1.isActive); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js b/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js index 02d37d89b..6b12489f2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js @@ -201,6 +201,7 @@ function run_test_4() { ] }); a1.uninstall(); + ensure_test_completed(); check_test_4(); }); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini index 6081c8411..bab072e83 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini @@ -245,6 +245,8 @@ fail-if = os == "android" # Bug 676992: test consistently fails on Android fail-if = os == "android" [test_types.js] +[test_undothemeuninstall.js] +[test_undouninstall.js] [test_uninstall.js] [test_update.js] # Bug 676992: test consistently hangs on Android diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index 21ca043ce..5a4f7bf9d 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -210,13 +210,11 @@ ParseIDFromDeviceID(const nsAString &key, const char *prefix, int length) // based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx enum { kWindowsUnknown = 0, - kWindowsXP = 0x50001, - kWindowsServer2003 = 0x50002, kWindowsVista = 0x60000, kWindows7 = 0x60001, kWindows8 = 0x60002, kWindows8_1 = 0x60003, - kWindows10 = 0x60004 + kWindows10 = 0xA0000 }; static int32_t @@ -729,10 +727,6 @@ static OperatingSystem WindowsVersionToOperatingSystem(int32_t aWindowsVersion) { switch(aWindowsVersion) { - case kWindowsXP: - return DRIVER_OS_WINDOWS_XP; - case kWindowsServer2003: - return DRIVER_OS_WINDOWS_SERVER_2003; case kWindowsVista: return DRIVER_OS_WINDOWS_VISTA; case kWindows7: @@ -868,22 +862,6 @@ GfxInfo::GetGfxDriverInfo() IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(DRIVER_OS_WINDOWS_7, IntelGMAX3000, V(8,15,10,1930)); IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(DRIVER_OS_WINDOWS_7, IntelGMAX4500HD, V(8,15,10,2202)); - IMPLEMENT_INTEL_DRIVER_BLOCKLIST(DRIVER_OS_WINDOWS_XP, IntelGMA500, V(3,0,20,3200)); - IMPLEMENT_INTEL_DRIVER_BLOCKLIST(DRIVER_OS_WINDOWS_XP, IntelGMA900, V(6,14,10,4764)); - IMPLEMENT_INTEL_DRIVER_BLOCKLIST(DRIVER_OS_WINDOWS_XP, IntelGMA950, V(6,14,10,4926)); - IMPLEMENT_INTEL_DRIVER_BLOCKLIST(DRIVER_OS_WINDOWS_XP, IntelGMA3150, V(6,14,10,5134)); - IMPLEMENT_INTEL_DRIVER_BLOCKLIST(DRIVER_OS_WINDOWS_XP, IntelGMAX3000, V(6,14,10,5218)); - IMPLEMENT_INTEL_DRIVER_BLOCKLIST(DRIVER_OS_WINDOWS_XP, IntelGMAX4500HD, V(6,14,10,4969)); - - // StrechRect seems to suffer from precision issues which leads to artifacting - // during content drawing starting with at least version 6.14.10.5082 - // and going until 6.14.10.5218. See bug 919454 and bug 949275 for more info. - APPEND_TO_DRIVER_BLOCKLIST_RANGE(DRIVER_OS_WINDOWS_XP, - const_cast<nsAString&>(GfxDriverInfo::GetDeviceVendor(VendorIntel)), - const_cast<GfxDeviceFamily*>(GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD)), - GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, - DRIVER_BETWEEN_EXCLUSIVE, V(6,14,10,5076), V(6,14,10,5218), "6.14.10.5218"); - IMPLEMENT_INTEL_DRIVER_BLOCKLIST(DRIVER_OS_WINDOWS_VISTA, IntelGMA500, V(3,0,20,3200)); IMPLEMENT_INTEL_DRIVER_BLOCKLIST(DRIVER_OS_WINDOWS_VISTA, IntelGMA900, GfxDriverInfo::allDriverVersions); IMPLEMENT_INTEL_DRIVER_BLOCKLIST(DRIVER_OS_WINDOWS_VISTA, IntelGMA950, V(7,14,10,1504)); @@ -1014,12 +992,6 @@ GfxInfo::GetGfxDriverInfo() GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE, V(8,17,12,5730), V(8,17,12,6901), "Nvidia driver > 8.17.12.6901"); - /* Bug 1153381: WebGL issues with D3D11 ANGLE on Intel. These may be fixed by an ANGLE update. */ - APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, - (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD), - nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, - DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions); - } return *mDriverInfo; } @@ -1072,27 +1044,6 @@ GfxInfo::GetFeatureStatusImpl(int32_t aFeature, return NS_ERROR_FAILURE; } - // special-case the WinXP test slaves: they have out-of-date drivers, but we still want to - // whitelist them, actually we do know that this combination of device and driver version - // works well. - if (mWindowsVersion == kWindowsXP && - adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), nsCaseInsensitiveStringComparator()) && - adapterDeviceID.LowerCaseEqualsLiteral("0x0861") && // GeForce 9400 - driverVersion == V(6,14,11,7756)) - { - *aStatus = FEATURE_STATUS_OK; - return NS_OK; - } - - // Windows Server 2003 should be just like Windows XP for present purpose, but still has a different version number. - // OTOH Windows Server 2008 R1 and R2 already have the same version numbers as Vista and Seven respectively - if (os == DRIVER_OS_WINDOWS_SERVER_2003) - os = DRIVER_OS_WINDOWS_XP; - - if (mHasDriverVersionMismatch) { - *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION; - return NS_OK; - } } return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, &os); |