summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPale Moon <git-repo@palemoon.org>2017-11-01 18:34:25 +0100
committerPale Moon <git-repo@palemoon.org>2017-11-01 18:34:25 +0100
commite696002451dc80420ee293a73758956a7a506e3b (patch)
treea632be211c046bfc49ed66cfc28679f5b4d41177
parent9a44165b281d3f6eab019e18dd64ba6dc19bf286 (diff)
parent0c11ba193b70b6acfa89e9100eec426a735ee7bd (diff)
downloadpalemoon-e696002451dc80420ee293a73758956a7a506e3b.tar.gz
Merge branch 'master' into 27.6_RelBranch
-rw-r--r--browser/base/content/safeMode.js7
-rw-r--r--browser/base/content/safeMode.xul8
-rw-r--r--browser/base/jar.mn2
-rw-r--r--dom/base/Element.cpp61
-rw-r--r--dom/base/nsGlobalWindowCommands.cpp21
-rw-r--r--dom/base/nsINode.cpp32
-rw-r--r--dom/base/nsINode.h29
-rw-r--r--dom/html/nsGenericHTMLElement.cpp15
-rw-r--r--dom/html/nsGenericHTMLElement.h7
-rw-r--r--editor/libeditor/tests/mochitest.ini2
-rw-r--r--editor/libeditor/tests/test_bug1181130-1.html50
-rw-r--r--editor/libeditor/tests/test_bug1181130-2.html44
-rw-r--r--layout/base/tests/bug1132768-1-ref.html12
-rw-r--r--layout/base/tests/bug1132768-1.html17
-rw-r--r--layout/base/tests/mochitest.ini2
-rw-r--r--layout/base/tests/test_reftests_with_caret.html8
-rw-r--r--layout/generic/nsFrame.cpp43
-rw-r--r--layout/style/contenteditable.css7
-rw-r--r--layout/style/nsCSSKeywordList.h1
-rw-r--r--layout/style/nsCSSProps.cpp1
-rw-r--r--layout/style/nsStyleConsts.h1
-rw-r--r--media/libcubeb/src/cubeb_wasapi.cpp41
-rw-r--r--toolkit/mozapps/extensions/AddonManager.jsm10
-rw-r--r--toolkit/mozapps/extensions/content/extensions.js2
-rw-r--r--toolkit/mozapps/extensions/content/extensions.xml25
-rw-r--r--toolkit/mozapps/extensions/internal/XPIProvider.jsm100
-rw-r--r--toolkit/mozapps/extensions/test/addons/test_undoincompatible/bootstrap.js1
-rw-r--r--toolkit/mozapps/extensions/test/addons/test_undoincompatible/install.rdf28
-rw-r--r--toolkit/mozapps/extensions/test/addons/test_undouninstall1/bootstrap.js1
-rw-r--r--toolkit/mozapps/extensions/test/addons/test_undouninstall1/install.rdf28
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_bug596336.js6
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_uninstalling.js20
-rw-r--r--toolkit/mozapps/extensions/test/browser/head.js16
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/head_addons.js17
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_undothemeuninstall.js421
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_undouninstall.js792
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_uninstall.js1
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini2
-rw-r--r--widget/windows/GfxInfo.cpp51
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);