summaryrefslogtreecommitdiffstats
path: root/toolkit
diff options
context:
space:
mode:
authorjanekptacijarabaci <janekptacijarabaci@seznam.cz>2018-04-25 15:48:44 +0200
committerjanekptacijarabaci <janekptacijarabaci@seznam.cz>2018-04-25 15:48:44 +0200
commit3ab6c7feee8126bdfc5c9ab9371db41102e12e95 (patch)
treea309c45826300b888238b6a517051fe7e71d63eb /toolkit
parentb18a9cf86ea25bc52d9cfea584e3aa8bfbe81f0a (diff)
parentb069dabc91b7e0f5f8d161cdbe598276a21d6d68 (diff)
downloadUXP-3ab6c7feee8126bdfc5c9ab9371db41102e12e95.tar
UXP-3ab6c7feee8126bdfc5c9ab9371db41102e12e95.tar.gz
UXP-3ab6c7feee8126bdfc5c9ab9371db41102e12e95.tar.lz
UXP-3ab6c7feee8126bdfc5c9ab9371db41102e12e95.tar.xz
UXP-3ab6c7feee8126bdfc5c9ab9371db41102e12e95.zip
Merge branch 'master' of https://github.com/MoonchildProductions/UXP into pm_url_1
Diffstat (limited to 'toolkit')
-rw-r--r--toolkit/components/aboutcache/content/aboutCache.js4
-rw-r--r--toolkit/components/autocomplete/nsAutoCompleteController.cpp122
-rw-r--r--toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp23
-rw-r--r--toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h2
-rw-r--r--toolkit/components/autocomplete/nsIAutoCompleteResult.idl7
-rw-r--r--toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl6
-rw-r--r--toolkit/components/autocomplete/tests/unit/head_autocomplete.js5
-rw-r--r--toolkit/components/autocomplete/tests/unit/test_hiddenResult.js76
-rw-r--r--toolkit/components/autocomplete/tests/unit/test_popupSelectionVsDefaultCompleteValue.js71
-rw-r--r--toolkit/components/autocomplete/tests/unit/xpcshell.ini2
-rw-r--r--toolkit/components/filepicker/nsFileView.cpp7
-rw-r--r--toolkit/components/jsdownloads/src/DownloadIntegration.jsm40
-rw-r--r--toolkit/components/places/moz.build2
-rw-r--r--toolkit/components/places/nsNavBookmarks.cpp2
-rw-r--r--toolkit/components/places/nsNavHistory.cpp2
-rw-r--r--toolkit/components/places/nsPlacesAutoComplete.js1778
-rw-r--r--toolkit/components/places/nsPlacesAutoComplete.manifest6
-rw-r--r--toolkit/components/places/nsTaggingService.js4
-rw-r--r--toolkit/components/url-classifier/HashStore.cpp3
-rw-r--r--toolkit/components/url-classifier/nsCheckSummedOutputStream.cpp17
-rw-r--r--toolkit/components/url-classifier/nsCheckSummedOutputStream.h24
-rw-r--r--toolkit/components/webextensions/ExtensionChild.jsm28
-rw-r--r--toolkit/components/webextensions/ExtensionCommon.jsm3
-rw-r--r--toolkit/components/webextensions/ExtensionContent.jsm4
-rw-r--r--toolkit/components/webextensions/LegacyExtensionsUtils.jsm2
-rw-r--r--toolkit/components/webextensions/ext-c-runtime.js5
-rw-r--r--toolkit/components/webextensions/schemas/runtime.json2
-rw-r--r--toolkit/components/webextensions/test/mochitest/mochitest.ini1
-rw-r--r--toolkit/components/webextensions/test/mochitest/test_ext_all_apis.js2
-rw-r--r--toolkit/components/webextensions/test/mochitest/test_ext_external_messaging.html111
-rw-r--r--toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_reply2.html144
-rw-r--r--toolkit/components/webextensions/test/xpcshell/test_ext_legacy_extension_context.js4
-rw-r--r--toolkit/components/xulstore/XULStore.js18
-rw-r--r--toolkit/content/plugins.html9
-rw-r--r--toolkit/content/widgets/toolbar.xml2
-rw-r--r--toolkit/forgetaboutsite/ForgetAboutSite.jsm244
-rw-r--r--toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js9
-rw-r--r--toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js68
-rw-r--r--toolkit/jetpack/modules/system/Startup.js1
-rw-r--r--toolkit/jetpack/moz.build25
-rw-r--r--toolkit/jetpack/sdk/addon/bootstrap.js2
-rw-r--r--toolkit/jetpack/sdk/clipboard.js1
-rw-r--r--toolkit/jetpack/sdk/context-menu.js1
-rw-r--r--toolkit/jetpack/sdk/panel.js1
-rw-r--r--toolkit/jetpack/sdk/places/bookmarks.js1
-rw-r--r--toolkit/jetpack/sdk/places/events.js1
-rw-r--r--toolkit/jetpack/sdk/places/favicon.js1
-rw-r--r--toolkit/jetpack/sdk/places/history.js1
-rw-r--r--toolkit/jetpack/sdk/places/host/host-bookmarks.js1
-rw-r--r--toolkit/jetpack/sdk/places/host/host-query.js1
-rw-r--r--toolkit/jetpack/sdk/places/host/host-tags.js1
-rw-r--r--toolkit/jetpack/sdk/places/utils.js1
-rw-r--r--toolkit/jetpack/sdk/selection.js1
-rw-r--r--toolkit/jetpack/sdk/system/xul-app.jsm1
-rw-r--r--toolkit/jetpack/sdk/ui.js3
-rw-r--r--toolkit/jetpack/sdk/ui/button/action.js1
-rw-r--r--toolkit/jetpack/sdk/ui/button/toggle.js1
-rw-r--r--toolkit/jetpack/sdk/ui/button/view.js45
-rw-r--r--toolkit/jetpack/sdk/ui/button/view/events.js1
-rw-r--r--toolkit/jetpack/sdk/ui/buttons.js198
-rw-r--r--toolkit/jetpack/sdk/ui/state.js1
-rw-r--r--toolkit/jetpack/sdk/ui/state/events.js1
-rw-r--r--toolkit/locales/en-US/chrome/global/aboutRights.dtd19
-rw-r--r--toolkit/locales/en-US/chrome/global/autocomplete.properties5
-rw-r--r--toolkit/locales/en-US/chrome/mozapps/preferences/changemp.dtd2
-rw-r--r--toolkit/modules/ResetProfile.jsm6
-rw-r--r--toolkit/modules/addons/WebRequestContent.js10
-rw-r--r--toolkit/modules/sessionstore/FormData.jsm2
-rw-r--r--toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm6
-rw-r--r--toolkit/mozapps/extensions/internal/XPIProvider.jsm41
-rw-r--r--toolkit/mozapps/extensions/test/addons/test_bootstrap_const/bootstrap.js5
-rw-r--r--toolkit/mozapps/extensions/test/addons/test_bootstrap_const/install.rdf24
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_const.js17
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini1
-rw-r--r--toolkit/mozapps/preferences/changemp.js12
-rw-r--r--toolkit/mozapps/preferences/changemp.xul7
-rw-r--r--toolkit/pluginproblem/content/pluginProblemBinding.css5
-rw-r--r--toolkit/pluginproblem/content/pluginProblemContent.css6
-rw-r--r--toolkit/pluginproblem/jar.mn4
-rw-r--r--toolkit/xre/MozMeegoAppService.h28
-rw-r--r--toolkit/xre/ProfileReset.cpp12
-rw-r--r--toolkit/xre/ProfileReset.h1
-rw-r--r--toolkit/xre/nsAppRunner.cpp113
83 files changed, 3057 insertions, 420 deletions
diff --git a/toolkit/components/aboutcache/content/aboutCache.js b/toolkit/components/aboutcache/content/aboutCache.js
index 07067cce3..e945d683e 100644
--- a/toolkit/components/aboutcache/content/aboutCache.js
+++ b/toolkit/components/aboutcache/content/aboutCache.js
@@ -40,5 +40,9 @@ function navigate()
if ($('priv').checked)
context += 'p,';
+ if (storage == null) {
+ storage = "";
+ }
+
window.location.href = 'about:cache?storage=' + storage + '&context=' + context;
}
diff --git a/toolkit/components/autocomplete/nsAutoCompleteController.cpp b/toolkit/components/autocomplete/nsAutoCompleteController.cpp
index 5d69ea1a3..9ca382fe5 100644
--- a/toolkit/components/autocomplete/nsAutoCompleteController.cpp
+++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp
@@ -1637,58 +1637,72 @@ nsAutoCompleteController::ProcessResult(int32_t aSearchIndex, nsIAutoCompleteRes
MOZ_ASSERT(mResults.Count() >= aSearchIndex + 1,
"aSearchIndex should always be valid for mResults");
- uint32_t oldRowCount = mRowCount;
- // If the search failed, increase the match count to include the error
- // description.
- if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
- nsAutoString error;
- aResult->GetErrorDescription(error);
- if (!error.IsEmpty()) {
- ++mRowCount;
- if (mTree) {
- mTree->RowCountChanged(oldRowCount, 1);
+ bool isTypeAheadResult = false;
+ aResult->GetTypeAheadResult(&isTypeAheadResult);
+
+ if (!isTypeAheadResult) {
+ uint32_t oldRowCount = mRowCount;
+ // If the search failed, increase the match count to include the error
+ // description.
+ if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
+ nsAutoString error;
+ aResult->GetErrorDescription(error);
+ if (!error.IsEmpty()) {
+ ++mRowCount;
+ if (mTree) {
+ mTree->RowCountChanged(oldRowCount, 1);
+ }
}
- }
- } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
- searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
- // Increase the match count for all matches in this result.
- uint32_t totalMatchCount = 0;
- for (uint32_t i = 0; i < mResults.Length(); i++) {
- nsIAutoCompleteResult* result = mResults.SafeObjectAt(i);
- if (result) {
- uint32_t matchCount = 0;
- result->GetMatchCount(&matchCount);
- totalMatchCount += matchCount;
+ } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
+ searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
+ // Increase the match count for all matches in this result.
+ uint32_t totalMatchCount = 0;
+ for (uint32_t i = 0; i < mResults.Length(); i++) {
+ nsIAutoCompleteResult* result = mResults.SafeObjectAt(i);
+ if (result) {
+ // not all results implement this, so it can likely fail.
+ bool typeAhead = false;
+ result->GetTypeAheadResult(&typeAhead);
+ if (!typeAhead) {
+ uint32_t matchCount = 0;
+ result->GetMatchCount(&matchCount);
+ totalMatchCount += matchCount;
+ }
+ }
}
- }
- uint32_t delta = totalMatchCount - oldRowCount;
+ uint32_t delta = totalMatchCount - oldRowCount;
- mRowCount += delta;
- if (mTree) {
- mTree->RowCountChanged(oldRowCount, delta);
+ mRowCount += delta;
+ if (mTree) {
+ mTree->RowCountChanged(oldRowCount, delta);
+ }
}
- }
- // Try to autocomplete the default index for this search.
- // Do this before invalidating so the binding knows about it.
- CompleteDefaultIndex(aSearchIndex);
+ // Try to autocomplete the default index for this search.
+ // Do this before invalidating so the binding knows about it.
+ CompleteDefaultIndex(aSearchIndex);
- // Refresh the popup view to display the new search results
- nsCOMPtr<nsIAutoCompletePopup> popup;
- input->GetPopup(getter_AddRefs(popup));
- NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
- popup->Invalidate(nsIAutoCompletePopup::INVALIDATE_REASON_NEW_RESULT);
+ // Refresh the popup view to display the new search results
+ nsCOMPtr<nsIAutoCompletePopup> popup;
+ input->GetPopup(getter_AddRefs(popup));
+ NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
+ popup->Invalidate(nsIAutoCompletePopup::INVALIDATE_REASON_NEW_RESULT);
- uint32_t minResults;
- input->GetMinResultsForPopup(&minResults);
+ uint32_t minResults;
+ input->GetMinResultsForPopup(&minResults);
- // Make sure the popup is open, if necessary, since we now have at least one
- // search result ready to display. Don't force the popup closed if we might
- // get results in the future to avoid unnecessarily canceling searches.
- if (mRowCount || !minResults) {
- OpenPopup();
- } else if (mSearchesOngoing == 0) {
- ClosePopup();
+ // Make sure the popup is open, if necessary, since we now have at least one
+ // search result ready to display. Don't force the popup closed if we might
+ // get results in the future to avoid unnecessarily canceling searches.
+ if (mRowCount || !minResults) {
+ OpenPopup();
+ } else if (mSearchesOngoing == 0) {
+ ClosePopup();
+ }
+ } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
+ searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
+ // Try to autocomplete the default index for this search.
+ CompleteDefaultIndex(aSearchIndex);
}
return NS_OK;
@@ -2033,14 +2047,20 @@ nsAutoCompleteController::RowIndexToSearch(int32_t aRowIndex, int32_t *aSearchIn
uint32_t rowCount = 0;
- uint16_t searchResult;
- result->GetSearchResult(&searchResult);
+ // Skip past the result completely if it is marked as hidden
+ bool isTypeAheadResult = false;
+ result->GetTypeAheadResult(&isTypeAheadResult);
- // Find out how many results were provided by the
- // current nsIAutoCompleteSearch.
- if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
- searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
- result->GetMatchCount(&rowCount);
+ if (!isTypeAheadResult) {
+ uint16_t searchResult;
+ result->GetSearchResult(&searchResult);
+
+ // Find out how many results were provided by the
+ // current nsIAutoCompleteSearch.
+ if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
+ searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
+ result->GetMatchCount(&rowCount);
+ }
}
// If the given row index is within the results range
diff --git a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp
index 9fd2c0022..683ac462a 100644
--- a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp
+++ b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp
@@ -43,7 +43,8 @@ struct AutoCompleteSimpleResultMatch
nsAutoCompleteSimpleResult::nsAutoCompleteSimpleResult() :
mDefaultIndex(-1),
- mSearchResult(RESULT_NOMATCH)
+ mSearchResult(RESULT_NOMATCH),
+ mTypeAheadResult(false)
{
}
@@ -66,6 +67,12 @@ nsAutoCompleteSimpleResult::AppendResult(nsIAutoCompleteResult* aResult)
mErrorDescription = errorDescription;
}
+ bool typeAheadResult = false;
+ if (NS_SUCCEEDED(aResult->GetTypeAheadResult(&typeAheadResult)) &&
+ typeAheadResult) {
+ mTypeAheadResult = typeAheadResult;
+ }
+
int32_t defaultIndex = -1;
if (NS_SUCCEEDED(aResult->GetDefaultIndex(&defaultIndex)) &&
defaultIndex >= 0) {
@@ -166,6 +173,20 @@ nsAutoCompleteSimpleResult::SetErrorDescription(
return NS_OK;
}
+// typeAheadResult
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetTypeAheadResult(bool *aTypeAheadResult)
+{
+ *aTypeAheadResult = mTypeAheadResult;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::SetTypeAheadResult(bool aTypeAheadResult)
+{
+ mTypeAheadResult = aTypeAheadResult;
+ return NS_OK;
+}
+
NS_IMETHODIMP
nsAutoCompleteSimpleResult::InsertMatchAt(int32_t aIndex,
const nsAString& aValue,
diff --git a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h
index 28968aa57..61ee542e4 100644
--- a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h
+++ b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h
@@ -38,6 +38,8 @@ protected:
int32_t mDefaultIndex;
uint32_t mSearchResult;
+ bool mTypeAheadResult;
+
nsCOMPtr<nsIAutoCompleteSimpleResultListener> mListener;
};
diff --git a/toolkit/components/autocomplete/nsIAutoCompleteResult.idl b/toolkit/components/autocomplete/nsIAutoCompleteResult.idl
index c719d9427..9ae22ade7 100644
--- a/toolkit/components/autocomplete/nsIAutoCompleteResult.idl
+++ b/toolkit/components/autocomplete/nsIAutoCompleteResult.idl
@@ -50,6 +50,13 @@ interface nsIAutoCompleteResult : nsISupports
readonly attribute unsigned long matchCount;
/**
+ * If true, the results will not be displayed in the popup. However,
+ * if a default index is specified, the default item will still be
+ * completed in the input.
+ */
+ readonly attribute boolean typeAheadResult;
+
+ /**
* Get the value of the result at the given index
*/
AString getValueAt(in long index);
diff --git a/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl b/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl
index 5e92e037a..6a8827ab8 100644
--- a/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl
+++ b/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl
@@ -42,6 +42,12 @@ interface nsIAutoCompleteSimpleResult : nsIAutoCompleteResult
void setSearchResult(in unsigned short aSearchResult);
/**
+ * A writer for the readonly attribute 'typeAheadResult', typically set
+ * because a result is only intended for type-ahead completion.
+ */
+ void setTypeAheadResult(in boolean aHidden);
+
+ /**
* Inserts a match consisting of the given value, comment, image, style and
* the value to use for defaultIndex completion at a given position.
* @param aIndex
diff --git a/toolkit/components/autocomplete/tests/unit/head_autocomplete.js b/toolkit/components/autocomplete/tests/unit/head_autocomplete.js
index 1443879f0..5a458bdf4 100644
--- a/toolkit/components/autocomplete/tests/unit/head_autocomplete.js
+++ b/toolkit/components/autocomplete/tests/unit/head_autocomplete.js
@@ -85,6 +85,11 @@ AutoCompleteResultBase.prototype = {
defaultIndex: -1,
+ _typeAheadResult: false,
+ get typeAheadResult() {
+ return this._typeAheadResult;
+ },
+
get matchCount() {
return this._values.length;
},
diff --git a/toolkit/components/autocomplete/tests/unit/test_hiddenResult.js b/toolkit/components/autocomplete/tests/unit/test_hiddenResult.js
new file mode 100644
index 000000000..8e2485716
--- /dev/null
+++ b/toolkit/components/autocomplete/tests/unit/test_hiddenResult.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function AutoCompleteResult(aValues) {
+ this._values = aValues;
+ this.defaultIndex = -1;
+ this._typeAheadResult = false;
+}
+AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype);
+
+function AutoCompleteTypeAheadResult(aValues) {
+ this._values = aValues;
+ this.defaultIndex = 0;
+ this._typeAheadResult = true;
+}
+AutoCompleteTypeAheadResult.prototype = Object.create(AutoCompleteResultBase.prototype);
+
+
+/**
+ * Test AutoComplete with multiple AutoCompleteSearch sources, with one of them
+ * being hidden from the popup, but can still do typeahead completion.
+ */
+function run_test() {
+ do_test_pending();
+
+ var inputStr = "moz";
+
+ // Type ahead result
+ var searchTypeAhead = new AutoCompleteSearchBase("search1",
+ new AutoCompleteTypeAheadResult(["mozillaTest1"]));
+ // Regular result
+ var searchNormal = new AutoCompleteSearchBase("search2",
+ new AutoCompleteResult(["mozillaTest2"]));
+
+ // Register searches so AutoCompleteController can find them
+ registerAutoCompleteSearch(searchNormal);
+ registerAutoCompleteSearch(searchTypeAhead);
+
+ // Make an AutoCompleteInput that uses our searches
+ // and confirms results on search complete.
+ var input = new AutoCompleteInputBase([searchTypeAhead.name, searchNormal.name]);
+ input.completeDefaultIndex = true;
+ input.textValue = inputStr;
+
+ // Caret must be at the end. Autofill doesn't happen unless you're typing
+ // characters at the end.
+ var strLen = inputStr.length;
+ input.selectTextRange(strLen, strLen);
+ do_check_eq(input.selectionStart, strLen);
+ do_check_eq(input.selectionEnd, strLen);
+
+ var controller = Cc["@mozilla.org/autocomplete/controller;1"].
+ getService(Ci.nsIAutoCompleteController);
+
+ controller.input = input;
+ controller.startSearch(inputStr);
+
+ input.onSearchComplete = function() {
+ // Hidden results should still be able to do inline autocomplete
+ do_check_eq(input.textValue, "mozillaTest1");
+
+ // Now, let's fill the textbox with the first result of the popup.
+ // The first search is marked as hidden, so we must always get the
+ // second search.
+ controller.handleEnter(true);
+ do_check_eq(input.textValue, "mozillaTest2");
+
+ // Only one item in the popup.
+ do_check_eq(controller.matchCount, 1);
+
+ // Unregister searches
+ unregisterAutoCompleteSearch(searchNormal);
+ unregisterAutoCompleteSearch(searchTypeAhead);
+ do_test_finished();
+ };
+}
diff --git a/toolkit/components/autocomplete/tests/unit/test_popupSelectionVsDefaultCompleteValue.js b/toolkit/components/autocomplete/tests/unit/test_popupSelectionVsDefaultCompleteValue.js
new file mode 100644
index 000000000..fb4153355
--- /dev/null
+++ b/toolkit/components/autocomplete/tests/unit/test_popupSelectionVsDefaultCompleteValue.js
@@ -0,0 +1,71 @@
+/* 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/. */
+
+function AutoCompleteTypeAheadResult(aValues, aFinalCompleteValues) {
+ this._values = aValues;
+ this._finalCompleteValues = aFinalCompleteValues;
+ this.defaultIndex = 0;
+ this._typeAheadResult = true;
+}
+AutoCompleteTypeAheadResult.prototype = Object.create(AutoCompleteResultBase.prototype);
+
+function AutoCompleteResult(aValues) {
+ this._values = aValues;
+}
+AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype);
+
+function AutoCompleteInput(aSearches) {
+ this.searches = aSearches;
+ this.popupOpen = true;
+ this.completeDefaultIndex = true;
+ this.completeSelectedIndex = true;
+}
+AutoCompleteInput.prototype = Object.create(AutoCompleteInputBase.prototype);
+
+function run_test() {
+ run_next_test();
+}
+
+add_test(function test_handleEnter() {
+ doSearch("moz", function(aController) {
+ do_check_eq(aController.input.textValue, "mozilla.com");
+ aController.handleEnter(true);
+ do_check_eq(aController.input.textValue, "mozilla.org");
+ });
+});
+
+function doSearch(aSearchString, aOnCompleteCallback) {
+ let typeAheadSearch = new AutoCompleteSearchBase(
+ "typeAheadSearch",
+ new AutoCompleteTypeAheadResult([ "mozilla.com" ], [ "http://www.mozilla.com" ])
+ );
+ registerAutoCompleteSearch(typeAheadSearch);
+
+ let search = new AutoCompleteSearchBase(
+ "search",
+ new AutoCompleteResult([ "mozilla.org" ])
+ );
+ registerAutoCompleteSearch(search);
+
+ let controller = Cc["@mozilla.org/autocomplete/controller;1"].
+ getService(Ci.nsIAutoCompleteController);
+
+ // Make an AutoCompleteInput that uses our searches and confirms results.
+ let input = new AutoCompleteInput([ typeAheadSearch.name, search.name ]);
+ input.textValue = aSearchString;
+
+ // Caret must be at the end for autofill to happen.
+ let strLen = aSearchString.length;
+ input.selectTextRange(strLen, strLen);
+ controller.input = input;
+ controller.startSearch(aSearchString);
+
+ input.onSearchComplete = function onSearchComplete() {
+ aOnCompleteCallback(controller);
+
+ // Clean up.
+ unregisterAutoCompleteSearch(search);
+ run_next_test();
+ };
+}
diff --git a/toolkit/components/autocomplete/tests/unit/xpcshell.ini b/toolkit/components/autocomplete/tests/unit/xpcshell.ini
index 4d193965c..daf89db17 100644
--- a/toolkit/components/autocomplete/tests/unit/xpcshell.ini
+++ b/toolkit/components/autocomplete/tests/unit/xpcshell.ini
@@ -18,7 +18,9 @@ tail =
[test_finalCompleteValue_forceComplete.js]
[test_finalCompleteValueSelectedIndex.js]
[test_finalDefaultCompleteValue.js]
+[test_hiddenResult.js]
[test_immediate_search.js]
[test_insertMatchAt.js]
+[test_popupSelectionVsDefaultCompleteValue.js]
[test_previousResult.js]
[test_stopSearch.js]
diff --git a/toolkit/components/filepicker/nsFileView.cpp b/toolkit/components/filepicker/nsFileView.cpp
index ad4471e86..9a8278496 100644
--- a/toolkit/components/filepicker/nsFileView.cpp
+++ b/toolkit/components/filepicker/nsFileView.cpp
@@ -133,6 +133,13 @@ NS_IMETHODIMP nsFileResult::GetMatchCount(uint32_t *aMatchCount)
return NS_OK;
}
+NS_IMETHODIMP nsFileResult::GetTypeAheadResult(bool *aTypeAheadResult)
+{
+ NS_ENSURE_ARG_POINTER(aTypeAheadResult);
+ *aTypeAheadResult = false;
+ return NS_OK;
+}
+
NS_IMETHODIMP nsFileResult::GetValueAt(int32_t index, nsAString & aValue)
{
aValue = mValues[index];
diff --git a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
index 1c30599f5..7439d9d11 100644
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -530,20 +530,34 @@ this.DownloadIntegration = {
* @return true if files should be marked
*/
_shouldSaveZoneInformation() {
- let key = Cc["@mozilla.org/windows-registry-key;1"]
- .createInstance(Ci.nsIWindowsRegKey);
+ let zonePref = 2;
try {
- key.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
- "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Attachments",
- Ci.nsIWindowsRegKey.ACCESS_QUERY_VALUE);
- try {
- return key.readIntValue("SaveZoneInformation") != 1;
- } finally {
- key.close();
- }
- } catch (ex) {
- // If the key is not present, files should be marked by default.
- return true;
+ zonePref = Services.prefs.getIntPref("browser.download.saveZoneInformation");
+ } catch (ex) {}
+
+ switch (zonePref) {
+ case 0: // Never
+ return false;
+ case 1: // Always
+ return true;
+ case 2: // System-defined
+ let key = Cc["@mozilla.org/windows-registry-key;1"]
+ .createInstance(Ci.nsIWindowsRegKey);
+ try {
+ key.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Attachments",
+ Ci.nsIWindowsRegKey.ACCESS_QUERY_VALUE);
+ try {
+ return key.readIntValue("SaveZoneInformation") != 1;
+ } finally {
+ key.close();
+ }
+ } catch (ex) {
+ // If the key is not present, files should be marked by default.
+ return true;
+ }
+ default: // Invalid pref value defaults marking files.
+ return true;
}
},
#endif
diff --git a/toolkit/components/places/moz.build b/toolkit/components/places/moz.build
index adac79cba..85e1e93e1 100644
--- a/toolkit/components/places/moz.build
+++ b/toolkit/components/places/moz.build
@@ -78,6 +78,8 @@ if CONFIG['MOZ_PLACES']:
EXTRA_COMPONENTS += [
'ColorAnalyzer.js',
'nsLivemarkService.js',
+ 'nsPlacesAutoComplete.js',
+ 'nsPlacesAutoComplete.manifest',
'nsPlacesExpiration.js',
'nsTaggingService.js',
'PageIconProtocolHandler.js',
diff --git a/toolkit/components/places/nsNavBookmarks.cpp b/toolkit/components/places/nsNavBookmarks.cpp
index 74707be99..693aaf5db 100644
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -2518,8 +2518,6 @@ nsNavBookmarks::GetURIForKeyword(const nsAString& aUserCasedKeyword,
NS_ENSURE_TRUE(!aUserCasedKeyword.IsEmpty(), NS_ERROR_INVALID_ARG);
*aURI = nullptr;
- PLACES_WARN_DEPRECATED();
-
// Shortcuts are always lowercased internally.
nsAutoString keyword(aUserCasedKeyword);
ToLowerCase(keyword);
diff --git a/toolkit/components/places/nsNavHistory.cpp b/toolkit/components/places/nsNavHistory.cpp
index 7f4007c1a..49d911d65 100644
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -2796,8 +2796,6 @@ nsNavHistory::MarkPageAsFollowedLink(nsIURI *aURI)
NS_IMETHODIMP
nsNavHistory::GetPageTitle(nsIURI* aURI, nsAString& aTitle)
{
- PLACES_WARN_DEPRECATED();
-
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
NS_ENSURE_ARG(aURI);
diff --git a/toolkit/components/places/nsPlacesAutoComplete.js b/toolkit/components/places/nsPlacesAutoComplete.js
new file mode 100644
index 000000000..29bdae4c1
--- /dev/null
+++ b/toolkit/components/places/nsPlacesAutoComplete.js
@@ -0,0 +1,1778 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
+ "resource://gre/modules/TelemetryStopwatch.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// Constants
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+// This SQL query fragment provides the following:
+// - whether the entry is bookmarked (kQueryIndexBookmarked)
+// - the bookmark title, if it is a bookmark (kQueryIndexBookmarkTitle)
+// - the tags associated with a bookmarked entry (kQueryIndexTags)
+const kBookTagSQLFragment =
+ `EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked,
+ (
+ SELECT title FROM moz_bookmarks WHERE fk = h.id AND title NOTNULL
+ ORDER BY lastModified DESC LIMIT 1
+ ) AS btitle,
+ (
+ SELECT GROUP_CONCAT(t.title, ',')
+ FROM moz_bookmarks b
+ JOIN moz_bookmarks t ON t.id = +b.parent AND t.parent = :parent
+ WHERE b.fk = h.id
+ ) AS tags`;
+
+// observer topics
+const kTopicShutdown = "places-shutdown";
+const kPrefChanged = "nsPref:changed";
+
+// Match type constants. These indicate what type of search function we should
+// be using.
+const MATCH_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE;
+const MATCH_BOUNDARY_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE;
+const MATCH_BOUNDARY = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY;
+const MATCH_BEGINNING = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING;
+const MATCH_BEGINNING_CASE_SENSITIVE = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING_CASE_SENSITIVE;
+
+// AutoComplete index constants. All AutoComplete queries will provide these
+// columns in this order.
+const kQueryIndexURL = 0;
+const kQueryIndexTitle = 1;
+const kQueryIndexFaviconURL = 2;
+const kQueryIndexBookmarked = 3;
+const kQueryIndexBookmarkTitle = 4;
+const kQueryIndexTags = 5;
+const kQueryIndexVisitCount = 6;
+const kQueryIndexTyped = 7;
+const kQueryIndexPlaceId = 8;
+const kQueryIndexQueryType = 9;
+const kQueryIndexOpenPageCount = 10;
+
+// AutoComplete query type constants. Describes the various types of queries
+// that we can process.
+const kQueryTypeKeyword = 0;
+const kQueryTypeFiltered = 1;
+
+// This separator is used as an RTL-friendly way to split the title and tags.
+// It can also be used by an nsIAutoCompleteResult consumer to re-split the
+// "comment" back into the title and the tag.
+const kTitleTagsSeparator = " \u2013 ";
+
+const kBrowserUrlbarBranch = "browser.urlbar.";
+// Toggle autocomplete.
+const kBrowserUrlbarAutocompleteEnabledPref = "autocomplete.enabled";
+// Toggle autoFill.
+const kBrowserUrlbarAutofillPref = "autoFill";
+// Whether to search only typed entries.
+const kBrowserUrlbarAutofillTypedPref = "autoFill.typed";
+
+// The Telemetry histogram for urlInlineComplete query on domain
+const DOMAIN_QUERY_TELEMETRY = "PLACES_AUTOCOMPLETE_URLINLINE_DOMAIN_QUERY_TIME_MS";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+XPCOMUtils.defineLazyServiceGetter(this, "gTextURIService",
+ "@mozilla.org/intl/texttosuburi;1",
+ "nsITextToSubURI");
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helpers
+
+/**
+ * Initializes our temporary table on a given database.
+ *
+ * @param aDatabase
+ * The mozIStorageConnection to set up the temp table on.
+ */
+function initTempTable(aDatabase)
+{
+ // Note: this should be kept up-to-date with the definition in
+ // nsPlacesTables.h.
+ let stmt = aDatabase.createAsyncStatement(
+ `CREATE TEMP TABLE moz_openpages_temp (
+ url TEXT PRIMARY KEY
+ , open_count INTEGER
+ )`
+ );
+ stmt.executeAsync();
+ stmt.finalize();
+
+ // Note: this should be kept up-to-date with the definition in
+ // nsPlacesTriggers.h.
+ stmt = aDatabase.createAsyncStatement(
+ `CREATE TEMPORARY TRIGGER moz_openpages_temp_afterupdate_trigger
+ AFTER UPDATE OF open_count ON moz_openpages_temp FOR EACH ROW
+ WHEN NEW.open_count = 0
+ BEGIN
+ DELETE FROM moz_openpages_temp
+ WHERE url = NEW.url;
+ END`
+ );
+ stmt.executeAsync();
+ stmt.finalize();
+}
+
+/**
+ * Used to unescape encoded URI strings, and drop information that we do not
+ * care about for searching.
+ *
+ * @param aURIString
+ * The text to unescape and modify.
+ * @return the modified uri.
+ */
+function fixupSearchText(aURIString)
+{
+ let uri = stripPrefix(aURIString);
+ return gTextURIService.unEscapeURIForUI("UTF-8", uri);
+}
+
+/**
+ * Strip prefixes from the URI that we don't care about for searching.
+ *
+ * @param aURIString
+ * The text to modify.
+ * @return the modified uri.
+ */
+function stripPrefix(aURIString)
+{
+ let uri = aURIString;
+
+ if (uri.indexOf("http://") == 0) {
+ uri = uri.slice(7);
+ }
+ else if (uri.indexOf("https://") == 0) {
+ uri = uri.slice(8);
+ }
+ else if (uri.indexOf("ftp://") == 0) {
+ uri = uri.slice(6);
+ }
+
+ if (uri.indexOf("www.") == 0) {
+ uri = uri.slice(4);
+ }
+ return uri;
+}
+
+/**
+ * safePrefGetter get the pref with type safety.
+ * This will return the default value provided if no pref is set.
+ *
+ * @param aPrefBranch
+ * The nsIPrefBranch containing the required preference
+ * @param aName
+ * A preference name
+ * @param aDefault
+ * The preference's default value
+ * @return the preference value or provided default
+ */
+
+function safePrefGetter(aPrefBranch, aName, aDefault) {
+ let types = {
+ boolean: "Bool",
+ number: "Int",
+ string: "Char"
+ };
+ let type = types[typeof(aDefault)];
+ if (!type) {
+ throw "Unknown type!";
+ }
+
+ // If the pref isn't set, we want to use the default.
+ if (aPrefBranch.getPrefType(aName) == Ci.nsIPrefBranch.PREF_INVALID) {
+ return aDefault;
+ }
+ try {
+ return aPrefBranch["get" + type + "Pref"](aName);
+ }
+ catch (e) {
+ return aDefault;
+ }
+}
+
+/**
+ * Whether UnifiedComplete is alive.
+ */
+function isUnifiedCompleteInstantiated() {
+ try {
+ return Components.manager.QueryInterface(Ci.nsIServiceManager)
+ .isServiceInstantiated(Cc["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"],
+ Ci.mozIPlacesAutoComplete);
+ } catch (ex) {
+ return false;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// AutoCompleteStatementCallbackWrapper class
+
+/**
+ * Wraps a callback and ensures that handleCompletion is not dispatched if the
+ * query is no longer tracked.
+ *
+ * @param aAutocomplete
+ * A reference to a nsPlacesAutoComplete.
+ * @param aCallback
+ * A reference to a mozIStorageStatementCallback
+ * @param aDBConnection
+ * The database connection to execute the queries on.
+ */
+function AutoCompleteStatementCallbackWrapper(aAutocomplete, aCallback,
+ aDBConnection)
+{
+ this._autocomplete = aAutocomplete;
+ this._callback = aCallback;
+ this._db = aDBConnection;
+}
+
+AutoCompleteStatementCallbackWrapper.prototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// mozIStorageStatementCallback
+
+ handleResult: function ACSCW_handleResult(aResultSet)
+ {
+ this._callback.handleResult.apply(this._callback, arguments);
+ },
+
+ handleError: function ACSCW_handleError(aError)
+ {
+ this._callback.handleError.apply(this._callback, arguments);
+ },
+
+ handleCompletion: function ACSCW_handleCompletion(aReason)
+ {
+ // Only dispatch handleCompletion if we are not done searching and are a
+ // pending search.
+ if (!this._autocomplete.isSearchComplete() &&
+ this._autocomplete.isPendingSearch(this._handle)) {
+ this._callback.handleCompletion.apply(this._callback, arguments);
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// AutoCompleteStatementCallbackWrapper
+
+ /**
+ * Executes the specified query asynchronously. This object will notify
+ * this._callback if we should notify (logic explained in handleCompletion).
+ *
+ * @param aQueries
+ * The queries to execute asynchronously.
+ * @return a mozIStoragePendingStatement that can be used to cancel the
+ * queries.
+ */
+ executeAsync: function ACSCW_executeAsync(aQueries)
+ {
+ return this._handle = this._db.executeAsync(aQueries, aQueries.length,
+ this);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.mozIStorageStatementCallback,
+ ])
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsPlacesAutoComplete class
+//// @mozilla.org/autocomplete/search;1?name=history
+
+function nsPlacesAutoComplete()
+{
+ //////////////////////////////////////////////////////////////////////////////
+ //// Shared Constants for Smart Getters
+
+ // TODO bug 412736 in case of a frecency tie, break it with h.typed and
+ // h.visit_count which is better than nothing. This is slow, so not doing it
+ // yet...
+ function baseQuery(conditions = "") {
+ let query = `SELECT h.url, h.title, f.url, ${kBookTagSQLFragment},
+ h.visit_count, h.typed, h.id, :query_type,
+ t.open_count
+ FROM moz_places h
+ LEFT JOIN moz_favicons f ON f.id = h.favicon_id
+ LEFT JOIN moz_openpages_temp t ON t.url = h.url
+ WHERE h.frecency <> 0
+ AND AUTOCOMPLETE_MATCH(:searchString, h.url,
+ IFNULL(btitle, h.title), tags,
+ h.visit_count, h.typed,
+ bookmarked, t.open_count,
+ :matchBehavior, :searchBehavior)
+ ${conditions}
+ ORDER BY h.frecency DESC, h.id DESC
+ LIMIT :maxResults`;
+ return query;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Smart Getters
+
+ XPCOMUtils.defineLazyGetter(this, "_db", function() {
+ // Get a cloned, read-only version of the database. We'll only ever write
+ // to our own in-memory temp table, and having a cloned copy means we do not
+ // run the risk of our queries taking longer due to the main database
+ // connection performing a long-running task.
+ let db = PlacesUtils.history.DBConnection.clone(true);
+
+ // Autocomplete often fallbacks to a table scan due to lack of text indices.
+ // In such cases a larger cache helps reducing IO. The default Storage
+ // value is MAX_CACHE_SIZE_BYTES in storage/mozStorageConnection.cpp.
+ let stmt = db.createAsyncStatement("PRAGMA cache_size = -6144"); // 6MiB
+ stmt.executeAsync();
+ stmt.finalize();
+
+ // Create our in-memory tables for tab tracking.
+ initTempTable(db);
+
+ // Populate the table with current open pages cache contents.
+ if (this._openPagesCache.length > 0) {
+ // Avoid getter re-entrance from the _registerOpenPageQuery lazy getter.
+ let stmt = this._registerOpenPageQuery =
+ db.createAsyncStatement(this._registerOpenPageQuerySQL);
+ let params = stmt.newBindingParamsArray();
+ for (let i = 0; i < this._openPagesCache.length; i++) {
+ let bp = params.newBindingParams();
+ bp.bindByName("page_url", this._openPagesCache[i]);
+ params.addParams(bp);
+ }
+ stmt.bindParameters(params);
+ stmt.executeAsync();
+ stmt.finalize();
+ delete this._openPagesCache;
+ }
+
+ return db;
+ });
+
+ this._customQuery = (conditions = "") => {
+ return this._db.createAsyncStatement(baseQuery(conditions));
+ };
+
+ XPCOMUtils.defineLazyGetter(this, "_defaultQuery", function() {
+ return this._db.createAsyncStatement(baseQuery());
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_historyQuery", function() {
+ // Enforce ignoring the visit_count index, since the frecency one is much
+ // faster in this case. ANALYZE helps the query planner to figure out the
+ // faster path, but it may not have run yet.
+ return this._db.createAsyncStatement(baseQuery("AND +h.visit_count > 0"));
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_bookmarkQuery", function() {
+ return this._db.createAsyncStatement(baseQuery("AND bookmarked"));
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_tagsQuery", function() {
+ return this._db.createAsyncStatement(baseQuery("AND tags IS NOT NULL"));
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_openPagesQuery", function() {
+ return this._db.createAsyncStatement(
+ `SELECT t.url, t.url, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ :query_type, t.open_count, NULL
+ FROM moz_openpages_temp t
+ LEFT JOIN moz_places h ON h.url = t.url
+ WHERE h.id IS NULL
+ AND AUTOCOMPLETE_MATCH(:searchString, t.url, t.url, NULL,
+ NULL, NULL, NULL, t.open_count,
+ :matchBehavior, :searchBehavior)
+ ORDER BY t.ROWID DESC
+ LIMIT :maxResults`
+ );
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_typedQuery", function() {
+ return this._db.createAsyncStatement(baseQuery("AND h.typed = 1"));
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_adaptiveQuery", function() {
+ return this._db.createAsyncStatement(
+ `/* do not warn (bug 487789) */
+ SELECT h.url, h.title, f.url, ${kBookTagSQLFragment},
+ h.visit_count, h.typed, h.id, :query_type, t.open_count
+ FROM (
+ SELECT ROUND(
+ MAX(use_count) * (1 + (input = :search_string)), 1
+ ) AS rank, place_id
+ FROM moz_inputhistory
+ WHERE input BETWEEN :search_string AND :search_string || X'FFFF'
+ GROUP BY place_id
+ ) AS i
+ JOIN moz_places h ON h.id = i.place_id
+ LEFT JOIN moz_favicons f ON f.id = h.favicon_id
+ LEFT JOIN moz_openpages_temp t ON t.url = h.url
+ WHERE AUTOCOMPLETE_MATCH(NULL, h.url,
+ IFNULL(btitle, h.title), tags,
+ h.visit_count, h.typed, bookmarked,
+ t.open_count,
+ :matchBehavior, :searchBehavior)
+ ORDER BY rank DESC, h.frecency DESC`
+ );
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_keywordQuery", function() {
+ return this._db.createAsyncStatement(
+ `/* do not warn (bug 487787) */
+ SELECT REPLACE(h.url, '%s', :query_string) AS search_url, h.title,
+ IFNULL(f.url, (SELECT f.url
+ FROM moz_places
+ JOIN moz_favicons f ON f.id = favicon_id
+ WHERE rev_host = h.rev_host
+ ORDER BY frecency DESC
+ LIMIT 1)
+ ), 1, NULL, NULL, h.visit_count, h.typed, h.id,
+ :query_type, t.open_count
+ FROM moz_keywords k
+ JOIN moz_places h ON k.place_id = h.id
+ LEFT JOIN moz_favicons f ON f.id = h.favicon_id
+ LEFT JOIN moz_openpages_temp t ON t.url = search_url
+ WHERE k.keyword = LOWER(:keyword)`
+ );
+ });
+
+ this._registerOpenPageQuerySQL =
+ `INSERT OR REPLACE INTO moz_openpages_temp (url, open_count)
+ VALUES (:page_url,
+ IFNULL(
+ (
+ SELECT open_count + 1
+ FROM moz_openpages_temp
+ WHERE url = :page_url
+ ),
+ 1
+ )
+ )`;
+ XPCOMUtils.defineLazyGetter(this, "_registerOpenPageQuery", function() {
+ return this._db.createAsyncStatement(this._registerOpenPageQuerySQL);
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_unregisterOpenPageQuery", function() {
+ return this._db.createAsyncStatement(
+ `UPDATE moz_openpages_temp
+ SET open_count = open_count - 1
+ WHERE url = :page_url`
+ );
+ });
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Initialization
+
+ // load preferences
+ this._prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ getBranch(kBrowserUrlbarBranch);
+ this._syncEnabledPref();
+ this._loadPrefs(true);
+
+ // register observers
+ this._os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ this._os.addObserver(this, kTopicShutdown, false);
+
+}
+
+nsPlacesAutoComplete.prototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIAutoCompleteSearch
+
+ startSearch: function PAC_startSearch(aSearchString, aSearchParam,
+ aPreviousResult, aListener)
+ {
+ // Stop the search in case the controller has not taken care of it.
+ this.stopSearch();
+
+ // Note: We don't use aPreviousResult to make sure ordering of results are
+ // consistent. See bug 412730 for more details.
+
+ // We want to store the original string with no leading or trailing
+ // whitespace for case sensitive searches.
+ this._originalSearchString = aSearchString.trim();
+
+ this._currentSearchString =
+ fixupSearchText(this._originalSearchString.toLowerCase());
+
+ let params = new Set(aSearchParam.split(" "));
+ this._enableActions = params.has("enable-actions");
+ this._disablePrivateActions = params.has("disable-private-actions");
+
+ this._listener = aListener;
+ let result = Cc["@mozilla.org/autocomplete/simple-result;1"].
+ createInstance(Ci.nsIAutoCompleteSimpleResult);
+ result.setSearchString(aSearchString);
+ result.setListener(this);
+ this._result = result;
+
+ // If we are not enabled, we need to return now.
+ if (!this._enabled) {
+ this._finishSearch(true);
+ return;
+ }
+
+ // Reset our search behavior to the default.
+ if (this._currentSearchString) {
+ this._behavior = this._defaultBehavior;
+ }
+ else {
+ this._behavior = this._emptySearchDefaultBehavior;
+ }
+ // For any given search, we run up to four queries:
+ // 1) keywords (this._keywordQuery)
+ // 2) adaptive learning (this._adaptiveQuery)
+ // 3) open pages not supported by history (this._openPagesQuery)
+ // 4) query from this._getSearch
+ // (1) only gets ran if we get any filtered tokens from this._getSearch,
+ // since if there are no tokens, there is nothing to match, so there is no
+ // reason to run the query).
+ let {query, tokens} =
+ this._getSearch(this._getUnfilteredSearchTokens(this._currentSearchString));
+ let queries = tokens.length ?
+ [this._getBoundKeywordQuery(tokens), this._getBoundAdaptiveQuery()] :
+ [this._getBoundAdaptiveQuery()];
+
+ if (this._hasBehavior("openpage")) {
+ queries.push(this._getBoundOpenPagesQuery(tokens));
+ }
+ queries.push(query);
+
+ // Start executing our queries.
+ this._telemetryStartTime = Date.now();
+ this._executeQueries(queries);
+
+ // Set up our persistent state for the duration of the search.
+ this._searchTokens = tokens;
+ this._usedPlaces = {};
+ },
+
+ stopSearch: function PAC_stopSearch()
+ {
+ // We need to cancel our searches so we do not get any [more] results.
+ // However, it's possible we haven't actually started any searches, so this
+ // method may throw because this._pendingQuery may be undefined.
+ if (this._pendingQuery) {
+ this._stopActiveQuery();
+ }
+
+ this._finishSearch(false);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIAutoCompleteSimpleResultListener
+
+ onValueRemoved: function PAC_onValueRemoved(aResult, aURISpec, aRemoveFromDB)
+ {
+ if (aRemoveFromDB) {
+ PlacesUtils.history.removePage(NetUtil.newURI(aURISpec));
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// mozIPlacesAutoComplete
+
+ // If the connection has not yet been started, use this local cache. This
+ // prevents autocomplete from initing the database till the first search.
+ _openPagesCache: [],
+ registerOpenPage: function PAC_registerOpenPage(aURI)
+ {
+ if (!this._databaseInitialized) {
+ this._openPagesCache.push(aURI.spec);
+ return;
+ }
+
+ let stmt = this._registerOpenPageQuery;
+ stmt.params.page_url = aURI.spec;
+ stmt.executeAsync();
+ },
+
+ unregisterOpenPage: function PAC_unregisterOpenPage(aURI)
+ {
+ if (!this._databaseInitialized) {
+ let index = this._openPagesCache.indexOf(aURI.spec);
+ if (index != -1) {
+ this._openPagesCache.splice(index, 1);
+ }
+ return;
+ }
+
+ let stmt = this._unregisterOpenPageQuery;
+ stmt.params.page_url = aURI.spec;
+ stmt.executeAsync();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// mozIStorageStatementCallback
+
+ handleResult: function PAC_handleResult(aResultSet)
+ {
+ let row, haveMatches = false;
+ while ((row = aResultSet.getNextRow())) {
+ let match = this._processRow(row);
+ haveMatches = haveMatches || match;
+
+ if (this._result.matchCount == this._maxRichResults) {
+ // We have enough results, so stop running our search.
+ this._stopActiveQuery();
+
+ // And finish our search.
+ this._finishSearch(true);
+ return;
+ }
+
+ }
+
+ // Notify about results if we've gotten them.
+ if (haveMatches) {
+ this._notifyResults(true);
+ }
+ },
+
+ handleError: function PAC_handleError(aError)
+ {
+ Components.utils.reportError("Places AutoComplete: An async statement encountered an " +
+ "error: " + aError.result + ", '" + aError.message + "'");
+ },
+
+ handleCompletion: function PAC_handleCompletion(aReason)
+ {
+ // If we have already finished our search, we should bail out early.
+ if (this.isSearchComplete()) {
+ return;
+ }
+
+ // If we do not have enough results, and our match type is
+ // MATCH_BOUNDARY_ANYWHERE, search again with MATCH_ANYWHERE to get more
+ // results.
+ if (this._matchBehavior == MATCH_BOUNDARY_ANYWHERE &&
+ this._result.matchCount < this._maxRichResults && !this._secondPass) {
+ this._secondPass = true;
+ let queries = [
+ this._getBoundAdaptiveQuery(MATCH_ANYWHERE),
+ this._getBoundSearchQuery(MATCH_ANYWHERE, this._searchTokens),
+ ];
+ this._executeQueries(queries);
+ return;
+ }
+
+ this._finishSearch(true);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIObserver
+
+ observe: function PAC_observe(aSubject, aTopic, aData)
+ {
+ if (aTopic == kTopicShutdown) {
+ this._os.removeObserver(this, kTopicShutdown);
+
+ // Remove our preference observer.
+ this._prefs.removeObserver("", this);
+ delete this._prefs;
+
+ // Finalize the statements that we have used.
+ let stmts = [
+ "_defaultQuery",
+ "_historyQuery",
+ "_bookmarkQuery",
+ "_tagsQuery",
+ "_openPagesQuery",
+ "_typedQuery",
+ "_adaptiveQuery",
+ "_keywordQuery",
+ "_registerOpenPageQuery",
+ "_unregisterOpenPageQuery",
+ ];
+ for (let i = 0; i < stmts.length; i++) {
+ // We do not want to create any query we haven't already created, so
+ // see if it is a getter first.
+ if (Object.getOwnPropertyDescriptor(this, stmts[i]).value !== undefined) {
+ this[stmts[i]].finalize();
+ }
+ }
+
+ if (this._databaseInitialized) {
+ this._db.asyncClose();
+ }
+ }
+ else if (aTopic == kPrefChanged) {
+ // Avoid re-entrancy when flipping linked preferences.
+ if (this._ignoreNotifications)
+ return;
+ this._ignoreNotifications = true;
+ this._loadPrefs(false, aTopic, aData);
+ this._ignoreNotifications = false;
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsPlacesAutoComplete
+
+ get _databaseInitialized() {
+ return Object.getOwnPropertyDescriptor(this, "_db").value !== undefined;
+ },
+
+ /**
+ * Generates the tokens used in searching from a given string.
+ *
+ * @param aSearchString
+ * The string to generate tokens from.
+ * @return an array of tokens.
+ */
+ _getUnfilteredSearchTokens: function PAC_unfilteredSearchTokens(aSearchString)
+ {
+ // Calling split on an empty string will return an array containing one
+ // empty string. We don't want that, as it'll break our logic, so return an
+ // empty array then.
+ return aSearchString.length ? aSearchString.split(" ") : [];
+ },
+
+ /**
+ * Properly cleans up when searching is completed.
+ *
+ * @param aNotify
+ * Indicates if we should notify the AutoComplete listener about our
+ * results or not.
+ */
+ _finishSearch: function PAC_finishSearch(aNotify)
+ {
+ // Notify about results if we are supposed to.
+ if (aNotify) {
+ this._notifyResults(false);
+ }
+
+ // Clear our state
+ delete this._originalSearchString;
+ delete this._currentSearchString;
+ delete this._strippedPrefix;
+ delete this._searchTokens;
+ delete this._listener;
+ delete this._result;
+ delete this._usedPlaces;
+ delete this._pendingQuery;
+ this._secondPass = false;
+ this._enableActions = false;
+ },
+
+ /**
+ * Executes the given queries asynchronously.
+ *
+ * @param aQueries
+ * The queries to execute.
+ */
+ _executeQueries: function PAC_executeQueries(aQueries)
+ {
+ // Because we might get a handleCompletion for canceled queries, we want to
+ // filter out queries we no longer care about (described in the
+ // handleCompletion implementation of AutoCompleteStatementCallbackWrapper).
+
+ // Create our wrapper object and execute the queries.
+ let wrapper = new AutoCompleteStatementCallbackWrapper(this, this, this._db);
+ this._pendingQuery = wrapper.executeAsync(aQueries);
+ },
+
+ /**
+ * Stops executing our active query.
+ */
+ _stopActiveQuery: function PAC_stopActiveQuery()
+ {
+ this._pendingQuery.cancel();
+ delete this._pendingQuery;
+ },
+
+ /**
+ * Notifies the listener about results.
+ *
+ * @param aSearchOngoing
+ * Indicates if the search is ongoing or not.
+ */
+ _notifyResults: function PAC_notifyResults(aSearchOngoing)
+ {
+ let result = this._result;
+ let resultCode = result.matchCount ? "RESULT_SUCCESS" : "RESULT_NOMATCH";
+ if (aSearchOngoing) {
+ resultCode += "_ONGOING";
+ }
+ result.setSearchResult(Ci.nsIAutoCompleteResult[resultCode]);
+ this._listener.onSearchResult(this, result);
+ if (this._telemetryStartTime) {
+ let elapsed = Date.now() - this._telemetryStartTime;
+ if (elapsed > 50) {
+ try {
+ Services.telemetry
+ .getHistogramById("PLACES_AUTOCOMPLETE_1ST_RESULT_TIME_MS")
+ .add(elapsed);
+ } catch (ex) {
+ Components.utils.reportError("Unable to report telemetry.");
+ }
+ }
+ this._telemetryStartTime = null;
+ }
+ },
+
+ /**
+ * Synchronize suggest.* prefs with autocomplete.enabled.
+ */
+ _syncEnabledPref: function PAC_syncEnabledPref()
+ {
+ let suggestPrefs = ["suggest.history", "suggest.bookmark", "suggest.openpage"];
+ let types = ["History", "Bookmark", "Openpage"];
+
+ this._enabled = safePrefGetter(this._prefs, kBrowserUrlbarAutocompleteEnabledPref,
+ true);
+ this._suggestHistory = safePrefGetter(this._prefs, "suggest.history", true);
+ this._suggestBookmark = safePrefGetter(this._prefs, "suggest.bookmark", true);
+ this._suggestOpenpage = safePrefGetter(this._prefs, "suggest.openpage", true);
+
+ if (this._enabled) {
+ // If the autocomplete preference is active, activate all suggest
+ // preferences only if all of them are false.
+ if (types.every(type => this["_suggest" + type] == false)) {
+ for (let type of suggestPrefs) {
+ this._prefs.setBoolPref(type, true);
+ }
+ }
+ } else {
+ // If the preference was deactivated, deactivate all suggest preferences.
+ for (let type of suggestPrefs) {
+ this._prefs.setBoolPref(type, false);
+ }
+ }
+ },
+
+ /**
+ * Loads the preferences that we care about.
+ *
+ * @param [optional] aRegisterObserver
+ * Indicates if the preference observer should be added or not. The
+ * default value is false.
+ * @param [optional] aTopic
+ * Observer's topic, if any.
+ * @param [optional] aSubject
+ * Observer's subject, if any.
+ */
+ _loadPrefs: function PAC_loadPrefs(aRegisterObserver, aTopic, aData)
+ {
+ // Avoid race conditions with UnifiedComplete component.
+ if (aData && !isUnifiedCompleteInstantiated()) {
+ // Synchronize suggest.* prefs with autocomplete.enabled.
+ if (aData == kBrowserUrlbarAutocompleteEnabledPref) {
+ this._syncEnabledPref();
+ } else if (aData.startsWith("suggest.")) {
+ let suggestPrefs = ["suggest.history", "suggest.bookmark", "suggest.openpage"];
+ this._prefs.setBoolPref(kBrowserUrlbarAutocompleteEnabledPref,
+ suggestPrefs.some(pref => safePrefGetter(this._prefs, pref, true)));
+ }
+ }
+
+ this._enabled = safePrefGetter(this._prefs,
+ kBrowserUrlbarAutocompleteEnabledPref,
+ true);
+ this._matchBehavior = safePrefGetter(this._prefs,
+ "matchBehavior",
+ MATCH_BOUNDARY_ANYWHERE);
+ this._filterJavaScript = safePrefGetter(this._prefs, "filter.javascript", true);
+ this._maxRichResults = safePrefGetter(this._prefs, "maxRichResults", 25);
+ this._restrictHistoryToken = safePrefGetter(this._prefs,
+ "restrict.history", "^");
+ this._restrictBookmarkToken = safePrefGetter(this._prefs,
+ "restrict.bookmark", "*");
+ this._restrictTypedToken = safePrefGetter(this._prefs, "restrict.typed", "~");
+ this._restrictTagToken = safePrefGetter(this._prefs, "restrict.tag", "+");
+ this._restrictOpenPageToken = safePrefGetter(this._prefs,
+ "restrict.openpage", "%");
+ this._matchTitleToken = safePrefGetter(this._prefs, "match.title", "#");
+ this._matchURLToken = safePrefGetter(this._prefs, "match.url", "@");
+
+ this._suggestHistory = safePrefGetter(this._prefs, "suggest.history", true);
+ this._suggestBookmark = safePrefGetter(this._prefs, "suggest.bookmark", true);
+ this._suggestOpenpage = safePrefGetter(this._prefs, "suggest.openpage", true);
+ this._suggestTyped = safePrefGetter(this._prefs, "suggest.history.onlyTyped", false);
+
+ // If history is not set, onlyTyped value should be ignored.
+ if (!this._suggestHistory) {
+ this._suggestTyped = false;
+ }
+ let types = ["History", "Bookmark", "Openpage", "Typed"];
+ this._defaultBehavior = types.reduce((memo, type) => {
+ let prefValue = this["_suggest" + type];
+ return memo | (prefValue &&
+ Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type.toUpperCase()]);
+ }, 0);
+
+ // Further restrictions to apply for "empty searches" (i.e. searches for "").
+ // The empty behavior is typed history, if history is enabled. Otherwise,
+ // it is bookmarks, if they are enabled. If both history and bookmarks are disabled,
+ // it defaults to open pages.
+ this._emptySearchDefaultBehavior = Ci.mozIPlacesAutoComplete.BEHAVIOR_RESTRICT;
+ if (this._suggestHistory) {
+ this._emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
+ Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED;
+ } else if (this._suggestBookmark) {
+ this._emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;
+ } else {
+ this._emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE;
+ }
+
+ // Validate matchBehavior; default to MATCH_BOUNDARY_ANYWHERE.
+ if (this._matchBehavior != MATCH_ANYWHERE &&
+ this._matchBehavior != MATCH_BOUNDARY &&
+ this._matchBehavior != MATCH_BEGINNING) {
+ this._matchBehavior = MATCH_BOUNDARY_ANYWHERE;
+ }
+ // register observer
+ if (aRegisterObserver) {
+ this._prefs.addObserver("", this, false);
+ }
+ },
+
+ /**
+ * Given an array of tokens, this function determines which query should be
+ * ran. It also removes any special search tokens.
+ *
+ * @param aTokens
+ * An array of search tokens.
+ * @return an object with two properties:
+ * query: the correctly optimized, bound query to search the database
+ * with.
+ * tokens: the filtered list of tokens to search with.
+ */
+ _getSearch: function PAC_getSearch(aTokens)
+ {
+ let foundToken = false;
+ let restrict = (behavior) => {
+ if (!foundToken) {
+ this._behavior = 0;
+ this._setBehavior("restrict");
+ foundToken = true;
+ }
+ this._setBehavior(behavior);
+ };
+
+ // Set the proper behavior so our call to _getBoundSearchQuery gives us the
+ // correct query.
+ for (let i = aTokens.length - 1; i >= 0; i--) {
+ switch (aTokens[i]) {
+ case this._restrictHistoryToken:
+ restrict("history");
+ break;
+ case this._restrictBookmarkToken:
+ restrict("bookmark");
+ break;
+ case this._restrictTagToken:
+ restrict("tag");
+ break;
+ case this._restrictOpenPageToken:
+ if (!this._enableActions) {
+ continue;
+ }
+ restrict("openpage");
+ break;
+ case this._matchTitleToken:
+ restrict("title");
+ break;
+ case this._matchURLToken:
+ restrict("url");
+ break;
+ case this._restrictTypedToken:
+ restrict("typed");
+ break;
+ default:
+ // We do not want to remove the token if we did not match.
+ continue;
+ }
+
+ aTokens.splice(i, 1);
+ }
+
+ // Set the right JavaScript behavior based on our preference. Note that the
+ // preference is whether or not we should filter JavaScript, and the
+ // behavior is if we should search it or not.
+ if (!this._filterJavaScript) {
+ this._setBehavior("javascript");
+ }
+
+ return {
+ query: this._getBoundSearchQuery(this._matchBehavior, aTokens),
+ tokens: aTokens
+ };
+ },
+
+ /**
+ * @return a string consisting of the search query to be used based on the
+ * previously set urlbar suggestion preferences.
+ */
+ _getSuggestionPrefQuery: function PAC_getSuggestionPrefQuery()
+ {
+ if (!this._hasBehavior("restrict") && this._hasBehavior("history") &&
+ this._hasBehavior("bookmark")) {
+ return this._hasBehavior("typed") ? this._customQuery("AND h.typed = 1")
+ : this._defaultQuery;
+ }
+ let conditions = [];
+ if (this._hasBehavior("history")) {
+ // Enforce ignoring the visit_count index, since the frecency one is much
+ // faster in this case. ANALYZE helps the query planner to figure out the
+ // faster path, but it may not have up-to-date information yet.
+ conditions.push("+h.visit_count > 0");
+ }
+ if (this._hasBehavior("typed")) {
+ conditions.push("h.typed = 1");
+ }
+ if (this._hasBehavior("bookmark")) {
+ conditions.push("bookmarked");
+ }
+ if (this._hasBehavior("tag")) {
+ conditions.push("tags NOTNULL");
+ }
+
+ return conditions.length ? this._customQuery("AND " + conditions.join(" AND "))
+ : this._defaultQuery;
+ },
+
+ /**
+ * Obtains the search query to be used based on the previously set search
+ * behaviors (accessed by this._hasBehavior). The query is bound and ready to
+ * execute.
+ *
+ * @param aMatchBehavior
+ * How this query should match its tokens to the search string.
+ * @param aTokens
+ * An array of search tokens.
+ * @return the correctly optimized query to search the database with and the
+ * new list of tokens to search with. The query has all the needed
+ * parameters bound, so consumers can execute it without doing any
+ * additional work.
+ */
+ _getBoundSearchQuery: function PAC_getBoundSearchQuery(aMatchBehavior,
+ aTokens)
+ {
+ let query = this._getSuggestionPrefQuery();
+
+ // Bind the needed parameters to the query so consumers can use it.
+ let params = query.params;
+ params.parent = PlacesUtils.tagsFolderId;
+ params.query_type = kQueryTypeFiltered;
+ params.matchBehavior = aMatchBehavior;
+ params.searchBehavior = this._behavior;
+
+ // We only want to search the tokens that we are left with - not the
+ // original search string.
+ params.searchString = aTokens.join(" ");
+
+ // Limit the query to the the maximum number of desired results.
+ // This way we can avoid doing more work than needed.
+ params.maxResults = this._maxRichResults;
+
+ return query;
+ },
+
+ _getBoundOpenPagesQuery: function PAC_getBoundOpenPagesQuery(aTokens)
+ {
+ let query = this._openPagesQuery;
+
+ // Bind the needed parameters to the query so consumers can use it.
+ let params = query.params;
+ params.query_type = kQueryTypeFiltered;
+ params.matchBehavior = this._matchBehavior;
+ params.searchBehavior = this._behavior;
+
+ // We only want to search the tokens that we are left with - not the
+ // original search string.
+ params.searchString = aTokens.join(" ");
+ params.maxResults = this._maxRichResults;
+
+ return query;
+ },
+
+ /**
+ * Obtains the keyword query with the properly bound parameters.
+ *
+ * @param aTokens
+ * The array of search tokens to check against.
+ * @return the bound keyword query.
+ */
+ _getBoundKeywordQuery: function PAC_getBoundKeywordQuery(aTokens)
+ {
+ // The keyword is the first word in the search string, with the parameters
+ // following it.
+ let searchString = this._originalSearchString;
+ let queryString = "";
+ let queryIndex = searchString.indexOf(" ");
+ if (queryIndex != -1) {
+ queryString = searchString.substring(queryIndex + 1);
+ }
+ // We need to escape the parameters as if they were the query in a URL
+ queryString = encodeURIComponent(queryString).replace(/%20/g, "+");
+
+ // The first word could be a keyword, so that's what we'll search.
+ let keyword = aTokens[0];
+
+ let query = this._keywordQuery;
+ let params = query.params;
+ params.keyword = keyword;
+ params.query_string = queryString;
+ params.query_type = kQueryTypeKeyword;
+
+ return query;
+ },
+
+ /**
+ * Obtains the adaptive query with the properly bound parameters.
+ *
+ * @return the bound adaptive query.
+ */
+ _getBoundAdaptiveQuery: function PAC_getBoundAdaptiveQuery(aMatchBehavior)
+ {
+ // If we were not given a match behavior, use the stored match behavior.
+ if (arguments.length == 0) {
+ aMatchBehavior = this._matchBehavior;
+ }
+
+ let query = this._adaptiveQuery;
+ let params = query.params;
+ params.parent = PlacesUtils.tagsFolderId;
+ params.search_string = this._currentSearchString;
+ params.query_type = kQueryTypeFiltered;
+ params.matchBehavior = aMatchBehavior;
+ params.searchBehavior = this._behavior;
+
+ return query;
+ },
+
+ /**
+ * Processes a mozIStorageRow to generate the proper data for the AutoComplete
+ * result. This will add an entry to the current result if it matches the
+ * criteria.
+ *
+ * @param aRow
+ * The row to process.
+ * @return true if the row is accepted, and false if not.
+ */
+ _processRow: function PAC_processRow(aRow)
+ {
+ // Before we do any work, make sure this entry isn't already in our results.
+ let entryId = aRow.getResultByIndex(kQueryIndexPlaceId);
+ let escapedEntryURL = aRow.getResultByIndex(kQueryIndexURL);
+ let openPageCount = aRow.getResultByIndex(kQueryIndexOpenPageCount) || 0;
+
+ // If actions are enabled and the page is open, add only the switch-to-tab
+ // result. Otherwise, add the normal result.
+ let [url, action] = this._enableActions && openPageCount > 0 && this._hasBehavior("openpage") ?
+ ["moz-action:switchtab," + escapedEntryURL, "action "] :
+ [escapedEntryURL, ""];
+
+ if (this._inResults(entryId, url)) {
+ return false;
+ }
+
+ let entryTitle = aRow.getResultByIndex(kQueryIndexTitle) || "";
+ let entryFavicon = aRow.getResultByIndex(kQueryIndexFaviconURL) || "";
+ let entryBookmarked = aRow.getResultByIndex(kQueryIndexBookmarked);
+ let entryBookmarkTitle = entryBookmarked ?
+ aRow.getResultByIndex(kQueryIndexBookmarkTitle) : null;
+ let entryTags = aRow.getResultByIndex(kQueryIndexTags) || "";
+
+ // Always prefer the bookmark title unless it is empty
+ let title = entryBookmarkTitle || entryTitle;
+
+ let style;
+ if (aRow.getResultByIndex(kQueryIndexQueryType) == kQueryTypeKeyword) {
+ style = "keyword";
+ title = NetUtil.newURI(escapedEntryURL).host;
+ }
+
+ // We will always prefer to show tags if we have them.
+ let showTags = !!entryTags;
+
+ // However, we'll act as if a page is not bookmarked if the user wants
+ // only history and not bookmarks and there are no tags.
+ if (this._hasBehavior("history") && !this._hasBehavior("bookmark") &&
+ !showTags) {
+ showTags = false;
+ style = "favicon";
+ }
+
+ // If we have tags and should show them, we need to add them to the title.
+ if (showTags) {
+ title += kTitleTagsSeparator + entryTags;
+ }
+ // We have to determine the right style to display. Tags show the tag icon,
+ // bookmarks get the bookmark icon, and keywords get the keyword icon. If
+ // the result does not fall into any of those, it just gets the favicon.
+ if (!style) {
+ // It is possible that we already have a style set (from a keyword
+ // search or because of the user's preferences), so only set it if we
+ // haven't already done so.
+ if (showTags) {
+ style = "tag";
+ }
+ else if (entryBookmarked) {
+ style = "bookmark";
+ }
+ else {
+ style = "favicon";
+ }
+ }
+
+ this._addToResults(entryId, url, title, entryFavicon, action + style);
+ return true;
+ },
+
+ /**
+ * Checks to see if the given place has already been added to the results.
+ *
+ * @param aPlaceId
+ * The place id to check for, may be null.
+ * @param aUrl
+ * The url to check for.
+ * @return true if the place has been added, false otherwise.
+ *
+ * @note Must check both the id and the url for a negative match, since
+ * autocomplete may run in the middle of a new page addition. In such
+ * a case the switch-to-tab query would hash the page by url, then a
+ * next query, running after the page addition, would hash it by id.
+ * It's not possible to just rely on url though, since keywords
+ * dynamically modify the url to include their search string.
+ */
+ _inResults: function PAC_inResults(aPlaceId, aUrl)
+ {
+ if (aPlaceId && aPlaceId in this._usedPlaces) {
+ return true;
+ }
+ return aUrl in this._usedPlaces;
+ },
+
+ /**
+ * Adds a result to the AutoComplete results. Also tracks that we've added
+ * this place_id into the result set.
+ *
+ * @param aPlaceId
+ * The place_id of the item to be added to the result set. This is
+ * used by _inResults.
+ * @param aURISpec
+ * The URI spec for the entry.
+ * @param aTitle
+ * The title to give the entry.
+ * @param aFaviconSpec
+ * The favicon to give to the entry.
+ * @param aStyle
+ * Indicates how the entry should be styled when displayed.
+ */
+ _addToResults: function PAC_addToResults(aPlaceId, aURISpec, aTitle,
+ aFaviconSpec, aStyle)
+ {
+ // Add this to our internal tracker to ensure duplicates do not end up in
+ // the result. _usedPlaces is an Object that is being used as a set.
+ // Not all entries have a place id, thus we fallback to the url for them.
+ // We cannot use only the url since keywords entries are modified to
+ // include the search string, and would be returned multiple times. Ids
+ // are faster too.
+ this._usedPlaces[aPlaceId || aURISpec] = true;
+
+ // Obtain the favicon for this URI.
+ let favicon;
+ if (aFaviconSpec) {
+ let uri = NetUtil.newURI(aFaviconSpec);
+ favicon = PlacesUtils.favicons.getFaviconLinkForIcon(uri).spec;
+ }
+ favicon = favicon || PlacesUtils.favicons.defaultFavicon.spec;
+
+ this._result.appendMatch(aURISpec, aTitle, favicon, aStyle);
+ },
+
+ /**
+ * Determines if the specified AutoComplete behavior is set.
+ *
+ * @param aType
+ * The behavior type to test for.
+ * @return true if the behavior is set, false otherwise.
+ */
+ _hasBehavior: function PAC_hasBehavior(aType)
+ {
+ let behavior = Ci.mozIPlacesAutoComplete["BEHAVIOR_" + aType.toUpperCase()];
+
+ if (this._disablePrivateActions &&
+ behavior == Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE) {
+ return false;
+ }
+
+ return this._behavior & behavior;
+ },
+
+ /**
+ * Enables the desired AutoComplete behavior.
+ *
+ * @param aType
+ * The behavior type to set.
+ */
+ _setBehavior: function PAC_setBehavior(aType)
+ {
+ this._behavior |=
+ Ci.mozIPlacesAutoComplete["BEHAVIOR_" + aType.toUpperCase()];
+ },
+
+ /**
+ * Determines if we are done searching or not.
+ *
+ * @return true if we have completed searching, false otherwise.
+ */
+ isSearchComplete: function PAC_isSearchComplete()
+ {
+ // If _pendingQuery is null, we should no longer do any work since we have
+ // already called _finishSearch. This means we completed our search.
+ return this._pendingQuery == null;
+ },
+
+ /**
+ * Determines if the given handle of a pending statement is a pending search
+ * or not.
+ *
+ * @param aHandle
+ * A mozIStoragePendingStatement to check and see if we are waiting for
+ * results from it still.
+ * @return true if it is a pending query, false otherwise.
+ */
+ isPendingSearch: function PAC_isPendingSearch(aHandle)
+ {
+ return this._pendingQuery == aHandle;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ classID: Components.ID("d0272978-beab-4adc-a3d4-04b76acfa4e7"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsPlacesAutoComplete),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIAutoCompleteSearch,
+ Ci.nsIAutoCompleteSimpleResultListener,
+ Ci.mozIPlacesAutoComplete,
+ Ci.mozIStorageStatementCallback,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ ])
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// urlInlineComplete class
+//// component @mozilla.org/autocomplete/search;1?name=urlinline
+
+function urlInlineComplete()
+{
+ this._loadPrefs(true);
+ Services.obs.addObserver(this, kTopicShutdown, true);
+}
+
+urlInlineComplete.prototype = {
+
+/////////////////////////////////////////////////////////////////////////////////
+//// Database and query getters
+
+ __db: null,
+
+ get _db()
+ {
+ if (!this.__db && this._autofillEnabled) {
+ this.__db = PlacesUtils.history.DBConnection.clone(true);
+ }
+ return this.__db;
+ },
+
+ __hostQuery: null,
+
+ get _hostQuery()
+ {
+ if (!this.__hostQuery) {
+ // Add a trailing slash at the end of the hostname, since we always
+ // want to complete up to and including a URL separator.
+ this.__hostQuery = this._db.createAsyncStatement(
+ `/* do not warn (bug no): could index on (typed,frecency) but not worth it */
+ SELECT host || '/', prefix || host || '/'
+ FROM moz_hosts
+ WHERE host BETWEEN :search_string AND :search_string || X'FFFF'
+ AND frecency <> 0
+ ${this._autofillTyped ? "AND typed = 1" : ""}
+ ORDER BY frecency DESC
+ LIMIT 1`
+ );
+ }
+ return this.__hostQuery;
+ },
+
+ __urlQuery: null,
+
+ get _urlQuery()
+ {
+ if (!this.__urlQuery) {
+ this.__urlQuery = this._db.createAsyncStatement(
+ `/* do not warn (bug no): can't use an index */
+ SELECT h.url
+ FROM moz_places h
+ WHERE h.frecency <> 0
+ ${this._autofillTyped ? "AND h.typed = 1 " : ""}
+ AND AUTOCOMPLETE_MATCH(:searchString, h.url,
+ h.title, '',
+ h.visit_count, h.typed, 0, 0,
+ :matchBehavior, :searchBehavior)
+ ORDER BY h.frecency DESC, h.id DESC
+ LIMIT 1`
+ );
+ }
+ return this.__urlQuery;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIAutoCompleteSearch
+
+ startSearch: function UIC_startSearch(aSearchString, aSearchParam,
+ aPreviousResult, aListener)
+ {
+ // Stop the search in case the controller has not taken care of it.
+ if (this._pendingQuery) {
+ this.stopSearch();
+ }
+
+ let pendingSearch = this._pendingSearch = {};
+
+ // We want to store the original string with no leading or trailing
+ // whitespace for case sensitive searches.
+ this._originalSearchString = aSearchString;
+ this._currentSearchString =
+ fixupSearchText(this._originalSearchString.toLowerCase());
+ // The protocol and the host are lowercased by nsIURI, so it's fine to
+ // lowercase the typed prefix to add it back to the results later.
+ this._strippedPrefix = this._originalSearchString.slice(
+ 0, this._originalSearchString.length - this._currentSearchString.length
+ ).toLowerCase();
+
+ this._result = Cc["@mozilla.org/autocomplete/simple-result;1"].
+ createInstance(Ci.nsIAutoCompleteSimpleResult);
+ this._result.setSearchString(aSearchString);
+ this._result.setTypeAheadResult(true);
+
+ this._listener = aListener;
+
+ Task.spawn(function* () {
+ // Don't autoFill if the search term is recognized as a keyword, otherwise
+ // it will override default keywords behavior. Note that keywords are
+ // hashed on first use, so while the first query may delay a little bit,
+ // next ones will just hit the memory hash.
+ let dontAutoFill = this._currentSearchString.length == 0 || !this._db ||
+ (yield PlacesUtils.keywords.fetch(this._currentSearchString));
+ if (this._pendingSearch != pendingSearch)
+ return;
+ if (dontAutoFill) {
+ this._finishSearch();
+ return;
+ }
+
+ // Don't try to autofill if the search term includes any whitespace.
+ // This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH
+ // tokenizer ends up trimming the search string and returning a value
+ // that doesn't match it, or is even shorter.
+ if (/\s/.test(this._currentSearchString)) {
+ this._finishSearch();
+ return;
+ }
+
+ // Hosts have no "/" in them.
+ let lastSlashIndex = this._currentSearchString.lastIndexOf("/");
+
+ // Search only URLs if there's a slash in the search string...
+ if (lastSlashIndex != -1) {
+ // ...but not if it's exactly at the end of the search string.
+ if (lastSlashIndex < this._currentSearchString.length - 1)
+ this._queryURL();
+ else
+ this._finishSearch();
+ return;
+ }
+
+ // Do a synchronous search on the table of hosts.
+ let query = this._hostQuery;
+ query.params.search_string = this._currentSearchString.toLowerCase();
+ // This is just to measure the delay to reach the UI, not the query time.
+ TelemetryStopwatch.start(DOMAIN_QUERY_TELEMETRY);
+ let wrapper = new AutoCompleteStatementCallbackWrapper(this, {
+ handleResult: aResultSet => {
+ if (this._pendingSearch != pendingSearch)
+ return;
+ let row = aResultSet.getNextRow();
+ let trimmedHost = row.getResultByIndex(0);
+ let untrimmedHost = row.getResultByIndex(1);
+ // If the untrimmed value doesn't preserve the user's input just
+ // ignore it and complete to the found host.
+ if (untrimmedHost &&
+ !untrimmedHost.toLowerCase().includes(this._originalSearchString.toLowerCase())) {
+ untrimmedHost = null;
+ }
+
+ this._result.appendMatch(this._strippedPrefix + trimmedHost, "", "", "", untrimmedHost);
+
+ // handleCompletion() will cause the result listener to be called, and
+ // will display the result in the UI.
+ },
+
+ handleError: aError => {
+ Components.utils.reportError(
+ "URL Inline Complete: An async statement encountered an " +
+ "error: " + aError.result + ", '" + aError.message + "'");
+ },
+
+ handleCompletion: aReason => {
+ if (this._pendingSearch != pendingSearch)
+ return;
+ TelemetryStopwatch.finish(DOMAIN_QUERY_TELEMETRY);
+ this._finishSearch();
+ }
+ }, this._db);
+ this._pendingQuery = wrapper.executeAsync([query]);
+ }.bind(this));
+ },
+
+ /**
+ * Execute an asynchronous search through places, and complete
+ * up to the next URL separator.
+ */
+ _queryURL: function UIC__queryURL()
+ {
+ // The URIs in the database are fixed up, so we can match on a lowercased
+ // host, but the path must be matched in a case sensitive way.
+ let pathIndex =
+ this._originalSearchString.indexOf("/", this._strippedPrefix.length);
+ this._currentSearchString = fixupSearchText(
+ this._originalSearchString.slice(0, pathIndex).toLowerCase() +
+ this._originalSearchString.slice(pathIndex)
+ );
+
+ // Within the standard autocomplete query, we only search the beginning
+ // of URLs for 1 result.
+ let query = this._urlQuery;
+ let params = query.params;
+ params.matchBehavior = MATCH_BEGINNING_CASE_SENSITIVE;
+ params.searchBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
+ Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED |
+ Ci.mozIPlacesAutoComplete.BEHAVIOR_URL;
+ params.searchString = this._currentSearchString;
+
+ // Execute the query.
+ let wrapper = new AutoCompleteStatementCallbackWrapper(this, {
+ handleResult: aResultSet => {
+ let row = aResultSet.getNextRow();
+ let value = row.getResultByIndex(0);
+ let url = fixupSearchText(value);
+
+ let prefix = value.slice(0, value.length - stripPrefix(value).length);
+
+ // We must complete the URL up to the next separator (which is /, ? or #).
+ let separatorIndex = url.slice(this._currentSearchString.length)
+ .search(/[\/\?\#]/);
+ if (separatorIndex != -1) {
+ separatorIndex += this._currentSearchString.length;
+ if (url[separatorIndex] == "/") {
+ separatorIndex++; // Include the "/" separator
+ }
+ url = url.slice(0, separatorIndex);
+ }
+
+ // Add the result.
+ // If the untrimmed value doesn't preserve the user's input just
+ // ignore it and complete to the found url.
+ let untrimmedURL = prefix + url;
+ if (untrimmedURL &&
+ !untrimmedURL.toLowerCase().includes(this._originalSearchString.toLowerCase())) {
+ untrimmedURL = null;
+ }
+
+ this._result.appendMatch(this._strippedPrefix + url, "", "", "", untrimmedURL);
+
+ // handleCompletion() will cause the result listener to be called, and
+ // will display the result in the UI.
+ },
+
+ handleError: aError => {
+ Components.utils.reportError(
+ "URL Inline Complete: An async statement encountered an " +
+ "error: " + aError.result + ", '" + aError.message + "'");
+ },
+
+ handleCompletion: aReason => {
+ this._finishSearch();
+ }
+ }, this._db);
+ this._pendingQuery = wrapper.executeAsync([query]);
+ },
+
+ stopSearch: function UIC_stopSearch()
+ {
+ delete this._originalSearchString;
+ delete this._currentSearchString;
+ delete this._result;
+ delete this._listener;
+ delete this._pendingSearch;
+
+ if (this._pendingQuery) {
+ this._pendingQuery.cancel();
+ delete this._pendingQuery;
+ }
+ },
+
+ /**
+ * Loads the preferences that we care about.
+ *
+ * @param [optional] aRegisterObserver
+ * Indicates if the preference observer should be added or not. The
+ * default value is false.
+ */
+ _loadPrefs: function UIC_loadPrefs(aRegisterObserver)
+ {
+ let prefBranch = Services.prefs.getBranch(kBrowserUrlbarBranch);
+ let autocomplete = safePrefGetter(prefBranch,
+ kBrowserUrlbarAutocompleteEnabledPref,
+ true);
+ let autofill = safePrefGetter(prefBranch,
+ kBrowserUrlbarAutofillPref,
+ true);
+ this._autofillEnabled = autocomplete && autofill;
+ this._autofillTyped = safePrefGetter(prefBranch,
+ kBrowserUrlbarAutofillTypedPref,
+ true);
+ if (aRegisterObserver) {
+ Services.prefs.addObserver(kBrowserUrlbarBranch, this, true);
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIAutoCompleteSearchDescriptor
+
+ get searchType() {
+ return Ci.nsIAutoCompleteSearchDescriptor.SEARCH_TYPE_IMMEDIATE;
+ },
+
+ get clearingAutoFillSearchesAgain() {
+ return false;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIObserver
+
+ observe: function UIC_observe(aSubject, aTopic, aData)
+ {
+ if (aTopic == kTopicShutdown) {
+ this._closeDatabase();
+ }
+ else if (aTopic == kPrefChanged &&
+ (aData.substr(kBrowserUrlbarBranch.length) == kBrowserUrlbarAutofillPref ||
+ aData.substr(kBrowserUrlbarBranch.length) == kBrowserUrlbarAutocompleteEnabledPref ||
+ aData.substr(kBrowserUrlbarBranch.length) == kBrowserUrlbarAutofillTypedPref)) {
+ let previousAutofillTyped = this._autofillTyped;
+ this._loadPrefs();
+ if (!this._autofillEnabled) {
+ this.stopSearch();
+ this._closeDatabase();
+ }
+ else if (this._autofillTyped != previousAutofillTyped) {
+ // Invalidate the statements to update them for the new typed status.
+ this._invalidateStatements();
+ }
+ }
+ },
+
+ /**
+ * Finalizes and invalidates cached statements.
+ */
+ _invalidateStatements: function UIC_invalidateStatements()
+ {
+ // Finalize the statements that we have used.
+ let stmts = [
+ "__hostQuery",
+ "__urlQuery",
+ ];
+ for (let i = 0; i < stmts.length; i++) {
+ // We do not want to create any query we haven't already created, so
+ // see if it is a getter first.
+ if (this[stmts[i]]) {
+ this[stmts[i]].finalize();
+ this[stmts[i]] = null;
+ }
+ }
+ },
+
+ /**
+ * Closes the database.
+ */
+ _closeDatabase: function UIC_closeDatabase()
+ {
+ this._invalidateStatements();
+ if (this.__db) {
+ this._db.asyncClose();
+ this.__db = null;
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// urlInlineComplete
+
+ _finishSearch: function UIC_finishSearch()
+ {
+ // Notify the result object
+ let result = this._result;
+
+ if (result.matchCount) {
+ result.setDefaultIndex(0);
+ result.setSearchResult(Ci.nsIAutoCompleteResult["RESULT_SUCCESS"]);
+ } else {
+ result.setDefaultIndex(-1);
+ result.setSearchResult(Ci.nsIAutoCompleteResult["RESULT_NOMATCH"]);
+ }
+
+ this._listener.onSearchResult(this, result);
+ this.stopSearch();
+ },
+
+ isSearchComplete: function UIC_isSearchComplete()
+ {
+ return this._pendingQuery == null;
+ },
+
+ isPendingSearch: function UIC_isPendingSearch(aHandle)
+ {
+ return this._pendingQuery == aHandle;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ classID: Components.ID("c88fae2d-25cf-4338-a1f4-64a320ea7440"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(urlInlineComplete),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIAutoCompleteSearch,
+ Ci.nsIAutoCompleteSearchDescriptor,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ ])
+};
+
+var components = [nsPlacesAutoComplete, urlInlineComplete];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/toolkit/components/places/nsPlacesAutoComplete.manifest b/toolkit/components/places/nsPlacesAutoComplete.manifest
new file mode 100644
index 000000000..eb704f449
--- /dev/null
+++ b/toolkit/components/places/nsPlacesAutoComplete.manifest
@@ -0,0 +1,6 @@
+component {d0272978-beab-4adc-a3d4-04b76acfa4e7} nsPlacesAutoComplete.js
+contract @mozilla.org/autocomplete/search;1?name=history {d0272978-beab-4adc-a3d4-04b76acfa4e7}
+
+component {c88fae2d-25cf-4338-a1f4-64a320ea7440} nsPlacesAutoComplete.js
+contract @mozilla.org/autocomplete/search;1?name=urlinline {c88fae2d-25cf-4338-a1f4-64a320ea7440}
+
diff --git a/toolkit/components/places/nsTaggingService.js b/toolkit/components/places/nsTaggingService.js
index 1fad67a82..e367e6cb3 100644
--- a/toolkit/components/places/nsTaggingService.js
+++ b/toolkit/components/places/nsTaggingService.js
@@ -528,6 +528,10 @@ TagAutoCompleteResult.prototype = {
return this._results.length;
},
+ get typeAheadResult() {
+ return false;
+ },
+
/**
* Get the value of the result at the given index
*/
diff --git a/toolkit/components/url-classifier/HashStore.cpp b/toolkit/components/url-classifier/HashStore.cpp
index c298612aa..77bf3cbd4 100644
--- a/toolkit/components/url-classifier/HashStore.cpp
+++ b/toolkit/components/url-classifier/HashStore.cpp
@@ -964,8 +964,7 @@ HashStore::WriteFile()
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIOutputStream> out;
- rv = NS_NewCheckSummedOutputStream(getter_AddRefs(out), storeFile,
- PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE);
+ rv = NS_NewCheckSummedOutputStream(getter_AddRefs(out), storeFile);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t written;
diff --git a/toolkit/components/url-classifier/nsCheckSummedOutputStream.cpp b/toolkit/components/url-classifier/nsCheckSummedOutputStream.cpp
index 68f9f1f6f..0e89fd20c 100644
--- a/toolkit/components/url-classifier/nsCheckSummedOutputStream.cpp
+++ b/toolkit/components/url-classifier/nsCheckSummedOutputStream.cpp
@@ -13,14 +13,11 @@
// nsCheckSummedOutputStream
NS_IMPL_ISUPPORTS_INHERITED(nsCheckSummedOutputStream,
- nsSafeFileOutputStream,
- nsISafeOutputStream,
- nsIOutputStream,
- nsIFileOutputStream)
+ nsBufferedOutputStream,
+ nsISafeOutputStream)
NS_IMETHODIMP
-nsCheckSummedOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
- int32_t behaviorFlags)
+nsCheckSummedOutputStream::Init(nsIOutputStream* stream, uint32_t bufferSize)
{
nsresult rv;
mHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
@@ -29,7 +26,7 @@ nsCheckSummedOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
rv = mHash->Init(nsICryptoHash::MD5);
NS_ENSURE_SUCCESS(rv, rv);
- return nsSafeFileOutputStream::Init(file, ioFlags, perm, behaviorFlags);
+ return nsBufferedOutputStream::Init(stream, bufferSize);
}
NS_IMETHODIMP
@@ -39,12 +36,12 @@ nsCheckSummedOutputStream::Finish()
NS_ENSURE_SUCCESS(rv, rv);
uint32_t written;
- rv = nsSafeFileOutputStream::Write(reinterpret_cast<const char*>(mCheckSum.BeginReading()),
+ rv = nsBufferedOutputStream::Write(reinterpret_cast<const char*>(mCheckSum.BeginReading()),
mCheckSum.Length(), &written);
NS_ASSERTION(written == mCheckSum.Length(), "Error writing stream checksum");
NS_ENSURE_SUCCESS(rv, rv);
- return nsSafeFileOutputStream::Finish();
+ return nsBufferedOutputStream::Finish();
}
NS_IMETHODIMP
@@ -53,7 +50,7 @@ nsCheckSummedOutputStream::Write(const char *buf, uint32_t count, uint32_t *resu
nsresult rv = mHash->Update(reinterpret_cast<const uint8_t*>(buf), count);
NS_ENSURE_SUCCESS(rv, rv);
- return nsSafeFileOutputStream::Write(buf, count, result);
+ return nsBufferedOutputStream::Write(buf, count, result);
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/toolkit/components/url-classifier/nsCheckSummedOutputStream.h b/toolkit/components/url-classifier/nsCheckSummedOutputStream.h
index c2fe26b5f..b72c7da86 100644
--- a/toolkit/components/url-classifier/nsCheckSummedOutputStream.h
+++ b/toolkit/components/url-classifier/nsCheckSummedOutputStream.h
@@ -12,25 +12,27 @@
#include "nsICryptoHash.h"
#include "nsNetCID.h"
#include "nsString.h"
-#include "../../../netwerk/base/nsFileStreams.h"
#include "nsToolkitCompsCID.h"
+#include "../../../netwerk/base/nsBufferedStreams.h"
+#include "prio.h"
-class nsCheckSummedOutputStream : public nsSafeFileOutputStream
+class nsCheckSummedOutputStream : public nsBufferedOutputStream
{
public:
NS_DECL_ISUPPORTS_INHERITED
// Size of MD5 hash in bytes
static const uint32_t CHECKSUM_SIZE = 16;
+ static const uint32_t MAX_BUFFER_SIZE = 64 * 1024;
nsCheckSummedOutputStream() {}
NS_IMETHOD Finish() override;
NS_IMETHOD Write(const char *buf, uint32_t count, uint32_t *result) override;
- NS_IMETHOD Init(nsIFile* file, int32_t ioFlags, int32_t perm, int32_t behaviorFlags) override;
+ NS_IMETHOD Init(nsIOutputStream* stream, uint32_t bufferSize) override;
protected:
- virtual ~nsCheckSummedOutputStream() { nsSafeFileOutputStream::Close(); }
+ virtual ~nsCheckSummedOutputStream() { nsBufferedOutputStream::Close(); }
nsCOMPtr<nsICryptoHash> mHash;
nsCString mCheckSum;
@@ -39,13 +41,15 @@ protected:
// returns a file output stream which can be QI'ed to nsIFileOutputStream.
inline nsresult
NS_NewCheckSummedOutputStream(nsIOutputStream **result,
- nsIFile *file,
- int32_t ioFlags = -1,
- int32_t perm = -1,
- int32_t behaviorFlags = 0)
+ nsIFile *file)
{
- nsCOMPtr<nsIFileOutputStream> out = new nsCheckSummedOutputStream();
- nsresult rv = out->Init(file, ioFlags, perm, behaviorFlags);
+ nsCOMPtr<nsIOutputStream> localOutFile;
+ nsresult rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(localOutFile), file,
+ PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIBufferedOutputStream> out = new nsCheckSummedOutputStream();
+ rv = out->Init(localOutFile, nsCheckSummedOutputStream::CHECKSUM_SIZE);
if (NS_SUCCEEDED(rv)) {
out.forget(result);
}
diff --git a/toolkit/components/webextensions/ExtensionChild.jsm b/toolkit/components/webextensions/ExtensionChild.jsm
index c953dd685..5dc4e2277 100644
--- a/toolkit/components/webextensions/ExtensionChild.jsm
+++ b/toolkit/components/webextensions/ExtensionChild.jsm
@@ -325,7 +325,7 @@ class Messenger {
return this.sendMessage(messageManager, msg, recipient, responseCallback);
}
- onMessage(name) {
+ _onMessage(name, filter) {
return new SingletonEventManager(this.context, name, callback => {
let listener = {
messageFilterPermissive: this.optionalFilter,
@@ -333,7 +333,8 @@ class Messenger {
filterMessage: (sender, recipient) => {
// Ignore the message if it was sent by this Messenger.
- return sender.contextId !== this.context.contextId;
+ return (sender.contextId !== this.context.contextId &&
+ filter(sender, recipient));
},
receiveMessage: ({target, data: message, sender, recipient}) => {
@@ -373,6 +374,14 @@ class Messenger {
}).api();
}
+ onMessage(name) {
+ return this._onMessage(name, sender => sender.id === this.sender.id);
+ }
+
+ onMessageExternal(name) {
+ return this._onMessage(name, sender => sender.id !== this.sender.id);
+ }
+
_connect(messageManager, port, recipient) {
let msg = {
name: port.name,
@@ -407,7 +416,7 @@ class Messenger {
return this._connect(messageManager, port, recipient);
}
- onConnect(name) {
+ _onConnect(name, filter) {
return new SingletonEventManager(this.context, name, callback => {
let listener = {
messageFilterPermissive: this.optionalFilter,
@@ -415,7 +424,8 @@ class Messenger {
filterMessage: (sender, recipient) => {
// Ignore the port if it was created by this Messenger.
- return sender.contextId !== this.context.contextId;
+ return (sender.contextId !== this.context.contextId &&
+ filter(sender, recipient));
},
receiveMessage: ({target, data: message, sender}) => {
@@ -438,6 +448,14 @@ class Messenger {
};
}).api();
}
+
+ onConnect(name) {
+ return this._onConnect(name, sender => sender.id === this.sender.id);
+ }
+
+ onConnectExternal(name) {
+ return this._onConnect(name, sender => sender.id !== this.sender.id);
+ }
}
var apiManager = new class extends SchemaAPIManager {
@@ -745,7 +763,7 @@ class ExtensionPageContextChild extends BaseContext {
// This is the MessageSender property passed to extension.
// It can be augmented by the "page-open" hook.
- let sender = {id: extension.uuid};
+ let sender = {id: extension.id};
if (viewType == "tab") {
sender.tabId = tabId;
this.tabId = tabId;
diff --git a/toolkit/components/webextensions/ExtensionCommon.jsm b/toolkit/components/webextensions/ExtensionCommon.jsm
index a339fb27e..9ec84b5c7 100644
--- a/toolkit/components/webextensions/ExtensionCommon.jsm
+++ b/toolkit/components/webextensions/ExtensionCommon.jsm
@@ -197,10 +197,9 @@ class BaseContext {
* @returns {Promise}
*/
sendMessage(target, messageName, data, options = {}) {
- options.recipient = options.recipient || {};
+ options.recipient = Object.assign({extensionId: this.extension.id}, options.recipient);
options.sender = options.sender || {};
- options.recipient.extensionId = this.extension.id;
options.sender.extensionId = this.extension.id;
options.sender.contextId = this.contextId;
diff --git a/toolkit/components/webextensions/ExtensionContent.jsm b/toolkit/components/webextensions/ExtensionContent.jsm
index 9b9a02091..5f9b88f35 100644
--- a/toolkit/components/webextensions/ExtensionContent.jsm
+++ b/toolkit/components/webextensions/ExtensionContent.jsm
@@ -344,6 +344,7 @@ class ContentScriptContextChild extends BaseContext {
// because it enables us to create the APIs object in this sandbox object and then copying it
// into the iframe's window, see Bug 1214658 for rationale)
this.sandbox = Cu.Sandbox(contentWindow, {
+ sandboxName: `Web-Accessible Extension Page ${this.extension.id}`,
sandboxPrototype: contentWindow,
sameZoneAs: contentWindow,
wantXrays: false,
@@ -360,6 +361,7 @@ class ContentScriptContextChild extends BaseContext {
this.sandbox = Cu.Sandbox(principal, {
metadata,
+ sandboxName: `Content Script ${this.extension.id}`,
sandboxPrototype: contentWindow,
sameZoneAs: contentWindow,
wantXrays: true,
@@ -456,7 +458,7 @@ class ContentScriptContextChild extends BaseContext {
defineLazyGetter(ContentScriptContextChild.prototype, "messenger", function() {
// The |sender| parameter is passed directly to the extension.
- let sender = {id: this.extension.uuid, frameId: this.frameId, url: this.url};
+ let sender = {id: this.extension.id, frameId: this.frameId, url: this.url};
let filter = {extensionId: this.extension.id};
let optionalFilter = {frameId: this.frameId};
diff --git a/toolkit/components/webextensions/LegacyExtensionsUtils.jsm b/toolkit/components/webextensions/LegacyExtensionsUtils.jsm
index 7632548e3..e8d276fe9 100644
--- a/toolkit/components/webextensions/LegacyExtensionsUtils.jsm
+++ b/toolkit/components/webextensions/LegacyExtensionsUtils.jsm
@@ -64,7 +64,7 @@ var LegacyExtensionContext = class extends BaseContext {
{value: cloneScope, enumerable: true, configurable: true, writable: true}
);
- let sender = {id: targetExtension.uuid};
+ let sender = {id: targetExtension.id};
let filter = {extensionId: targetExtension.id};
// Legacy addons live in the main process. Messages from other addons are
// Messages from WebExtensions are sent to the main process and forwarded via
diff --git a/toolkit/components/webextensions/ext-c-runtime.js b/toolkit/components/webextensions/ext-c-runtime.js
index 8adca60ca..1dcac35da 100644
--- a/toolkit/components/webextensions/ext-c-runtime.js
+++ b/toolkit/components/webextensions/ext-c-runtime.js
@@ -9,6 +9,10 @@ function runtimeApiFactory(context) {
onMessage: context.messenger.onMessage("runtime.onMessage"),
+ onConnectExternal: context.messenger.onConnectExternal("runtime.onConnectExternal"),
+
+ onMessageExternal: context.messenger.onMessageExternal("runtime.onMessageExternal"),
+
connect: function(extensionId, connectInfo) {
let name = connectInfo !== null && connectInfo.name || "";
extensionId = extensionId || extension.id;
@@ -47,7 +51,6 @@ function runtimeApiFactory(context) {
if (options != null && typeof options != "object") {
return Promise.reject({message: "runtime.sendMessage's options argument is invalid"});
}
- // TODO(robwu): Validate option keys and values when we support it.
extensionId = extensionId || extension.id;
let recipient = {extensionId};
diff --git a/toolkit/components/webextensions/schemas/runtime.json b/toolkit/components/webextensions/schemas/runtime.json
index b3f12a768..575df7d27 100644
--- a/toolkit/components/webextensions/schemas/runtime.json
+++ b/toolkit/components/webextensions/schemas/runtime.json
@@ -535,7 +535,6 @@
},
{
"name": "onConnectExternal",
- "unsupported": true,
"type": "function",
"description": "Fired when a connection is made from another extension.",
"parameters": [
@@ -560,7 +559,6 @@
},
{
"name": "onMessageExternal",
- "unsupported": true,
"type": "function",
"description": "Fired when a message is sent from another extension/app. Cannot be used in a content script.",
"parameters": [
diff --git a/toolkit/components/webextensions/test/mochitest/mochitest.ini b/toolkit/components/webextensions/test/mochitest/mochitest.ini
index 45586237e..1f61060ad 100644
--- a/toolkit/components/webextensions/test/mochitest/mochitest.ini
+++ b/toolkit/components/webextensions/test/mochitest/mochitest.ini
@@ -59,6 +59,7 @@ skip-if = os == 'android' # Android does not support tabs API. Bug 1260250
[test_ext_contentscript_teardown.html]
skip-if = (os == 'android') # Android does not support tabs API. Bug 1260250
[test_ext_exclude_include_globs.html]
+[test_ext_external_messaging.html]
[test_ext_i18n_css.html]
[test_ext_generate.html]
[test_ext_notifications.html]
diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_all_apis.js b/toolkit/components/webextensions/test/mochitest/test_ext_all_apis.js
index 0f617c37e..25d04b36b 100644
--- a/toolkit/components/webextensions/test/mochitest/test_ext_all_apis.js
+++ b/toolkit/components/webextensions/test/mochitest/test_ext_all_apis.js
@@ -75,7 +75,9 @@ let expectedBackgroundApis = [
"runtime.getBackgroundPage",
"runtime.getBrowserInfo",
"runtime.getPlatformInfo",
+ "runtime.onConnectExternal",
"runtime.onInstalled",
+ "runtime.onMessageExternal",
"runtime.onStartup",
"runtime.onUpdateAvailable",
"runtime.openOptionsPage",
diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_external_messaging.html b/toolkit/components/webextensions/test/mochitest/test_ext_external_messaging.html
new file mode 100644
index 000000000..dfc1f9427
--- /dev/null
+++ b/toolkit/components/webextensions/test/mochitest/test_ext_external_messaging.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>WebExtension external messaging</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <script type="text/javascript" src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+function backgroundScript(id, otherId) {
+ browser.runtime.onMessage.addListener((msg, sender) => {
+ browser.test.fail(`Got unexpected message: ${uneval(msg)} ${uneval(sender)}`);
+ });
+
+ browser.runtime.onConnect.addListener(port => {
+ browser.test.fail(`Got unexpected connection: ${uneval(port.sender)}`);
+ });
+
+ browser.runtime.onMessageExternal.addListener((msg, sender) => {
+ browser.test.assertEq(otherId, sender.id, `${id}: Got expected external sender ID`);
+ browser.test.assertEq(`helo-${id}`, msg, "Got expected message");
+
+ browser.test.sendMessage("onMessage-done");
+
+ return Promise.resolve(`ehlo-${otherId}`);
+ });
+
+ browser.runtime.onConnectExternal.addListener(port => {
+ browser.test.assertEq(otherId, port.sender.id, `${id}: Got expected external connecter ID`);
+
+ port.onMessage.addListener(msg => {
+ browser.test.assertEq(`helo-${id}`, msg, "Got expected port message");
+
+ port.postMessage(`ehlo-${otherId}`);
+
+ browser.test.sendMessage("onConnect-done");
+ });
+ });
+
+ browser.test.onMessage.addListener(msg => {
+ if (msg === "go") {
+ browser.runtime.sendMessage(otherId, `helo-${otherId}`).then(result => {
+ browser.test.assertEq(`ehlo-${id}`, result, "Got expected reply");
+ browser.test.sendMessage("sendMessage-done");
+ });
+
+ let port = browser.runtime.connect(otherId);
+ port.postMessage(`helo-${otherId}`);
+
+ port.onMessage.addListener(msg => {
+ port.disconnect();
+
+ browser.test.assertEq(msg, `ehlo-${id}`, "Got expected port reply");
+ browser.test.sendMessage("connect-done");
+ });
+ }
+ });
+}
+
+function makeExtension(id, otherId) {
+ let args = `${JSON.stringify(id)}, ${JSON.stringify(otherId)}`;
+
+ let extensionData = {
+ background: `(${backgroundScript})(${args})`,
+ manifest: {
+ "applications": {"gecko": {id}},
+ },
+ };
+
+ return ExtensionTestUtils.loadExtension(extensionData);
+}
+
+add_task(function* test_contentscript() {
+ const ID1 = "foo-message@mochitest.mozilla.org";
+ const ID2 = "bar-message@mochitest.mozilla.org";
+
+ let extension1 = makeExtension(ID1, ID2);
+ let extension2 = makeExtension(ID2, ID1);
+
+ yield Promise.all([extension1.startup(), extension2.startup()]);
+
+ extension1.sendMessage("go");
+ extension2.sendMessage("go");
+
+ yield Promise.all([
+ extension1.awaitMessage("sendMessage-done"),
+ extension2.awaitMessage("sendMessage-done"),
+
+ extension1.awaitMessage("onMessage-done"),
+ extension2.awaitMessage("onMessage-done"),
+
+ extension1.awaitMessage("connect-done"),
+ extension2.awaitMessage("connect-done"),
+
+ extension1.awaitMessage("onConnect-done"),
+ extension2.awaitMessage("onConnect-done"),
+ ]);
+
+ yield extension1.unload();
+ yield extension2.unload();
+});
+</script>
+
+</body>
+</html>
diff --git a/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_reply2.html b/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_reply2.html
index 1ebc1b40f..5c350be2f 100644
--- a/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_reply2.html
+++ b/toolkit/components/webextensions/test/mochitest/test_ext_sendmessage_reply2.html
@@ -13,48 +13,119 @@
<script type="text/javascript">
"use strict";
-function backgroundScript(token) {
+function backgroundScript(token, id, otherId) {
+ browser.tabs.create({url: "tab.html"});
+
browser.runtime.onMessage.addListener((msg, sender, sendReply) => {
- browser.test.assertTrue(sender.tab.url.endsWith("file_sample.html"), "sender url correct");
+ browser.test.assertEq(id, sender.id, `${id}: Got expected sender ID`);
- if (msg == "done") {
- browser.test.notifyPass("sendmessage_reply");
- return;
+ if (msg === `content-${token}`) {
+ browser.test.assertTrue(sender.tab.url.endsWith("file_sample.html"),
+ `${id}: sender url correct`);
+
+ let tabId = sender.tab.id;
+ browser.tabs.sendMessage(tabId, `${token}-contentMessage`);
+
+ sendReply(`${token}-done`);
+ } else if (msg === `tab-${token}`) {
+ browser.runtime.sendMessage(otherId, `${otherId}-tabMessage`);
+ browser.runtime.sendMessage(`${token}-tabMessage`);
+
+ sendReply(`${token}-done`);
+ } else {
+ browser.test.fail(`${id}: Unexpected runtime message received: ${msg} ${uneval(sender)}`);
}
+ });
+
+ browser.runtime.onMessageExternal.addListener((msg, sender, sendReply) => {
+ browser.test.assertEq(otherId, sender.id, `${id}: Got expected external sender ID`);
+
+ if (msg === `content-${id}`) {
+ browser.test.assertTrue(sender.tab.url.endsWith("file_sample.html"),
+ `${id}: external sender url correct`);
+
+ sendReply(`${otherId}-done`);
+ } else if (msg === `tab-${id}`) {
+ sendReply(`${otherId}-done`);
+ } else if (msg !== `${id}-tabMessage`) {
+ browser.test.fail(`${id}: Unexpected runtime external message received: ${msg} ${uneval(sender)}`);
+ }
+ });
+}
+
+function contentScript(token, id, otherId) {
+ let gotContentMessage = false;
+ browser.runtime.onMessage.addListener((msg, sender, sendReply) => {
+ browser.test.assertEq(id, sender.id, `${id}: Got expected sender ID`);
+
+ browser.test.assertEq(`${token}-contentMessage`, msg,
+ `${id}: Correct content script message`);
+ if (msg === `${token}-contentMessage`) {
+ gotContentMessage = true;
+ }
+ });
+
+ Promise.all([
+ browser.runtime.sendMessage(otherId, `content-${otherId}`).then(resp => {
+ browser.test.assertEq(`${id}-done`, resp, `${id}: Correct content script external response token`);
+ }),
- let tabId = sender.tab.id;
- browser.tabs.sendMessage(tabId, `${token}-tabMessage`);
+ browser.runtime.sendMessage(`content-${token}`).then(resp => {
+ browser.test.assertEq(`${token}-done`, resp, `${id}: Correct content script response token`);
+ }),
+ ]).then(() => {
+ browser.test.assertTrue(gotContentMessage, `${id}: Got content script message`);
- browser.test.assertEq(msg, token, "token matches");
- sendReply(`${token}-done`);
+ browser.test.sendMessage("content-script-done");
});
}
-function contentScript(token) {
+function tabScript(token, id, otherId) {
let gotTabMessage = false;
- let badTabMessage = false;
browser.runtime.onMessage.addListener((msg, sender, sendReply) => {
- if (msg == `${token}-tabMessage`) {
+ browser.test.assertEq(id, sender.id, `${id}: Got expected sender ID`);
+
+ if (String(msg).startsWith("content-")) {
+ return;
+ }
+
+ browser.test.assertEq(`${token}-tabMessage`, msg,
+ `${id}: Correct tab script message`);
+ if (msg === `${token}-tabMessage`) {
gotTabMessage = true;
- } else {
- badTabMessage = true;
}
});
- browser.runtime.sendMessage(token, function(resp) {
- if (resp != `${token}-done` || !gotTabMessage || badTabMessage) {
- return; // test failed
- }
- browser.runtime.sendMessage("done");
+ Promise.all([
+ browser.runtime.sendMessage(otherId, `tab-${otherId}`).then(resp => {
+ browser.test.assertEq(`${id}-done`, resp, `${id}: Correct tab script external response token`);
+ }),
+
+ browser.runtime.sendMessage(`tab-${token}`).then(resp => {
+ browser.test.assertEq(`${token}-done`, resp, `${id}: Correct tab script response token`);
+ }),
+ ]).then(() => {
+ browser.test.assertTrue(gotTabMessage, `${id}: Got tab script message`);
+
+ window.close();
+
+ browser.test.sendMessage("tab-script-done");
});
}
-function makeExtension() {
+function makeExtension(id, otherId) {
let token = Math.random();
+
+ let args = `${token}, ${JSON.stringify(id)}, ${JSON.stringify(otherId)}`;
+
let extensionData = {
- background: `(${backgroundScript})(${token})`,
+ background: `(${backgroundScript})(${args})`,
manifest: {
+ "applications": {"gecko": {id}},
+
"permissions": ["tabs"],
+
+
"content_scripts": [{
"matches": ["http://mochi.test/*/file_sample.html"],
"js": ["content_script.js"],
@@ -63,29 +134,46 @@ function makeExtension() {
},
files: {
- "content_script.js": `(${contentScript})(${token})`,
+ "tab.html": `<!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ <script src="tab.js"><\/script>
+ </head>
+ </html>`,
+
+ "tab.js": `(${tabScript})(${args})`,
+
+ "content_script.js": `(${contentScript})(${args})`,
},
};
return extensionData;
}
add_task(function* test_contentscript() {
- let extension1 = ExtensionTestUtils.loadExtension(makeExtension());
- let extension2 = ExtensionTestUtils.loadExtension(makeExtension());
+ const ID1 = "sendmessage1@mochitest.mozilla.org";
+ const ID2 = "sendmessage2@mochitest.mozilla.org";
+
+ let extension1 = ExtensionTestUtils.loadExtension(makeExtension(ID1, ID2));
+ let extension2 = ExtensionTestUtils.loadExtension(makeExtension(ID2, ID1));
yield Promise.all([extension1.startup(), extension2.startup()]);
let win = window.open("file_sample.html");
- yield Promise.all([waitForLoad(win),
- extension1.awaitFinish("sendmessage_reply"),
- extension2.awaitFinish("sendmessage_reply")]);
+ yield waitForLoad(win);
+
+ yield Promise.all([
+ extension1.awaitMessage("content-script-done"),
+ extension2.awaitMessage("content-script-done"),
+ extension1.awaitMessage("tab-script-done"),
+ extension2.awaitMessage("tab-script-done"),
+ ]);
win.close();
yield extension1.unload();
yield extension2.unload();
- info("extensions unloaded");
});
</script>
diff --git a/toolkit/components/webextensions/test/xpcshell/test_ext_legacy_extension_context.js b/toolkit/components/webextensions/test/xpcshell/test_ext_legacy_extension_context.js
index 63d5361a1..770851472 100644
--- a/toolkit/components/webextensions/test/xpcshell/test_ext_legacy_extension_context.js
+++ b/toolkit/components/webextensions/test/xpcshell/test_ext_legacy_extension_context.js
@@ -108,7 +108,7 @@ add_task(function* test_legacy_extension_context() {
"Got the expected message");
ok(msgSender, "Got a message sender object");
- equal(msgSender.id, extensionInfo.uuid, "The sender has the expected id property");
+ equal(msgSender.id, extension.id, "The sender has the expected id property");
equal(msgSender.url, extensionInfo.bgURL, "The sender has the expected url property");
// Wait confirmation that the reply has been received.
@@ -136,7 +136,7 @@ add_task(function* test_legacy_extension_context() {
ok(port, "Got the Port API object");
ok(port.sender, "The port has a sender property");
- equal(port.sender.id, extensionInfo.uuid,
+ equal(port.sender.id, extension.id,
"The port sender has the expected id property");
equal(port.sender.url, extensionInfo.bgURL,
"The port sender has the expected url property");
diff --git a/toolkit/components/xulstore/XULStore.js b/toolkit/components/xulstore/XULStore.js
index c2721327c..8b5bc1313 100644
--- a/toolkit/components/xulstore/XULStore.js
+++ b/toolkit/components/xulstore/XULStore.js
@@ -63,11 +63,21 @@ XULStore.prototype = {
load: function () {
Services.obs.addObserver(this, "profile-before-change", true);
- this._storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let profileType = "ProfD";
+ try {
+ this._storeFile = Services.dirsvc.get(profileType, Ci.nsIFile);
+ } catch (ex) {
+ try {
+ profileType = "ProfDS";
+ this._storeFile = Services.dirsvc.get(profileType, Ci.nsIFile);
+ } catch (ex) {
+ throw new Error("Can't find profile directory.");
+ }
+ }
this._storeFile.append(STOREDB_FILENAME);
if (!this._storeFile.exists()) {
- this.import();
+ this.import(profileType);
} else {
this.readFile();
}
@@ -90,8 +100,8 @@ XULStore.prototype = {
Services.console.logStringMessage("XULStore: " + message);
},
- import: function() {
- let localStoreFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ import(profileType) {
+ let localStoreFile = Services.dirsvc.get(profileType || "ProfD", Ci.nsIFile);
localStoreFile.append("localstore.rdf");
if (!localStoreFile.exists()) {
diff --git a/toolkit/content/plugins.html b/toolkit/content/plugins.html
index d389f52dd..15bbed9bb 100644
--- a/toolkit/content/plugins.html
+++ b/toolkit/content/plugins.html
@@ -75,15 +75,6 @@
enabledplugins.appendChild(document.createTextNode(pluginsbundle.GetStringFromName(label)));
fragment.appendChild(enabledplugins);
- var deprecation = document.createElement("p");
- deprecation.setAttribute("class", "notice");
- deprecation.textContent = pluginsbundle.GetStringFromName("deprecation_description") + " \u00A0 ";
- var deprecationLink = document.createElement("a");
- deprecationLink.textContent = pluginsbundle.GetStringFromName("deprecation_learn_more");
- deprecationLink.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + "npapi";
- deprecation.appendChild(deprecationLink);
- fragment.appendChild(deprecation);
-
var stateNames = {};
["STATE_SOFTBLOCKED",
"STATE_BLOCKED",
diff --git a/toolkit/content/widgets/toolbar.xml b/toolkit/content/widgets/toolbar.xml
index e1f58f7aa..55cef8244 100644
--- a/toolkit/content/widgets/toolbar.xml
+++ b/toolkit/content/widgets/toolbar.xml
@@ -54,7 +54,7 @@
// Look to see if there is a toolbarset.
this.toolbarset = this.firstChild;
while (this.toolbarset && this.toolbarset.localName != "toolbarset") {
- this.toolbarset = toolbarset.nextSibling;
+ this.toolbarset = this.toolbarset.nextSibling;
}
if (this.toolbarset) {
diff --git a/toolkit/forgetaboutsite/ForgetAboutSite.jsm b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
index 5250ca75d..8c7825392 100644
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -45,175 +45,201 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
this.ForgetAboutSite = {
- removeDataFromDomain: function CRH_removeDataFromDomain(aDomain)
+ removeDataFromDomain: Task.async(function* (aDomain)
{
PlacesUtils.history.removePagesFromHost(aDomain, true);
+ let promises = [];
// Cache
- let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
- getService(Ci.nsICacheStorageService);
- // NOTE: there is no way to clear just that domain, so we clear out
- // everything)
- try {
+ promises.push(Task.spawn(function*() {
+ let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
+ getService(Ci.nsICacheStorageService);
+ // NOTE: there is no way to clear just that domain, so we clear out
+ // everything)
cs.clear();
- } catch (ex) {
- Cu.reportError("Exception thrown while clearing the cache: " +
- ex.toString());
- }
+ }).catch(ex => {
+ throw new Error("Exception thrown while clearing the cache: " + ex);
+ }));
// Image Cache
- let imageCache = Cc["@mozilla.org/image/tools;1"].
- getService(Ci.imgITools).getImgCacheForDocument(null);
- try {
+ promises.push(Task.spawn(function*() {
+ let imageCache = Cc["@mozilla.org/image/tools;1"].
+ getService(Ci.imgITools).getImgCacheForDocument(null);
imageCache.clearCache(false); // true=chrome, false=content
- } catch (ex) {
- Cu.reportError("Exception thrown while clearing the image cache: " +
- ex.toString());
- }
+ }).catch(ex => {
+ throw new Error("Exception thrown while clearing the image cache: " + ex);
+ }));
// Cookies
- let cm = Cc["@mozilla.org/cookiemanager;1"].
- getService(Ci.nsICookieManager2);
- let enumerator = cm.getCookiesWithOriginAttributes(JSON.stringify({}), aDomain);
- while (enumerator.hasMoreElements()) {
- let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
- cm.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
- }
+ // Need to maximize the number of cookies cleaned here
+ promises.push(Task.spawn(function*() {
+ let cm = Cc["@mozilla.org/cookiemanager;1"].
+ getService(Ci.nsICookieManager2);
+ let enumerator = cm.getCookiesWithOriginAttributes(JSON.stringify({}), aDomain);
+ while (enumerator.hasMoreElements()) {
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
+ cm.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
+ }
+ }).catch(ex => {
+ throw new Error("Exception thrown while clearning cookies: " + ex);
+ }));
// EME
- let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
- getService(Ci.mozIGeckoMediaPluginChromeService);
- mps.forgetThisSite(aDomain, JSON.stringify({}));
+ promises.push(Task.spawn(function*() {
+ let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
+ getService(Ci.mozIGeckoMediaPluginChromeService);
+ mps.forgetThisSite(aDomain, JSON.stringify({}));
+ }).catch(ex => {
+ throw new Error("Exception thrown while clearing Encrypted Media Extensions: " + ex);
+ }));
// Plugin data
const phInterface = Ci.nsIPluginHost;
const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
let tags = ph.getPluginTags();
- let promises = [];
for (let i = 0; i < tags.length; i++) {
- let promise = new Promise(resolve => {
- let tag = tags[i];
+ promises.push(new Promise(resolve => {
try {
- ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1, function(rv) {
- resolve();
- });
+ ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1, resolve);
} catch (e) {
// Ignore errors from the plugin, but resolve the promise
+ // We cannot check if something is a bailout or an error
resolve();
}
- });
- promises.push(promise);
+ }));
}
// Downloads
- Task.spawn(function*() {
+ promises.push(Task.spawn(function*() {
let list = yield Downloads.getList(Downloads.ALL);
list.removeFinished(download => hasRootDomain(
- NetUtil.newURI(download.source.url).host, aDomain));
- }).then(null, Cu.reportError);
+ NetUtil.newURI(download.source.url).host, aDomain));
+ }).catch(ex => {
+ throw new Error("Exception in clearing Downloads: " + ex);
+ }));
// Passwords
- let lm = Cc["@mozilla.org/login-manager;1"].
- getService(Ci.nsILoginManager);
- // Clear all passwords for domain
- try {
+ promises.push(Task.spawn(function*() {
+ let lm = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+ // Clear all passwords for domain
let logins = lm.getAllLogins();
- for (let i = 0; i < logins.length; i++)
- if (hasRootDomain(logins[i].hostname, aDomain))
+ for (let i = 0; i < logins.length; i++) {
+ if (hasRootDomain(logins[i].hostname, aDomain)) {
lm.removeLogin(logins[i]);
- }
- // XXXehsan: is there a better way to do this rather than this
- // hacky comparison?
- catch (ex) {
- if (!ex.message.includes("User canceled Master Password entry")) {
- throw ex;
+ }
}
- }
+ }).catch(ex => {
+ // XXX:
+ // Is there a better way to do this rather than this hacky comparison?
+ // Copied this from toolkit/components/passwordmgr/crypto-SDR.js
+ if (!ex.message.includes("User canceled master password entry")) {
+ throw new Error("Exception occured in clearing passwords: " + ex);
+ }
+ }));
// Permissions
let pm = Cc["@mozilla.org/permissionmanager;1"].
getService(Ci.nsIPermissionManager);
// Enumerate all of the permissions, and if one matches, remove it
- enumerator = pm.enumerator;
+ let enumerator = pm.enumerator;
while (enumerator.hasMoreElements()) {
let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
- try {
- if (hasRootDomain(perm.principal.URI.host, aDomain)) {
- pm.removePermission(perm);
+ promises.push(new Promise((resolve, reject) => {
+ try {
+ if (hasRootDomain(perm.principal.URI.host, aDomain)) {
+ pm.removePermission(perm);
+ }
+ } catch (ex) {
+ // Ignore entry
+ } finally {
+ resolve();
}
- } catch (e) {
- /* Ignore entry */
- }
+ }));
}
// Offline Storages
- let qms = Cc["@mozilla.org/dom/quota-manager-service;1"].
- getService(Ci.nsIQuotaManagerService);
- // delete data from both HTTP and HTTPS sites
- let caUtils = {};
- let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
- getService(Ci.mozIJSSubScriptLoader);
- scriptLoader.loadSubScript("chrome://global/content/contentAreaUtils.js",
- caUtils);
- let httpURI = caUtils.makeURI("http://" + aDomain);
- let httpsURI = caUtils.makeURI("https://" + aDomain);
- // Following code section has been reverted to the state before Bug 1238183,
- // but added a new argument to clearStoragesForPrincipal() for indicating
- // clear all storages under a given origin.
- let httpPrincipal = Services.scriptSecurityManager
- .createCodebasePrincipal(httpURI, {});
- let httpsPrincipal = Services.scriptSecurityManager
- .createCodebasePrincipal(httpsURI, {});
- qms.clearStoragesForPrincipal(httpPrincipal, null, true);
- qms.clearStoragesForPrincipal(httpsPrincipal, null, true);
-
-
- function onContentPrefsRemovalFinished() {
- // Everybody else (including extensions)
- Services.obs.notifyObservers(null, "browser:purge-domain-data", aDomain);
- }
+ promises.push(Task.spawn(function*() {
+ let qms = Cc["@mozilla.org/dom/quota-manager-service;1"].
+ getService(Ci.nsIQuotaManagerService);
+ // delete data from both HTTP and HTTPS sites
+ let httpURI = NetUtil.newURI("http://" + aDomain);
+ let httpsURI = NetUtil.newURI("https://" + aDomain);
+ // Following code section has been reverted to the state before Bug 1238183,
+ // but added a new argument to clearStoragesForPrincipal() for indicating
+ // clear all storages under a given origin.
+ let httpPrincipal = Services.scriptSecurityManager
+ .createCodebasePrincipal(httpURI, {});
+ let httpsPrincipal = Services.scriptSecurityManager
+ .createCodebasePrincipal(httpsURI, {});
+ qms.clearStoragesForPrincipal(httpPrincipal, null, true);
+ qms.clearStoragesForPrincipal(httpsPrincipal, null, true);
+ }).catch(ex => {
+ throw new Error("Exception occured while clearing offline storages: " + ex);
+ }));
// Content Preferences
- let cps2 = Cc["@mozilla.org/content-pref/service;1"].
- getService(Ci.nsIContentPrefService2);
- cps2.removeBySubdomain(aDomain, null, {
- handleCompletion: () => onContentPrefsRemovalFinished(),
- handleError: function() {}
- });
+ promises.push(Task.spawn(function*() {
+ let cps2 = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService2);
+ cps2.removeBySubdomain(aDomain, null, {
+ handleCompletion: (reason) => {
+ // Notify other consumers, including extensions
+ Services.obs.notifyObservers(null, "browser:purge-domain-data", aDomain);
+ if (reason === cps2.COMPLETE_ERROR) {
+ throw new Error("Exception occured while clearing content preferences");
+ }
+ },
+ handleError() {}
+ });
+ }));
// Predictive network data - like cache, no way to clear this per
// domain, so just trash it all
- let np = Cc["@mozilla.org/network/predictor;1"].
- getService(Ci.nsINetworkPredictor);
- np.reset();
-
- // Push notifications.
- promises.push(new Promise(resolve => {
- var push = Cc["@mozilla.org/push/Service;1"]
- .getService(Ci.nsIPushService);
+ promises.push(Task.spawn(function*() {
+ let np = Cc["@mozilla.org/network/predictor;1"].
+ getService(Ci.nsINetworkPredictor);
+ np.reset();
+ }).catch(ex => {
+ throw new Error("Exception occured while clearing predictive network data: " + ex);
+ }));
+
+ // Push notifications
+ promises.push(Task.spawn(function*() {
+ var push = Cc["@mozilla.org/push/Service;1"].
+ getService(Ci.nsIPushService);
push.clearForDomain(aDomain, status => {
- (Components.isSuccessCode(status) ? resolve : reject)(status);
+ if (!Components.isSuccessCode(status)) {
+ throw new Error("Exception occured while clearing push notifications: " + status);
+ }
});
- }).catch(e => {
- Cu.reportError("Exception thrown while clearing Push notifications: " +
- e.toString());
}));
// HSTS and HPKP
// TODO (bug 1290529): also remove HSTS/HPKP information for subdomains.
// Since we can't enumerate the information in the site security service
// (bug 1115712), we can't implement this right now.
- try {
+ promises.push(Task.spawn(function*() {
let sss = Cc["@mozilla.org/ssservice;1"].
getService(Ci.nsISiteSecurityService);
+ let httpsURI = NetUtil.newURI("https://" + aDomain);
sss.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, httpsURI, 0);
sss.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, httpsURI, 0);
- } catch (e) {
- Cu.reportError("Exception thrown while clearing HSTS/HPKP: " +
- e.toString());
- }
+ }).catch(ex => {
+ throw new Error("Exception thrown while clearing HSTS/HPKP: " + ex);
+ }));
- return Promise.all(promises);
- }
-};
+ let ErrorCount = 0;
+ for (let promise of promises) {
+ try {
+ yield promise;
+ } catch (ex) {
+ Cu.reportError(ex);
+ ErrorCount++;
+ }
+ }
+ if (ErrorCount !== 0)
+ throw new Error(`There were a total of ${ErrorCount} errors during removal`);
+ })
+}
diff --git a/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js b/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js
index ec0a70228..ca0d394c3 100644
--- a/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js
+++ b/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js
@@ -37,7 +37,7 @@ function setTestPluginEnabledState(newEnabledState, plugin) {
});
}
-add_task(function* setup() {
+add_task(function* () {
var tags = pluginHost.getPluginTags();
// Find the test plugin
@@ -50,12 +50,9 @@ add_task(function* setup() {
}
if (!pluginTag) {
ok(false, "Test Plug-in not available, can't run test");
- finish();
+ return;
}
setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, pluginTag);
-});
-
-add_task(function* () {
yield BrowserTestUtils.openNewForegroundTab(gBrowser, testURL);
// Set data for the plugin after the page load.
@@ -87,5 +84,3 @@ add_task(function* () {
gBrowser.removeCurrentTab();
});
-
-
diff --git a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
index f6ace1e64..d2db95e6a 100644
--- a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
+++ b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
@@ -257,7 +257,7 @@ function* test_history_cleared_with_direct_match()
do_check_false(yield promiseIsURIVisited(TEST_URI));
yield PlacesTestUtils.addVisits(TEST_URI);
do_check_true(yield promiseIsURIVisited(TEST_URI));
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
do_check_false(yield promiseIsURIVisited(TEST_URI));
}
@@ -267,7 +267,7 @@ function* test_history_cleared_with_subdomain()
do_check_false(yield promiseIsURIVisited(TEST_URI));
yield PlacesTestUtils.addVisits(TEST_URI);
do_check_true(yield promiseIsURIVisited(TEST_URI));
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
do_check_false(yield promiseIsURIVisited(TEST_URI));
}
@@ -277,7 +277,7 @@ function* test_history_not_cleared_with_uri_contains_domain()
do_check_false(yield promiseIsURIVisited(TEST_URI));
yield PlacesTestUtils.addVisits(TEST_URI);
do_check_true(yield promiseIsURIVisited(TEST_URI));
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
do_check_true(yield promiseIsURIVisited(TEST_URI));
// Clear history since we left something there from this test.
@@ -285,52 +285,52 @@ function* test_history_not_cleared_with_uri_contains_domain()
}
// Cookie Service
-function test_cookie_cleared_with_direct_match()
+function* test_cookie_cleared_with_direct_match()
{
const TEST_DOMAIN = "mozilla.org";
add_cookie(TEST_DOMAIN);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_cookie_exists(TEST_DOMAIN, false);
}
-function test_cookie_cleared_with_subdomain()
+function* test_cookie_cleared_with_subdomain()
{
const TEST_DOMAIN = "www.mozilla.org";
add_cookie(TEST_DOMAIN);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_cookie_exists(TEST_DOMAIN, false);
}
-function test_cookie_not_cleared_with_uri_contains_domain()
+function* test_cookie_not_cleared_with_uri_contains_domain()
{
const TEST_DOMAIN = "ilovemozilla.org";
add_cookie(TEST_DOMAIN);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_cookie_exists(TEST_DOMAIN, true);
}
// Login Manager
-function test_login_manager_disabled_hosts_cleared_with_direct_match()
+function* test_login_manager_disabled_hosts_cleared_with_direct_match()
{
const TEST_HOST = "http://mozilla.org";
add_disabled_host(TEST_HOST);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_disabled_host(TEST_HOST, false);
}
-function test_login_manager_disabled_hosts_cleared_with_subdomain()
+function* test_login_manager_disabled_hosts_cleared_with_subdomain()
{
const TEST_HOST = "http://www.mozilla.org";
add_disabled_host(TEST_HOST);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_disabled_host(TEST_HOST, false);
}
-function test_login_manager_disabled_hosts_not_cleared_with_uri_contains_domain()
+function* test_login_manager_disabled_hosts_not_cleared_with_uri_contains_domain()
{
const TEST_HOST = "http://ilovemozilla.org";
add_disabled_host(TEST_HOST);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_disabled_host(TEST_HOST, true);
// Reset state
@@ -340,27 +340,27 @@ function test_login_manager_disabled_hosts_not_cleared_with_uri_contains_domain(
check_disabled_host(TEST_HOST, false);
}
-function test_login_manager_logins_cleared_with_direct_match()
+function* test_login_manager_logins_cleared_with_direct_match()
{
const TEST_HOST = "http://mozilla.org";
add_login(TEST_HOST);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_login_exists(TEST_HOST, false);
}
-function test_login_manager_logins_cleared_with_subdomain()
+function* test_login_manager_logins_cleared_with_subdomain()
{
const TEST_HOST = "http://www.mozilla.org";
add_login(TEST_HOST);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_login_exists(TEST_HOST, false);
}
-function test_login_manager_logins_not_cleared_with_uri_contains_domain()
+function* test_login_manager_logins_not_cleared_with_uri_contains_domain()
{
const TEST_HOST = "http://ilovemozilla.org";
add_login(TEST_HOST);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_login_exists(TEST_HOST, true);
let lm = Cc["@mozilla.org/login-manager;1"].
@@ -370,27 +370,27 @@ function test_login_manager_logins_not_cleared_with_uri_contains_domain()
}
// Permission Manager
-function test_permission_manager_cleared_with_direct_match()
+function* test_permission_manager_cleared_with_direct_match()
{
const TEST_URI = uri("http://mozilla.org");
add_permission(TEST_URI);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_permission_exists(TEST_URI, false);
}
-function test_permission_manager_cleared_with_subdomain()
+function* test_permission_manager_cleared_with_subdomain()
{
const TEST_URI = uri("http://www.mozilla.org");
add_permission(TEST_URI);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_permission_exists(TEST_URI, false);
}
-function test_permission_manager_not_cleared_with_uri_contains_domain()
+function* test_permission_manager_not_cleared_with_uri_contains_domain()
{
const TEST_URI = uri("http://ilovemozilla.org");
add_permission(TEST_URI);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
check_permission_exists(TEST_URI, true);
// Reset state
@@ -427,7 +427,7 @@ function* test_content_preferences_cleared_with_direct_match()
do_check_false(yield preference_exists(TEST_URI));
yield add_preference(TEST_URI);
do_check_true(yield preference_exists(TEST_URI));
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
yield waitForPurgeNotification();
do_check_false(yield preference_exists(TEST_URI));
}
@@ -438,7 +438,7 @@ function* test_content_preferences_cleared_with_subdomain()
do_check_false(yield preference_exists(TEST_URI));
yield add_preference(TEST_URI);
do_check_true(yield preference_exists(TEST_URI));
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
yield waitForPurgeNotification();
do_check_false(yield preference_exists(TEST_URI));
}
@@ -449,12 +449,12 @@ function* test_content_preferences_not_cleared_with_uri_contains_domain()
do_check_false(yield preference_exists(TEST_URI));
yield add_preference(TEST_URI);
do_check_true(yield preference_exists(TEST_URI));
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
yield waitForPurgeNotification();
do_check_true(yield preference_exists(TEST_URI));
// Reset state
- ForgetAboutSite.removeDataFromDomain("ilovemozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("ilovemozilla.org");
yield waitForPurgeNotification();
do_check_false(yield preference_exists(TEST_URI));
}
@@ -535,7 +535,7 @@ function* test_push_cleared()
}
// Cache
-function test_cache_cleared()
+function* test_cache_cleared()
{
// Because this test is asynchronous, it should be the last test
do_check_true(tests[tests.length - 1] == arguments.callee);
@@ -557,7 +557,7 @@ function test_cache_cleared()
}
};
os.addObserver(observer, "cacheservice:empty-cache", false);
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
do_test_pending();
}
@@ -588,7 +588,7 @@ function* test_storage_cleared()
do_check_eq(storage.getItem("test"), "value" + i);
}
- ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
yield waitForPurgeNotification();
do_check_eq(s[0].getItem("test"), null);
diff --git a/toolkit/jetpack/modules/system/Startup.js b/toolkit/jetpack/modules/system/Startup.js
index b9e5d88b3..89eeac3bc 100644
--- a/toolkit/jetpack/modules/system/Startup.js
+++ b/toolkit/jetpack/modules/system/Startup.js
@@ -15,6 +15,7 @@ const appStartupSrv = Cc["@mozilla.org/toolkit/app-startup;1"]
.getService(Ci.nsIAppStartup);
const NAME2TOPIC = {
+ 'Palemoon': 'sessionstore-windows-restored',
'Firefox': 'sessionstore-windows-restored',
'Fennec': 'sessionstore-windows-restored',
'SeaMonkey': 'sessionstore-windows-restored',
diff --git a/toolkit/jetpack/moz.build b/toolkit/jetpack/moz.build
index 2024e6a7c..2e5dbe5e8 100644
--- a/toolkit/jetpack/moz.build
+++ b/toolkit/jetpack/moz.build
@@ -85,10 +85,18 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
'sdk/ui/toolbar.js',
]
+ if CONFIG['MC_PALEMOON']:
+ EXTRA_JS_MODULES.commonjs.sdk.ui += [
+ 'sdk/ui/buttons.js',
+ ]
+
EXTRA_JS_MODULES.commonjs.sdk.ui.button += [
'sdk/ui/button/action.js',
'sdk/ui/button/contract.js',
'sdk/ui/button/toggle.js',
+ ]
+
+ EXTRA_PP_JS_MODULES.commonjs.sdk.ui.button += [
'sdk/ui/button/view.js',
]
@@ -120,9 +128,10 @@ EXTRA_JS_MODULES.commonjs += [
'test.js',
]
-EXTRA_JS_MODULES.commonjs.sdk += [
- 'sdk/webextension.js',
-]
+if CONFIG['MOZ_WEBEXTENSIONS']:
+ EXTRA_JS_MODULES.commonjs.sdk += [
+ 'sdk/webextension.js',
+ ]
EXTRA_JS_MODULES.commonjs.dev += [
'dev/debuggee.js',
@@ -211,13 +220,15 @@ EXTRA_JS_MODULES.commonjs.sdk += [
'sdk/tabs.js',
'sdk/test.js',
'sdk/timers.js',
- 'sdk/ui.js',
'sdk/url.js',
'sdk/windows.js',
]
+EXTRA_PP_JS_MODULES.commonjs.sdk += [
+ 'sdk/ui.js',
+]
+
EXTRA_JS_MODULES.commonjs.sdk.addon += [
- 'sdk/addon/bootstrap.js',
'sdk/addon/events.js',
'sdk/addon/host.js',
'sdk/addon/installer.js',
@@ -226,6 +237,10 @@ EXTRA_JS_MODULES.commonjs.sdk.addon += [
'sdk/addon/window.js',
]
+EXTRA_PP_JS_MODULES.commonjs.sdk.addon += [
+ 'sdk/addon/bootstrap.js',
+]
+
EXTRA_JS_MODULES.commonjs.sdk.browser += [
'sdk/browser/events.js',
]
diff --git a/toolkit/jetpack/sdk/addon/bootstrap.js b/toolkit/jetpack/sdk/addon/bootstrap.js
index 0397d91e5..6c5827f1e 100644
--- a/toolkit/jetpack/sdk/addon/bootstrap.js
+++ b/toolkit/jetpack/sdk/addon/bootstrap.js
@@ -134,8 +134,10 @@ Bootstrap.prototype = {
const main = command === "test" ? "sdk/test/runner" : null;
const prefsURI = `${baseURI}defaults/preferences/prefs.js`;
+#ifdef MOZ_WEBEXTENSIONS
// Init the 'sdk/webextension' module from the bootstrap addon parameter.
require("sdk/webextension").initFromBootstrapAddonParam(addon);
+#endif
const { startup } = require("sdk/addon/runner");
startup(reason, {loader, main, prefsURI});
diff --git a/toolkit/jetpack/sdk/clipboard.js b/toolkit/jetpack/sdk/clipboard.js
index 048d5f2f1..c6b3c46fe 100644
--- a/toolkit/jetpack/sdk/clipboard.js
+++ b/toolkit/jetpack/sdk/clipboard.js
@@ -8,6 +8,7 @@ module.metadata = {
"stability": "stable",
"engines": {
// TODO Fennec Support 789757
+ "Palemoon": "*",
"Firefox": "*",
"SeaMonkey": "*",
"Thunderbird": "*"
diff --git a/toolkit/jetpack/sdk/context-menu.js b/toolkit/jetpack/sdk/context-menu.js
index 004c642d4..e00f41d79 100644
--- a/toolkit/jetpack/sdk/context-menu.js
+++ b/toolkit/jetpack/sdk/context-menu.js
@@ -7,6 +7,7 @@ module.metadata = {
"stability": "stable",
"engines": {
// TODO Fennec support Bug 788334
+ "Palemoon": "*",
"Firefox": "*",
"SeaMonkey": "*"
}
diff --git a/toolkit/jetpack/sdk/panel.js b/toolkit/jetpack/sdk/panel.js
index 4b625799d..34cde2edd 100644
--- a/toolkit/jetpack/sdk/panel.js
+++ b/toolkit/jetpack/sdk/panel.js
@@ -8,6 +8,7 @@
module.metadata = {
"stability": "stable",
"engines": {
+ "Palemoon": "*",
"Firefox": "*",
"SeaMonkey": "*"
}
diff --git a/toolkit/jetpack/sdk/places/bookmarks.js b/toolkit/jetpack/sdk/places/bookmarks.js
index c4f9528f1..e8fdae4f4 100644
--- a/toolkit/jetpack/sdk/places/bookmarks.js
+++ b/toolkit/jetpack/sdk/places/bookmarks.js
@@ -7,6 +7,7 @@
module.metadata = {
"stability": "unstable",
"engines": {
+ "Palemoon": "*",
"Firefox": "*",
"SeaMonkey": "*"
}
diff --git a/toolkit/jetpack/sdk/places/events.js b/toolkit/jetpack/sdk/places/events.js
index a3f95ee03..c5e728039 100644
--- a/toolkit/jetpack/sdk/places/events.js
+++ b/toolkit/jetpack/sdk/places/events.js
@@ -7,6 +7,7 @@
module.metadata = {
'stability': 'experimental',
'engines': {
+ 'Palemoon': '*',
'Firefox': '*',
"SeaMonkey": '*'
}
diff --git a/toolkit/jetpack/sdk/places/favicon.js b/toolkit/jetpack/sdk/places/favicon.js
index 05b057db1..7a74aa517 100644
--- a/toolkit/jetpack/sdk/places/favicon.js
+++ b/toolkit/jetpack/sdk/places/favicon.js
@@ -7,6 +7,7 @@
module.metadata = {
"stability": "unstable",
"engines": {
+ "Palemoon": "*",
"Firefox": "*",
"SeaMonkey": "*"
}
diff --git a/toolkit/jetpack/sdk/places/history.js b/toolkit/jetpack/sdk/places/history.js
index b243b024c..f7fc3ed57 100644
--- a/toolkit/jetpack/sdk/places/history.js
+++ b/toolkit/jetpack/sdk/places/history.js
@@ -7,6 +7,7 @@
module.metadata = {
"stability": "unstable",
"engines": {
+ "Palemoon": "*",
"Firefox": "*",
"SeaMonkey": "*"
}
diff --git a/toolkit/jetpack/sdk/places/host/host-bookmarks.js b/toolkit/jetpack/sdk/places/host/host-bookmarks.js
index 3245c4070..f6dec4069 100644
--- a/toolkit/jetpack/sdk/places/host/host-bookmarks.js
+++ b/toolkit/jetpack/sdk/places/host/host-bookmarks.js
@@ -7,6 +7,7 @@
module.metadata = {
"stability": "experimental",
"engines": {
+ "Palemoon": "*",
"Firefox": "*",
"SeaMonkey": "*"
}
diff --git a/toolkit/jetpack/sdk/places/host/host-query.js b/toolkit/jetpack/sdk/places/host/host-query.js
index f2dbd6550..a2cd4cd35 100644
--- a/toolkit/jetpack/sdk/places/host/host-query.js
+++ b/toolkit/jetpack/sdk/places/host/host-query.js
@@ -6,6 +6,7 @@
module.metadata = {
"stability": "experimental",
"engines": {
+ "Palemoon": "*",
"Firefox": "*",
"SeaMonkey": "*"
}
diff --git a/toolkit/jetpack/sdk/places/host/host-tags.js b/toolkit/jetpack/sdk/places/host/host-tags.js
index 929a5d5af..b94342549 100644
--- a/toolkit/jetpack/sdk/places/host/host-tags.js
+++ b/toolkit/jetpack/sdk/places/host/host-tags.js
@@ -7,6 +7,7 @@
module.metadata = {
"stability": "experimental",
"engines": {
+ "Palemoon": "*",
"Firefox": "*",
"SeaMonkey": "*"
}
diff --git a/toolkit/jetpack/sdk/places/utils.js b/toolkit/jetpack/sdk/places/utils.js
index 44366d2aa..fe928c4ea 100644
--- a/toolkit/jetpack/sdk/places/utils.js
+++ b/toolkit/jetpack/sdk/places/utils.js
@@ -7,6 +7,7 @@
module.metadata = {
"stability": "experimental",
"engines": {
+ "Palemoon": "*",
"Firefox": "*",
"SeaMonkey": "*"
}
diff --git a/toolkit/jetpack/sdk/selection.js b/toolkit/jetpack/sdk/selection.js
index 8682e8c6d..e393aae06 100644
--- a/toolkit/jetpack/sdk/selection.js
+++ b/toolkit/jetpack/sdk/selection.js
@@ -7,6 +7,7 @@
module.metadata = {
"stability": "stable",
"engines": {
+ "Palemoon": "*",
"Firefox": "*",
"SeaMonkey": "*"
}
diff --git a/toolkit/jetpack/sdk/system/xul-app.jsm b/toolkit/jetpack/sdk/system/xul-app.jsm
index 90681bb1b..ed760c3a4 100644
--- a/toolkit/jetpack/sdk/system/xul-app.jsm
+++ b/toolkit/jetpack/sdk/system/xul-app.jsm
@@ -41,6 +41,7 @@ var platformVersion = exports.platformVersion = appInfo.platformVersion;
// GUID.
var ids = exports.ids = {
+ Palemoon: "{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}",
Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
diff --git a/toolkit/jetpack/sdk/ui.js b/toolkit/jetpack/sdk/ui.js
index 7f9110b26..d1ff7ceb8 100644
--- a/toolkit/jetpack/sdk/ui.js
+++ b/toolkit/jetpack/sdk/ui.js
@@ -6,12 +6,15 @@
module.metadata = {
'stability': 'experimental',
'engines': {
+ 'Palemoon': '> 27',
'Firefox': '> 28'
}
};
exports.ActionButton = require('./ui/button/action').ActionButton;
exports.ToggleButton = require('./ui/button/toggle').ToggleButton;
+#ifndef MC_PALEMOON
exports.Sidebar = require('./ui/sidebar').Sidebar;
exports.Frame = require('./ui/frame').Frame;
exports.Toolbar = require('./ui/toolbar').Toolbar;
+#endif
diff --git a/toolkit/jetpack/sdk/ui/button/action.js b/toolkit/jetpack/sdk/ui/button/action.js
index dfb092d0c..5355705be 100644
--- a/toolkit/jetpack/sdk/ui/button/action.js
+++ b/toolkit/jetpack/sdk/ui/button/action.js
@@ -6,6 +6,7 @@
module.metadata = {
'stability': 'experimental',
'engines': {
+ 'Palemoon': '> 27',
'Firefox': '> 28'
}
};
diff --git a/toolkit/jetpack/sdk/ui/button/toggle.js b/toolkit/jetpack/sdk/ui/button/toggle.js
index a226b3212..d8a3d1758 100644
--- a/toolkit/jetpack/sdk/ui/button/toggle.js
+++ b/toolkit/jetpack/sdk/ui/button/toggle.js
@@ -6,6 +6,7 @@
module.metadata = {
'stability': 'experimental',
'engines': {
+ 'Palemoon': '> 27',
'Firefox': '> 28'
}
};
diff --git a/toolkit/jetpack/sdk/ui/button/view.js b/toolkit/jetpack/sdk/ui/button/view.js
index 63b7aea31..dcc3be59d 100644
--- a/toolkit/jetpack/sdk/ui/button/view.js
+++ b/toolkit/jetpack/sdk/ui/button/view.js
@@ -6,6 +6,7 @@
module.metadata = {
'stability': 'experimental',
'engines': {
+ 'Palemoon': '> 27',
'Firefox': '> 28'
}
};
@@ -19,14 +20,19 @@ const { isObject, isNil } = require('../../lang/type');
const { getMostRecentBrowserWindow } = require('../../window/utils');
const { ignoreWindow } = require('../../private-browsing/utils');
+#ifdef MC_PALEMOON
+const { buttons } = require('../buttons');
+#else
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { AREA_PANEL, AREA_NAVBAR } = CustomizableUI;
+#endif
const { events: viewEvents } = require('./view/events');
const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
const views = new Map();
+#ifndef MC_PALEMOON
const customizedWindows = new WeakMap();
const buttonListener = {
@@ -64,19 +70,25 @@ CustomizableUI.addListener(buttonListener);
require('../../system/unload').when( _ =>
CustomizableUI.removeListener(buttonListener)
);
+#endif
function getNode(id, window) {
return !views.has(id) || ignoreWindow(window)
? null
+#ifdef MC_PALEMOON
+ : buttons.getNode(id, window);
+#else
: CustomizableUI.getWidget(id).forWindow(window).node
+#endif
};
+#ifndef MC_PALEMOON
function isInToolbar(id) {
let placement = CustomizableUI.getPlacementOfWidget(id);
return placement && CustomizableUI.getAreaType(placement.area) === 'toolbar';
}
-
+#endif
function getImage(icon, isInToolbar, pixelRatio) {
let targetSize = (isInToolbar ? 18 : 32) * pixelRatio;
@@ -109,7 +121,11 @@ function getImage(icon, isInToolbar, pixelRatio) {
}
function nodeFor(id, window=getMostRecentBrowserWindow()) {
+#ifdef MC_PALEMOON
+ return getNode(id, window);
+#else
return customizedWindows.has(window) ? null : getNode(id, window);
+#endif
};
exports.nodeFor = nodeFor;
@@ -119,14 +135,23 @@ function create(options) {
if (views.has(id))
throw new Error('The ID "' + id + '" seems already used.');
+#ifdef MC_PALEMOON
+ buttons.createButton({
+#else
CustomizableUI.createWidget({
+#endif
id: id,
type: 'custom',
+#ifdef MC_PALEMOON
+
+ onBuild: function(document, _id) {
+#else
removable: true,
defaultArea: AREA_NAVBAR,
allowedAreas: [ AREA_PANEL, AREA_NAVBAR ],
onBuild: function(document) {
+#endif
let window = document.defaultView;
let node = document.createElementNS(XUL_NS, 'toolbarbutton');
@@ -136,16 +161,26 @@ function create(options) {
if (ignoreWindow(window))
node.style.display = 'none';
+#ifdef MC_PALEMOON
+ node.setAttribute('id', _id);
+#else
node.setAttribute('id', this.id);
+#endif
node.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional badged-button');
node.setAttribute('type', type);
node.setAttribute('label', label);
node.setAttribute('tooltiptext', label);
node.setAttribute('image', image);
+#ifdef MC_PALEMOON
+ node.setAttribute('pmkit-button', 'true');
+#else
node.setAttribute('constrain-size', 'true');
+#endif
views.set(id, {
+#ifndef MC_PALEMOON
area: this.currentArea,
+#endif
icon: icon,
label: label
});
@@ -171,7 +206,11 @@ function dispose(id) {
if (!views.has(id)) return;
views.delete(id);
+#ifdef MC_PALEMOON
+ buttons.destroyButton(id);
+#else
CustomizableUI.destroyWidget(id);
+#endif
}
exports.dispose = dispose;
@@ -179,8 +218,12 @@ function setIcon(id, window, icon) {
let node = getNode(id, window);
if (node) {
+#ifdef MC_PALEMOON
+ let image = getImage(icon, true, window.devicePixelRatio);
+#else
icon = customizedWindows.has(window) ? views.get(id).icon : icon;
let image = getImage(icon, isInToolbar(id), window.devicePixelRatio);
+#endif
node.setAttribute('image', image);
}
diff --git a/toolkit/jetpack/sdk/ui/button/view/events.js b/toolkit/jetpack/sdk/ui/button/view/events.js
index 98909656a..34d14be3a 100644
--- a/toolkit/jetpack/sdk/ui/button/view/events.js
+++ b/toolkit/jetpack/sdk/ui/button/view/events.js
@@ -7,6 +7,7 @@
module.metadata = {
'stability': 'experimental',
'engines': {
+ 'Palemoon': '*',
'Firefox': '*',
'SeaMonkey': '*',
'Thunderbird': '*'
diff --git a/toolkit/jetpack/sdk/ui/buttons.js b/toolkit/jetpack/sdk/ui/buttons.js
new file mode 100644
index 000000000..66e0fd742
--- /dev/null
+++ b/toolkit/jetpack/sdk/ui/buttons.js
@@ -0,0 +1,198 @@
+/* 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/.
+ * PMkit shim for 'sdk/ui/button', (c) JustOff, 2017 */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Palemoon": "> 27"
+ }
+};
+
+const { Ci, Cc } = require('chrome');
+const prefs = require('../preferences/service');
+
+const buttonsList = new Map();
+const LOCATION_PREF_ROOT = "extensions.sdk-button-location.";
+
+let gWindowListener;
+
+function getLocation(id) {
+ let toolbarId = "nav-bar", nextItemId = "";
+ let location = prefs.get(LOCATION_PREF_ROOT + id);
+ if (location && location.indexOf(",") !== -1) {
+ [toolbarId, nextItemId] = location.split(",");
+ }
+ return [toolbarId, nextItemId];
+}
+
+function saveLocation(id, toolbarId, nextItemId) {
+ let _toolbarId = toolbarId || "";
+ let _nextItemId = nextItemId || "";
+ prefs.set(LOCATION_PREF_ROOT + id, [_toolbarId, _nextItemId].join(","));
+}
+
+// Insert button into window
+function insertButton(aWindow, id, onBuild) {
+ // Build button and save reference to it
+ let doc = aWindow.document;
+ let b = onBuild(doc, id);
+ aWindow[id] = b;
+
+ // Add to the customization palette
+ let toolbox = doc.getElementById("navigator-toolbox");
+ toolbox.palette.appendChild(b);
+
+ // Retrieve button location from preferences
+ let [toolbarId, nextItemId] = getLocation(id);
+ let toolbar = toolbarId != "" && doc.getElementById(toolbarId);
+
+ if (toolbar) {
+ let nextItem = doc.getElementById(nextItemId);
+ // If nextItem not in toolbar then retrieve it by reading currentset attribute
+ if (!(nextItem && nextItem.parentNode && nextItem.parentNode.id == toolbarId)) {
+ nextItem = null;
+ let currentSet = toolbar.getAttribute("currentset");
+ let ids = (currentSet == "__empty") ? [] : currentSet.split(",");
+ let idx = ids.indexOf(id);
+ if (idx != -1) {
+ for (let i = idx; i < ids.length; i++) {
+ nextItem = doc.getElementById(ids[i]);
+ if (nextItem)
+ break;
+ }
+ }
+ }
+ // Finally insert button in the right toolbar and in the right position
+ toolbar.insertItem(id, nextItem, null, false);
+ }
+}
+
+// Remove button from window
+function removeButton(aWindow, id) {
+ let b = aWindow[id];
+ b.parentNode.removeChild(b);
+ delete aWindow[id];
+}
+
+// Save locations of buttons after customization
+function afterCustomize(e) {
+ for (let [id] of buttonsList) {
+ let toolbox = e.target;
+ let b = toolbox.parentNode.querySelector("#" + id);
+ let toolbarId = null, nextItemId = null;
+ if (b) {
+ let parent = b.parentNode;
+ let nextItem = b.nextSibling;
+ if (parent && parent.localName == "toolbar") {
+ toolbarId = parent.id;
+ nextItemId = nextItem && nextItem.id;
+ }
+ }
+ saveLocation(id, toolbarId, nextItemId);
+ }
+}
+
+// Global window observer
+function browserWindowObserver(handlers) {
+ this.handlers = handlers;
+}
+
+browserWindowObserver.prototype = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ aSubject.QueryInterface(Ci.nsIDOMWindow).addEventListener("load", this, false);
+ } else if (aTopic == "domwindowclosed") {
+ if (aSubject.document.documentElement.getAttribute("windowtype") == "navigator:browser") {
+ this.handlers.onShutdown(aSubject);
+ }
+ }
+ },
+
+ handleEvent: function(aEvent) {
+ let aWindow = aEvent.currentTarget;
+ aWindow.removeEventListener(aEvent.type, this, false);
+
+ if (aWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser") {
+ this.handlers.onStartup(aWindow);
+ }
+ }
+};
+
+// Run on every window startup
+function browserWindowStartup(aWindow) {
+ for (let [id, onBuild] of buttonsList) {
+ insertButton(aWindow, id, onBuild);
+ }
+ aWindow.addEventListener("aftercustomization", afterCustomize, false);
+};
+
+// Run on every window shutdown
+function browserWindowShutdown(aWindow) {
+ for (let [id, onBuild] of buttonsList) {
+ removeButton(aWindow, id);
+ }
+ aWindow.removeEventListener("aftercustomization", afterCustomize, false);
+}
+
+// Main object
+const buttons = {
+ createButton: function(aProperties) {
+ // If no buttons were inserted yet, setup global window observer
+ if (buttonsList.size == 0) {
+ let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
+ gWindowListener = new browserWindowObserver({
+ onStartup: browserWindowStartup,
+ onShutdown: browserWindowShutdown
+ });
+ ww.registerNotification(gWindowListener);
+ }
+
+ // Add button to list
+ buttonsList.set(aProperties.id, aProperties.onBuild);
+
+ // Inster button to all open windows
+ let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+ let winenu = wm.getEnumerator("navigator:browser");
+ while (winenu.hasMoreElements()) {
+ let win = winenu.getNext();
+ insertButton(win, aProperties.id, aProperties.onBuild);
+ // When first button inserted, add afterCustomize listener
+ if (buttonsList.size == 1) {
+ win.addEventListener("aftercustomization", afterCustomize, false);
+ }
+ }
+ },
+
+ destroyButton: function(id) {
+ // Remove button from list
+ buttonsList.delete(id);
+
+ // If no more buttons exist, remove global window observer
+ if (buttonsList.size == 0) {
+ let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
+ ww.unregisterNotification(gWindowListener);
+ gWindowListener = null;
+ }
+
+ // Remove button from all open windows
+ let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+ let winenu = wm.getEnumerator("navigator:browser");
+ while (winenu.hasMoreElements()) {
+ let win = winenu.getNext();
+ removeButton(win, id);
+ // If no more buttons exist, remove afterCustomize listener
+ if (buttonsList.size == 0) {
+ win.removeEventListener("aftercustomization", afterCustomize, false);
+ }
+ }
+ },
+
+ getNode: function(id, window) {
+ return window[id];
+ }
+};
+
+exports.buttons = buttons;
diff --git a/toolkit/jetpack/sdk/ui/state.js b/toolkit/jetpack/sdk/ui/state.js
index 152ce696d..c90d4283d 100644
--- a/toolkit/jetpack/sdk/ui/state.js
+++ b/toolkit/jetpack/sdk/ui/state.js
@@ -8,6 +8,7 @@
module.metadata = {
'stability': 'experimental',
'engines': {
+ 'Palemoon': '*',
'Firefox': '*',
'SeaMonkey': '*',
'Thunderbird': '*'
diff --git a/toolkit/jetpack/sdk/ui/state/events.js b/toolkit/jetpack/sdk/ui/state/events.js
index 98909656a..34d14be3a 100644
--- a/toolkit/jetpack/sdk/ui/state/events.js
+++ b/toolkit/jetpack/sdk/ui/state/events.js
@@ -7,6 +7,7 @@
module.metadata = {
'stability': 'experimental',
'engines': {
+ 'Palemoon': '*',
'Firefox': '*',
'SeaMonkey': '*',
'Thunderbird': '*'
diff --git a/toolkit/locales/en-US/chrome/global/aboutRights.dtd b/toolkit/locales/en-US/chrome/global/aboutRights.dtd
index 319984d41..28b24e1d8 100644
--- a/toolkit/locales/en-US/chrome/global/aboutRights.dtd
+++ b/toolkit/locales/en-US/chrome/global/aboutRights.dtd
@@ -19,12 +19,12 @@
<!ENTITY rights.intro-point1b "Mozilla Public License">
<!ENTITY rights.intro-point1c ". This means you may use, copy and distribute &brandShortName; to others. You are also welcome to modify the source code of &brandShortName; as you want to meet your needs. The Mozilla Public License also gives you the right to distribute your modified versions.">
-<!ENTITY rights.intro-point2-a "You are not granted any trademark rights or licenses to the trademarks of the Mozilla Foundation or any party, including without limitation the Firefox name or logo. Additional information on trademarks may be found ">
+<!ENTITY rights.intro-point2-a "You are not granted any trademark rights or licenses to the trademarks of Moonchild Productions or any other party, including without limitation the Basilisk and Pale Moon names or logos. Additional information on trademarks may be found ">
<!ENTITY rights.intro-point2-b "here">
<!ENTITY rights.intro-point2-c ".">
<!-- point 2.5 text for official branded builds -->
-<!ENTITY rights.intro-point2.5 "Some features in &brandShortName;, such as the Crash Reporter, give you the option to provide feedback to &vendorShortName;. By choosing to submit feedback, you give &vendorShortName; permission to use the feedback to improve its products, to publish the feedback on its websites, and to distribute the feedback.">
+<!ENTITY rights.intro-point2.5 "Some features in &brandShortName; give you the option to provide feedback to the publishers of this software. By choosing to submit feedback, you give the publishers in question permission to use the feedback to improve their products, to publish the feedback on their websites, and to distribute the feedback.">
<!-- point 3 text for official branded builds -->
<!ENTITY rights2.intro-point3a "How we use your personal information and feedback submitted to &vendorShortName; through &brandShortName; is described in the ">
@@ -47,7 +47,7 @@
<!ENTITY rights2.webservices-header "&brandFullName; Web-Based Information Services">
<!-- point 5 -->
-<!ENTITY rights.intro-point5 "In order to play back certain types of video content, &brandShortName; downloads certain content decryption modules from third parties.">
+<!ENTITY rights.intro-point5 "In order to play back certain types of video content, &brandShortName; may download certain content decryption modules from third parties, if playback of DRM content is enabled or available.">
<!-- Note that this paragraph references a couple of entities from
preferences/security.dtd, so that we can refer to text the user sees in
@@ -59,6 +59,8 @@
<!ENTITY rights3.webservices-c ". Other features and Services can be disabled in the application preferences.">
<!-- safe browsing points for branded builds -->
+<!-- Google SafeBrowsing requires an API key; this should always remain disabled unless
+ an API key is obtained. Official builds do not use SafeBrowsing. -->
<!ENTITY rights.safebrowsing-a "SafeBrowsing: ">
<!ENTITY rights.safebrowsing-b "Disabling the Safe Browsing feature is not recommended as it may result in you going to unsafe sites. If you wish to disable the feature completely, follow these steps:">
<!ENTITY rights.safebrowsing-term1 "Open the application preferences">
@@ -67,6 +69,7 @@
<!ENTITY rights.safebrowsing-term4 "Safe Browsing is now disabled">
<!-- location aware browsing points for branded builds -->
+<!-- Official builds use IP-API.com -->
<!ENTITY rights.locationawarebrowsing-a "Location Aware Browsing: ">
<!ENTITY rights.locationawarebrowsing-b "is always opt-in. No location information is ever sent without your permission. If you wish to disable the feature completely, follow these steps:">
<!ENTITY rights.locationawarebrowsing-term1a "In the URL bar, type ">
@@ -82,10 +85,10 @@
<!ENTITY rights.webservices-term1-unbranded "Any applicable service terms for this product should be listed here.">
<!-- points 1-7 text for branded builds -->
-<!ENTITY rights2.webservices-term1 "&vendorShortName; and its contributors, licensors and partners work to provide the most accurate and up-to-date Services. However, we cannot guarantee that this information is comprehensive and error-free. For example, the Safe Browsing Service may not identify some risky sites and may identify some safe sites in error and the Location Aware Service all locations returned by our service providers are estimates only and neither we nor our service providers guarantee the accuracy of the locations provided.">
+<!ENTITY rights2.webservices-term1 "&vendorShortName; and its contributors, licensors and partners work to provide the most accurate and up-to-date Services. However, we cannot guarantee that this information is comprehensive and error-free. For example, for the Location Aware Service all locations returned by our service provider are estimates only and neither we nor our service provider guarantee the accuracy of the locations provided.">
<!ENTITY rights.webservices-term2 "&vendorShortName; may discontinue or change the Services at its discretion.">
-<!ENTITY rights2.webservices-term3 "You are welcome to use these Services with the accompanying version of &brandShortName;, and &vendorShortName; grants you its rights to do so. &vendorShortName; and its licensors reserve all other rights in the Services. These terms are not intended to limit any rights granted under open source licenses applicable to &brandShortName; and to corresponding source code versions of &brandShortName;.">
-<!ENTITY rights.webservices-term4 "The Services are provided &quot;as-is.&quot; &vendorShortName;, its contributors, licensors, and distributors, disclaim all warranties, whether express or implied, including without limitation, warranties that the Services are merchantable and fit for your particular purposes. You bear the entire risk as to selecting the Services for your purposes and as to the quality and performance of the Services. Some jurisdictions do not allow the exclusion or limitation of implied warranties, so this disclaimer may not apply to you.">
-<!ENTITY rights.webservices-term5 "Except as required by law, &vendorShortName;, its contributors, licensors, and distributors will not be liable for any indirect, special, incidental, consequential, punitive, or exemplary damages arising out of or in any way relating to the use of &brandShortName; and the Services. The collective liability under these terms will not exceed $500 (five hundred dollars). Some jurisdictions do not allow the exclusion or limitation of certain damages, so this exclusion and limitation may not apply to you.">
+<!ENTITY rights2.webservices-term3 "You are welcome to use these Services with the accompanying version of &brandShortName;, and &vendorShortName; grants you its rights to do so. &vendorShortName; and its licensors reserve all other rights in the Services. These terms are not intended to limit any rights granted under open source licenses applicable to &brandShortName; and to corresponding source code versions of &brandShortName;, however these (optional) services are provided asa convenience to you, and in no way extend your software rights to the Services.">
+<!ENTITY rights.webservices-term4 "The Services are provided &quot;as-is&quot; and &quot;as-available&quot;. &vendorShortName;, its contributors, licensors and distributors disclaim all warranties, whether express or implied, including without limitation warranties that the Services are merchantable and fit for your particular purposes. You bear the entire risk as to selecting the Services for your purposes and as to the quality and performance of the Services. If your jurisdiction does not allow disclaiming of warranties, then you should not use &brandShortName; or Services.">
+<!ENTITY rights.webservices-term5 "Except as required by law, &vendorShortName;, its contributors, licensors, and distributors will not be liable for any indirect, special, incidental, consequential, punitive, or exemplary damages arising out of or in any way relating to the use of &brandShortName; and the Services. The collective liability under these terms will not exceed $500 (five hundred dollars). If your jurisdiction does not allow the exclusion or limitation of damages, then you should not use &brandShortName; or Services.">
<!ENTITY rights.webservices-term6 "&vendorShortName; may update these terms as necessary from time to time. These terms may not be modified or canceled without &vendorShortName;'s written agreement.">
-<!ENTITY rights.webservices-term7 "These terms are governed by the laws of the state of California, U.S.A., excluding its conflict of law provisions. If any portion of these terms is held to be invalid or unenforceable, the remaining portions will remain in full force and effect. In the event of a conflict between a translated version of these terms and the English language version, the English language version shall control.">
+<!ENTITY rights.webservices-term7 "These terms are governed by the laws of Sweden, excluding any conflict of law provisions. If any portion of these terms is held to be invalid or unenforceable, the remaining portions will remain in full force and effect. In the event of a conflict between a translated version of these terms and the English language version, the English language version shall take precedence.">
diff --git a/toolkit/locales/en-US/chrome/global/autocomplete.properties b/toolkit/locales/en-US/chrome/global/autocomplete.properties
index 5c7c84b96..44da643ac 100644
--- a/toolkit/locales/en-US/chrome/global/autocomplete.properties
+++ b/toolkit/locales/en-US/chrome/global/autocomplete.properties
@@ -1,6 +1,11 @@
# 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/.
+switchToTab = Switch to tab
+
+# LOCALIZATION NOTE (visitURL):
+# %S is the URL to visit.
+visitURL = Visit %S
# LOCALIZATION NOTE (searchWithEngine): %S will be replaced with
# the search engine provider's name. This format was chosen because
diff --git a/toolkit/locales/en-US/chrome/mozapps/preferences/changemp.dtd b/toolkit/locales/en-US/chrome/mozapps/preferences/changemp.dtd
index 1cc7b9621..1b1d5ac55 100644
--- a/toolkit/locales/en-US/chrome/mozapps/preferences/changemp.dtd
+++ b/toolkit/locales/en-US/chrome/mozapps/preferences/changemp.dtd
@@ -9,5 +9,5 @@
<!ENTITY setPassword.reenterPassword.label "Re-enter password:">
<!ENTITY setPassword.meter.label "Password quality meter">
<!ENTITY setPassword.meter.loading "Loading">
-<!ENTITY masterPasswordDescription.label "A Master Password is used to protect sensitive information like site passwords. If you create a Master Password you will be asked to enter it once per session when &brandShortName; retrieves saved information protected by the password.">
+<!ENTITY masterPasswordDescription.label "A Master Password is used to protect sensitive information like site passwords. If you create a Master Password you will be asked to enter it once per session when &brandShortName; retrieves saved information protected by the password. A master password must be 8 characters or longer; longer is better.">
<!ENTITY masterPasswordWarning.label "Please make sure you remember the Master Password you have set. If you forget your Master Password, you will be unable to access any of the information protected by it.">
diff --git a/toolkit/modules/ResetProfile.jsm b/toolkit/modules/ResetProfile.jsm
index fe0d1cfe8..c1839af4f 100644
--- a/toolkit/modules/ResetProfile.jsm
+++ b/toolkit/modules/ResetProfile.jsm
@@ -11,7 +11,11 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
-const MOZ_APP_NAME = AppConstants.MOZ_APP_NAME;
+// For Basilisk and Pale Moon
+// Hard-code MOZ_APP_NAME to firefox because of hard-coded type in migrator.
+const MOZ_APP_NAME = (((AppConstants.MOZ_APP_NAME == "basilisk")
+ || (AppConstants.MOZ_APP_NAME == "palemoon"))
+ ? "firefox" : AppConstants.MOZ_APP_NAME);
const MOZ_BUILD_APP = AppConstants.MOZ_BUILD_APP;
this.ResetProfile = {
diff --git a/toolkit/modules/addons/WebRequestContent.js b/toolkit/modules/addons/WebRequestContent.js
index 219675e5b..f044a1cd4 100644
--- a/toolkit/modules/addons/WebRequestContent.js
+++ b/toolkit/modules/addons/WebRequestContent.js
@@ -80,6 +80,16 @@ var ContentPolicy = {
shouldLoad(policyType, contentLocation, requestOrigin,
node, mimeTypeGuess, extra, requestPrincipal) {
+
+ // Loads of TYPE_DOCUMENT and TYPE_SUBDOCUMENT perform a ConPol check
+ // within docshell as well as within the ContentSecurityManager. To avoid
+ // duplicate evaluations we ignore ConPol checks performed within docShell.
+ if (extra instanceof Ci.nsISupportsString) {
+ if (extra.data === "conPolCheckFromDocShell") {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+ }
+
if (requestPrincipal &&
Services.scriptSecurityManager.isSystemPrincipal(requestPrincipal)) {
return Ci.nsIContentPolicy.ACCEPT;
diff --git a/toolkit/modules/sessionstore/FormData.jsm b/toolkit/modules/sessionstore/FormData.jsm
index f90ba5825..d4fb08d93 100644
--- a/toolkit/modules/sessionstore/FormData.jsm
+++ b/toolkit/modules/sessionstore/FormData.jsm
@@ -216,7 +216,7 @@ var FormDataInternal = {
// We want to avoid saving data for about:sessionrestore as a string.
// Since it's stored in the form as stringified JSON, stringifying further
// causes an explosion of escape characters. cf. bug 467409
- if (isRestorationPage(ret.url)) {
+ if (isRestorationPage(ret.url) && ret.id && ret.id.sessionData) {
ret.id.sessionData = JSON.parse(ret.id.sessionData);
}
diff --git a/toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm b/toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm
index 11944ddf5..66147b9aa 100644
--- a/toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm
@@ -60,7 +60,11 @@ this.AddonRepository_SQLiteMigrator = {
this._retrieveStoredData((results) => {
this._closeConnection();
- let resultArray = [addon for ([,addon] of Iterator(results))];
+ // Tycho: let resultArray = [addon for ([,addon] of Iterator(results))];
+ let resultArray = [];
+ for (let [,addon] of Iterator(results)) {
+ resultArray.push(addon);
+ }
logger.debug(resultArray.length + " addons imported.")
aCallback(resultArray);
});
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
index 2c5e3dfa7..c43811ba8 100644
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1931,12 +1931,10 @@ this.XPIProvider = {
let chan;
try {
- chan = Services.io.newChannelFromURI2(aURI,
- null, // aLoadingNode
- Services.scriptSecurityManager.getSystemPrincipal(),
- null, // aTriggeringPrincipal
- Ci.nsILoadInfo.SEC_NORMAL,
- Ci.nsIContentPolicy.TYPE_OTHER);
+ chan = NetUtil.newChannel({
+ uri: aURI,
+ loadUsingSystemPrincipal: true
+ });
}
catch (ex) {
return null;
@@ -4438,7 +4436,18 @@ this.XPIProvider = {
if (aAddon.type == "locale")
return;
- if (!(aMethod in this.bootstrapScopes[aAddon.id])) {
+ let method = undefined;
+ try {
+ method = Components.utils.evalInSandbox(`${aMethod};`,
+ this.bootstrapScopes[aAddon.id],
+ "ECMAv5");
+ }
+ catch (e) {
+ // An exception will be caught if the expected method is not defined.
+ // That will be logged below.
+ }
+
+ if (!method) {
logger.warn("Add-on " + aAddon.id + " is missing bootstrap method " + aMethod);
return;
}
@@ -4457,9 +4466,9 @@ this.XPIProvider = {
}
logger.debug("Calling bootstrap method " + aMethod + " on " + aAddon.id + " version " +
- aAddon.version);
+ aAddon.version);
try {
- this.bootstrapScopes[aAddon.id][aMethod](params, aReason);
+ method(params, aReason);
}
catch (e) {
logger.warn("Exception running bootstrap method " + aMethod + " on " + aAddon.id, e);
@@ -5456,21 +5465,17 @@ AddonInstall.prototype = {
let requireBuiltIn = Preferences.get(PREF_INSTALL_REQUIREBUILTINCERTS, true);
this.badCertHandler = new BadCertHandler(!requireBuiltIn);
- this.channel = NetUtil.newChannel2(this.sourceURI,
- null,
- null,
- null, // aLoadingNode
- Services.scriptSecurityManager.getSystemPrincipal(),
- null, // aTriggeringPrincipal
- Ci.nsILoadInfo.SEC_NORMAL,
- Ci.nsIContentPolicy.TYPE_OTHER);
+ this.channel = NetUtil.newChannel({
+ uri: this.sourceURI,
+ loadUsingSystemPrincipal: true
+ });
this.channel.notificationCallbacks = this;
if (this.channel instanceof Ci.nsIHttpChannel) {
this.channel.setRequestHeader("Moz-XPI-Update", "1", true);
if (this.channel instanceof Ci.nsIHttpChannelInternal)
this.channel.forceAllowThirdPartyCookie = true;
}
- this.channel.asyncOpen(listener, null);
+ this.channel.asyncOpen2(listener);
Services.obs.addObserver(this, "network:offline-about-to-go-offline", false);
}
diff --git a/toolkit/mozapps/extensions/test/addons/test_bootstrap_const/bootstrap.js b/toolkit/mozapps/extensions/test/addons/test_bootstrap_const/bootstrap.js
new file mode 100644
index 000000000..498b76526
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_bootstrap_const/bootstrap.js
@@ -0,0 +1,5 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const install = function() {
+ Services.obs.notifyObservers(null, "addon-install", "");
+} \ No newline at end of file
diff --git a/toolkit/mozapps/extensions/test/addons/test_bootstrap_const/install.rdf b/toolkit/mozapps/extensions/test/addons/test_bootstrap_const/install.rdf
new file mode 100644
index 000000000..af3a749ce
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/test_bootstrap_const/install.rdf
@@ -0,0 +1,24 @@
+<?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>bootstrap@tests.mozilla.org</em:id>
+ <em:version>1.0</em:version>
+ <em:bootstrap>true</em:bootstrap>
+
+ <!-- Front End MetaData -->
+ <em:name>Test Bootstrap</em:name>
+ <em:description>Test Description</em:description>
+
+ <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> \ No newline at end of file
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_const.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_const.js
new file mode 100644
index 000000000..fb02b59be
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_const.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+startupManager();
+
+add_task(function*() {
+ let sawInstall = false;
+ Services.obs.addObserver(function() {
+ sawInstall = true;
+ }, "addon-install", false);
+
+ yield promiseInstallAllFiles([do_get_addon("test_bootstrap_const")]);
+
+ ok(sawInstall);
+}); \ No newline at end of file
diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
index bab072e83..2a12f147a 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -29,6 +29,7 @@ skip-if = os == "android"
[test_bootstrap.js]
# Bug 676992: test consistently hangs on Android
skip-if = os == "android"
+[test_bootstrap_const.js]
[test_bootstrap_resource.js]
[test_bug299716.js]
# Bug 676992: test consistently hangs on Android
diff --git a/toolkit/mozapps/preferences/changemp.js b/toolkit/mozapps/preferences/changemp.js
index 82dd20128..71664b3e1 100644
--- a/toolkit/mozapps/preferences/changemp.js
+++ b/toolkit/mozapps/preferences/changemp.js
@@ -167,8 +167,8 @@ function setPasswordStrength()
// length of the password
var pwlength=(pw.length);
- if (pwlength>5)
- pwlength=5;
+ if (pwlength>10)
+ pwlength=10;
// use of numbers in the password
@@ -190,7 +190,7 @@ function setPasswordStrength()
upper=3;
- var pwstrength=((pwlength*10)-20) + (numeric*10) + (numsymbols*15) + (upper*10);
+ var pwstrength=((pwlength*5)-20) + (numeric*10) + (numsymbols*15) + (upper*10);
// make sure we're give a value between 0 and 100
if ( pwstrength < 0 ) {
@@ -227,6 +227,12 @@ function checkPasswords()
}
}
+ // Never accept short passwords < 8 chars
+ if (pw1.length < 8) {
+ ok.setAttribute("disabled", "true");
+ return;
+ }
+
if (pw1 == pw2) {
ok.setAttribute("disabled", "false");
} else
diff --git a/toolkit/mozapps/preferences/changemp.xul b/toolkit/mozapps/preferences/changemp.xul
index 14d02295e..b316fa42b 100644
--- a/toolkit/mozapps/preferences/changemp.xul
+++ b/toolkit/mozapps/preferences/changemp.xul
@@ -34,7 +34,7 @@
<rows>
<row>
<label control="oldpw">&setPassword.oldPassword.label;</label>
- <textbox id="oldpw" type="password"/>
+ <textbox id="oldpw" type="password" size="18"/>
<!-- This textbox is inserted as a workaround to the fact that making the 'type'
& 'disabled' property of the 'oldpw' textbox toggle between ['password' &
'false'] and ['text' & 'true'] - as would be necessary if the menu has more
@@ -46,12 +46,13 @@
</row>
<row>
<label control="pw1">&setPassword.newPassword.label;</label>
- <textbox id="pw1" type="password"
+ <textbox id="pw1" type="password" size="18"
oninput="setPasswordStrength(); checkPasswords();"/>
</row>
<row>
<label control="pw2">&setPassword.reenterPassword.label;</label>
- <textbox id="pw2" type="password" oninput="checkPasswords();"/>
+ <textbox id="pw2" type="password" size="18"
+ oninput="checkPasswords();"/>
</row>
</rows>
</grid>
diff --git a/toolkit/pluginproblem/content/pluginProblemBinding.css b/toolkit/pluginproblem/content/pluginProblemBinding.css
index 48506de34..a545e3eba 100644
--- a/toolkit/pluginproblem/content/pluginProblemBinding.css
+++ b/toolkit/pluginproblem/content/pluginProblemBinding.css
@@ -19,6 +19,11 @@ object:-moz-handler-crashed,
object:-moz-handler-clicktoplay,
object:-moz-handler-vulnerable-updatable,
object:-moz-handler-vulnerable-no-update {
+%ifdef MC_PALEMOON
+ /* Initialize the overlay with visibility:hidden to prevent flickering if
+ * the plugin is too small to show the overlay */
+ visibility: hidden;
+%endif
display: inline-block;
overflow: hidden;
opacity: 1 !important;
diff --git a/toolkit/pluginproblem/content/pluginProblemContent.css b/toolkit/pluginproblem/content/pluginProblemContent.css
index 43a9f52dc..cf8755635 100644
--- a/toolkit/pluginproblem/content/pluginProblemContent.css
+++ b/toolkit/pluginproblem/content/pluginProblemContent.css
@@ -51,6 +51,7 @@ a .mainBox:focus,
line-height: initial;
}
+%ifndef MC_PALEMOON
/* Initialize the overlay with visibility:hidden to prevent flickering if
* the plugin is too small to show the overlay */
.mainBox > .hoverBox,
@@ -62,6 +63,7 @@ a .mainBox:focus,
.visible > .closeIcon {
visibility: visible;
}
+%endif
.mainBox[chromedir="rtl"] {
direction: rtl;
@@ -97,6 +99,10 @@ a .msgTapToPlay,
:-moz-handler-blocked .msgBlocked,
:-moz-handler-crashed .msgCrashed {
display: block;
+ position: relative;
+ left: 0;
+ top: 0;
+ z-index: 9999;
}
.submitStatus[status] {
diff --git a/toolkit/pluginproblem/jar.mn b/toolkit/pluginproblem/jar.mn
index d0af1c82f..c027793de 100644
--- a/toolkit/pluginproblem/jar.mn
+++ b/toolkit/pluginproblem/jar.mn
@@ -5,6 +5,6 @@
toolkit.jar:
% content pluginproblem %pluginproblem/ contentaccessible=yes
pluginproblem/pluginProblem.xml (content/pluginProblem.xml)
- pluginproblem/pluginProblemContent.css (content/pluginProblemContent.css)
- pluginproblem/pluginProblemBinding.css (content/pluginProblemBinding.css)
+* pluginproblem/pluginProblemContent.css (content/pluginProblemContent.css)
+* pluginproblem/pluginProblemBinding.css (content/pluginProblemBinding.css)
pluginproblem/pluginReplaceBinding.css (content/pluginReplaceBinding.css)
diff --git a/toolkit/xre/MozMeegoAppService.h b/toolkit/xre/MozMeegoAppService.h
deleted file mode 100644
index 063d03e02..000000000
--- a/toolkit/xre/MozMeegoAppService.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef MOZMEEGOAPPSERVICE_H
-#define MOZMEEGOAPPSERVICE_H
-
-#include <MApplicationService>
-
-/**
- * App service class, which prevents registration to d-bus
- * and allows multiple instances of application. This is
- * required for Mozillas remote service to work, because
- * it is initialized after MApplication.
- */
-class MozMeegoAppService: public MApplicationService
-{
- Q_OBJECT
-public:
- MozMeegoAppService(): MApplicationService(QString()) {}
-public Q_SLOTS:
- virtual QString registeredName() { return QString(); }
- virtual bool isRegistered() { return false; }
- virtual bool registerService() { return true; }
-};
-#endif // MOZMEEGOAPPSERVICE_H
diff --git a/toolkit/xre/ProfileReset.cpp b/toolkit/xre/ProfileReset.cpp
index aef2d7746..c9e2ef8d4 100644
--- a/toolkit/xre/ProfileReset.cpp
+++ b/toolkit/xre/ProfileReset.cpp
@@ -31,13 +31,19 @@ static const char kProfileProperties[] =
* Creates a new profile with a timestamp in the name to use for profile reset.
*/
nsresult
-CreateResetProfile(nsIToolkitProfileService* aProfileSvc, nsIToolkitProfile* *aNewProfile)
+CreateResetProfile(nsIToolkitProfileService* aProfileSvc, const nsACString& aOldProfileName, nsIToolkitProfile* *aNewProfile)
{
MOZ_ASSERT(aProfileSvc, "NULL profile service");
nsCOMPtr<nsIToolkitProfile> newProfile;
- // Make the new profile "default-" + the time in seconds since epoch for uniqueness.
- nsAutoCString newProfileName("default-");
+ // Make the new profile the old profile (or "default-") + the time in seconds since epoch for uniqueness.
+ nsAutoCString newProfileName;
+ if (!aOldProfileName.IsEmpty()) {
+ newProfileName.Assign(aOldProfileName);
+ newProfileName.Append("-");
+ } else {
+ newProfileName.Assign("default-");
+ }
newProfileName.Append(nsPrintfCString("%lld", PR_Now() / 1000));
nsresult rv = aProfileSvc->CreateProfile(nullptr, // choose a default dir for us
newProfileName,
diff --git a/toolkit/xre/ProfileReset.h b/toolkit/xre/ProfileReset.h
index 7b5efbc4e..a164fe075 100644
--- a/toolkit/xre/ProfileReset.h
+++ b/toolkit/xre/ProfileReset.h
@@ -11,6 +11,7 @@ static bool gProfileResetCleanupCompleted = false;
static const char kResetProgressURL[] = "chrome://global/content/resetProfileProgress.xul";
nsresult CreateResetProfile(nsIToolkitProfileService* aProfileSvc,
+ const nsACString& aOldProfileName,
nsIToolkitProfile* *aNewProfile);
nsresult ProfileResetCleanup(nsIToolkitProfile* aOldProfile);
diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp
index 530e4a353..3493cd837 100644
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -1357,7 +1357,9 @@ DumpHelp()
" -v or --version Print %s version.\n"
" -P <profile> Start with <profile>.\n"
" --profile <path> Start with profile at <path>.\n"
+#ifdef MC_BASILISK
" --migration Start with migration wizard.\n"
+#endif
" --ProfileManager Start with ProfileManager.\n"
" --no-remote Do not accept or send remote commands;\n"
" implies --new-instance.\n"
@@ -1886,17 +1888,20 @@ ShowProfileManager(nsIToolkitProfileService* aProfileSvc,
}
/**
- * Set the currently running profile as the default/selected one.
+ * Get the currently running profile using its root directory.
*
+ * @param aProfileSvc The profile service
* @param aCurrentProfileRoot The root directory of the current profile.
- * @return an error if aCurrentProfileRoot is not found or the profile could not
- * be set as the default.
+ * @param aProfile Out-param that returns the profile object.
+ * @return an error if aCurrentProfileRoot is not found
*/
static nsresult
-SetCurrentProfileAsDefault(nsIToolkitProfileService* aProfileSvc,
- nsIFile* aCurrentProfileRoot)
+GetCurrentProfile(nsIToolkitProfileService* aProfileSvc,
+ nsIFile* aCurrentProfileRoot,
+ nsIToolkitProfile** aProfile)
{
NS_ENSURE_ARG_POINTER(aProfileSvc);
+ NS_ENSURE_ARG_POINTER(aProfile);
nsCOMPtr<nsISimpleEnumerator> profiles;
nsresult rv = aProfileSvc->GetProfiles(getter_AddRefs(profiles));
@@ -1912,7 +1917,8 @@ SetCurrentProfileAsDefault(nsIToolkitProfileService* aProfileSvc,
profile->GetRootDir(getter_AddRefs(profileRoot));
profileRoot->Equals(aCurrentProfileRoot, &foundMatchingProfile);
if (foundMatchingProfile) {
- return aProfileSvc->SetSelectedProfile(profile);
+ profile.forget(aProfile);
+ return NS_OK;
}
rv = profiles->GetNext(getter_AddRefs(supports));
}
@@ -2000,7 +2006,7 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n
if (gDoProfileReset) {
// If we're resetting a profile, create a new one and use it to startup.
nsCOMPtr<nsIToolkitProfile> newProfile;
- rv = CreateResetProfile(aProfileSvc, getter_AddRefs(newProfile));
+ rv = CreateResetProfile(aProfileSvc, gResetOldProfileName, getter_AddRefs(newProfile));
if (NS_SUCCEEDED(rv)) {
rv = newProfile->GetRootDir(getter_AddRefs(lf));
NS_ENSURE_SUCCESS(rv, rv);
@@ -2146,20 +2152,20 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n
return ProfileLockedDialog(profile, unlocker, aNative, &tempProfileLock);
}
- nsCOMPtr<nsIToolkitProfile> newProfile;
- rv = CreateResetProfile(aProfileSvc, getter_AddRefs(newProfile));
- if (NS_FAILED(rv)) {
- NS_WARNING("Failed to create a profile to reset to.");
- gDoProfileReset = false;
- } else {
- nsresult gotName = profile->GetName(gResetOldProfileName);
- if (NS_SUCCEEDED(gotName)) {
- profile = newProfile;
- } else {
- NS_WARNING("Failed to get the name of the profile we're resetting, so aborting reset.");
- gResetOldProfileName.Truncate(0);
+ nsresult gotName = profile->GetName(gResetOldProfileName);
+ if (NS_SUCCEEDED(gotName)) {
+ nsCOMPtr<nsIToolkitProfile> newProfile;
+ rv = CreateResetProfile(aProfileSvc, gResetOldProfileName, getter_AddRefs(newProfile));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create a profile to reset to.");
gDoProfileReset = false;
+ } else {
+ profile = newProfile;
}
+ } else {
+ NS_WARNING("Failed to get the name of the profile we're resetting, so aborting reset.");
+ gResetOldProfileName.Truncate(0);
+ gDoProfileReset = false;
}
}
@@ -2254,20 +2260,22 @@ SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, n
return ProfileLockedDialog(profile, unlocker, aNative, &tempProfileLock);
}
- nsCOMPtr<nsIToolkitProfile> newProfile;
- rv = CreateResetProfile(aProfileSvc, getter_AddRefs(newProfile));
- if (NS_FAILED(rv)) {
- NS_WARNING("Failed to create a profile to reset to.");
- gDoProfileReset = false;
- } else {
- nsresult gotName = profile->GetName(gResetOldProfileName);
- if (NS_SUCCEEDED(gotName)) {
- profile = newProfile;
- } else {
- NS_WARNING("Failed to get the name of the profile we're resetting, so aborting reset.");
- gResetOldProfileName.Truncate(0);
+ nsresult gotName = profile->GetName(gResetOldProfileName);
+ if (NS_SUCCEEDED(gotName)) {
+ nsCOMPtr<nsIToolkitProfile> newProfile;
+ rv = CreateResetProfile(aProfileSvc, gResetOldProfileName, getter_AddRefs(newProfile));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create a profile to reset to.");
gDoProfileReset = false;
}
+ else {
+ profile = newProfile;
+ }
+ }
+ else {
+ NS_WARNING("Failed to get the name of the profile we're resetting, so aborting reset.");
+ gResetOldProfileName.Truncate(0);
+ gDoProfileReset = false;
}
}
@@ -3739,7 +3747,12 @@ XREMain::XRE_mainRun()
if (gDoProfileReset) {
// Automatically migrate from the current application if we just
// reset the profile.
- aKey = MOZ_APP_NAME;
+ // For Basilisk and Pale Moon:
+ // Hard-code MOZ_APP_NAME to firefox because of hard-coded type in migrator.
+ aKey = (((MOZ_APP_NAME == "basilisk")
+ || (MOZ_APP_NAME == "palemoon"))
+ ? "firefox" : MOZ_APP_NAME);
+
}
pm->Migrate(&mDirProvider, aKey, gResetOldProfileName);
}
@@ -3749,15 +3762,23 @@ XREMain::XRE_mainRun()
nsresult backupCreated = ProfileResetCleanup(profileBeingReset);
if (NS_FAILED(backupCreated)) NS_WARNING("Could not cleanup the profile that was reset");
- // Set the new profile as the default after we're done cleaning up the old profile,
- // iff that profile was already the default
- if (profileWasSelected) {
- // this is actually "broken" - see bug 1122124
- rv = SetCurrentProfileAsDefault(mProfileSvc, mProfD);
- if (NS_FAILED(rv)) NS_WARNING("Could not set current profile as the default");
+ nsCOMPtr<nsIToolkitProfile> newProfile;
+ rv = GetCurrentProfile(mProfileSvc, mProfD, getter_AddRefs(newProfile));
+ if (NS_SUCCEEDED(rv)) {
+ newProfile->SetName(gResetOldProfileName);
+ mProfileName.Assign(gResetOldProfileName);
+ // Set the new profile as the default after we're done cleaning up the old profile,
+ // iff that profile was already the default
+ if (profileWasSelected) {
+ rv = mProfileSvc->SetDefaultProfile(newProfile);
+ if (NS_FAILED(rv)) NS_WARNING("Could not set current profile as the default");
+ }
+ } else {
+ NS_WARNING("Could not find current profile to set as default / change name.");
}
- // Need to write out the fact that the profile has been removed and potentially
- // that the selected/default profile changed.
+
+ // Need to write out the fact that the profile has been removed, the new profile
+ // renamed, and potentially that the selected/default profile changed.
mProfileSvc->Flush();
}
}
@@ -4285,7 +4306,6 @@ MultiprocessBlockPolicy() {
if (addonsCanDisable && disabledByAddons) {
gMultiprocessBlockPolicy = kE10sDisabledForAddons;
- return gMultiprocessBlockPolicy;
}
#if defined(XP_WIN)
@@ -4319,16 +4339,13 @@ MultiprocessBlockPolicy() {
if (disabledForA11y) {
gMultiprocessBlockPolicy = kE10sDisabledForAccessibility;
- return gMultiprocessBlockPolicy;
}
#endif
+
+ // We do not support E10S, block by policy.
+ gMultiprocessBlockPolicy = kE10sForceDisabled;
- /*
- * None of the blocking policies matched, so e10s is allowed to run.
- * Cache the information and return 0, indicating success.
- */
- gMultiprocessBlockPolicy = 0;
- return 0;
+ return gMultiprocessBlockPolicy;
}
bool