merge fx-team to mozilla-central a=merge
@ -404,8 +404,13 @@ pref("browser.search.order.1", "chrome://browser-region/locale/re
|
||||
pref("browser.search.order.2", "chrome://browser-region/locale/region.properties");
|
||||
pref("browser.search.order.3", "chrome://browser-region/locale/region.properties");
|
||||
|
||||
// Market-specific search defaults (US market only)
|
||||
pref("browser.search.geoSpecificDefaults", true);
|
||||
// Market-specific search defaults
|
||||
// This is disabled globally, and then enabled for individual locales
|
||||
// in firefox-l10n.js (eg. it's enabled for en-US).
|
||||
pref("browser.search.geoSpecificDefaults", false);
|
||||
pref("browser.search.geoSpecificDefaults.url", "");
|
||||
|
||||
// US specific default (used as a fallback if the geoSpecificDefaults request fails).
|
||||
pref("browser.search.defaultenginename.US", "data:text/plain,browser.search.defaultenginename.US=Yahoo");
|
||||
pref("browser.search.order.US.1", "data:text/plain,browser.search.order.US.1=Yahoo");
|
||||
pref("browser.search.order.US.2", "data:text/plain,browser.search.order.US.2=Google");
|
||||
@ -1359,7 +1364,6 @@ pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
|
||||
// Developer edition preferences
|
||||
#ifdef MOZ_DEV_EDITION
|
||||
sticky_pref("lightweightThemes.selectedThemeID", "firefox-devedition@mozilla.org");
|
||||
sticky_pref("browser.devedition.theme.enabled", true);
|
||||
#else
|
||||
sticky_pref("lightweightThemes.selectedThemeID", "");
|
||||
#endif
|
||||
|
@ -53,8 +53,10 @@ function test() {
|
||||
ok(data.days.hasDay(now), "Have data for today.");
|
||||
let day = data.days.getDay(now);
|
||||
|
||||
// Will need to be changed if Yahoo isn't the default search engine.
|
||||
let defaultProviderID = "yahoo";
|
||||
// Will need to be changed if Google isn't the default search engine.
|
||||
// Note: geoSpecificDefaults are disabled for mochitests, so this is the
|
||||
// non-US en-US default.
|
||||
let defaultProviderID = "google";
|
||||
let field = defaultProviderID + ".contextmenu";
|
||||
ok(day.has(field), "Have search recorded for context menu.");
|
||||
|
||||
@ -66,4 +68,3 @@ function test() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
/*
|
||||
* Testing changes for Developer Edition theme.
|
||||
* A special stylesheet should be added to the browser.xul document
|
||||
* when browser.devedition.theme.enabled is set to true and no themes
|
||||
* are applied.
|
||||
* when the firefox-devedition@mozilla.org lightweight theme
|
||||
* is applied.
|
||||
*/
|
||||
|
||||
const PREF_LWTHEME_USED_THEMES = "lightweightThemes.usedThemes";
|
||||
|
@ -9,6 +9,7 @@
|
||||
// * A page with tracking elements is loaded and they are not blocked.
|
||||
// See also Bugs 1175327, 1043801, 1178985
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
const PREF = "privacy.trackingprotection.enabled";
|
||||
const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
|
||||
const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
|
||||
@ -16,8 +17,11 @@ const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/
|
||||
let TrackingProtection = null;
|
||||
let browser = null;
|
||||
|
||||
let {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
TrackingProtection = browser = null;
|
||||
UrlClassifierTestUtils.cleanupTestTrackers();
|
||||
Services.prefs.clearUserPref(PREF);
|
||||
Services.prefs.clearUserPref(PB_PREF);
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
@ -108,7 +112,7 @@ function* testTrackingProtectionForTab(tab) {
|
||||
}
|
||||
|
||||
add_task(function* testNormalBrowsing() {
|
||||
yield updateTrackingProtectionDatabase();
|
||||
yield UrlClassifierTestUtils.addTestTrackers();
|
||||
|
||||
browser = gBrowser;
|
||||
let tab = browser.selectedTab = browser.addTab();
|
||||
|
@ -6,6 +6,7 @@
|
||||
// Control Center when the feature is off.
|
||||
// See also Bugs 1175327, 1043801, 1178985.
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
const PREF = "privacy.trackingprotection.enabled";
|
||||
const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
|
||||
const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
|
||||
@ -13,8 +14,11 @@ const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/
|
||||
let TrackingProtection = null;
|
||||
let browser = null;
|
||||
|
||||
let {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
TrackingProtection = browser = null;
|
||||
UrlClassifierTestUtils.cleanupTestTrackers();
|
||||
Services.prefs.clearUserPref(PREF);
|
||||
Services.prefs.clearUserPref(PB_PREF);
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
@ -23,7 +27,7 @@ registerCleanupFunction(function() {
|
||||
});
|
||||
|
||||
add_task(function* testNormalBrowsing() {
|
||||
yield updateTrackingProtectionDatabase();
|
||||
yield UrlClassifierTestUtils.addTestTrackers();
|
||||
|
||||
browser = gBrowser;
|
||||
let tab = browser.selectedTab = browser.addTab();
|
||||
|
@ -27,8 +27,10 @@ add_task(function* test_healthreport_search_recording() {
|
||||
let now = new Date();
|
||||
let oldCount = 0;
|
||||
|
||||
// This will to be need changed if default search engine is not Yahoo.
|
||||
let defaultEngineID = "yahoo";
|
||||
// This will to be need changed if default search engine is not Google.
|
||||
// Note: geoSpecificDefaults are disabled for mochitests, so this is the
|
||||
// non-US en-US default.
|
||||
let defaultEngineID = "google";
|
||||
|
||||
let field = defaultEngineID + ".urlbar";
|
||||
|
||||
|
@ -660,55 +660,6 @@ function promiseIndicatorWindow() {
|
||||
return promiseWindow("chrome://browser/content/webrtcIndicator.xul");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some entries to a test tracking protection database, and reset
|
||||
* back to the default database after the test ends.
|
||||
*/
|
||||
function updateTrackingProtectionDatabase() {
|
||||
let TABLE = "urlclassifier.trackingTable";
|
||||
Services.prefs.setCharPref(TABLE, "test-track-simple");
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref(TABLE);
|
||||
});
|
||||
|
||||
// Add some URLs to the tracking database (to be blocked)
|
||||
let testData = "tracking.example.com/";
|
||||
let testUpdate =
|
||||
"n:1000\ni:test-track-simple\nad:1\n" +
|
||||
"a:524:32:" + testData.length + "\n" +
|
||||
testData;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
|
||||
.getService(Ci.nsIUrlClassifierDBService);
|
||||
let listener = {
|
||||
QueryInterface: iid => {
|
||||
if (iid.equals(Ci.nsISupports) ||
|
||||
iid.equals(Ci.nsIUrlClassifierUpdateObserver))
|
||||
return listener;
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
updateUrlRequested: url => { },
|
||||
streamFinished: status => { },
|
||||
updateError: errorCode => {
|
||||
ok(false, "Couldn't update classifier.");
|
||||
resolve();
|
||||
},
|
||||
updateSuccess: requestedTimeout => {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
dbService.beginUpdate(listener, "test-track-simple", "");
|
||||
dbService.beginStream("", "");
|
||||
dbService.updateStream(testUpdate);
|
||||
dbService.finishStream();
|
||||
dbService.finishUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
function assertWebRTCIndicatorStatus(expected) {
|
||||
let ui = Cu.import("resource:///modules/webrtcUI.jsm", {}).webrtcUI;
|
||||
let expectedState = expected ? "visible" : "hidden";
|
||||
|
@ -216,17 +216,16 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
if (!this._formattingEnabled || !this.editor)
|
||||
return;
|
||||
|
||||
// Always clear the strike-out selection first.
|
||||
let controller = this.editor.selectionController;
|
||||
let strikeOut = controller.getSelection(controller.SELECTION_URLSTRIKEOUT);
|
||||
strikeOut.removeAllRanges();
|
||||
|
||||
if (this.focused)
|
||||
return;
|
||||
|
||||
let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
|
||||
selection.removeAllRanges();
|
||||
|
||||
if (this.focused)
|
||||
return;
|
||||
|
||||
let textNode = this.editor.rootElement.firstChild;
|
||||
let value = textNode.textContent;
|
||||
|
||||
@ -286,17 +285,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_clearFormatting">
|
||||
<body><![CDATA[
|
||||
if (!this._formattingEnabled)
|
||||
return;
|
||||
|
||||
let controller = this.editor.selectionController;
|
||||
let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
|
||||
selection.removeAllRanges();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="handleRevert">
|
||||
<body><![CDATA[
|
||||
var isScrolling = this.popupOpen;
|
||||
@ -953,6 +941,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
<handler event="focus"><![CDATA[
|
||||
if (event.originalTarget == this.inputField) {
|
||||
this._hideURLTooltip();
|
||||
this.formatValue();
|
||||
}
|
||||
]]></handler>
|
||||
@ -991,11 +980,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
event.stopPropagation();
|
||||
]]></handler>
|
||||
|
||||
<handler event="focus" phase="capturing"><![CDATA[
|
||||
this._hideURLTooltip();
|
||||
this._clearFormatting();
|
||||
]]></handler>
|
||||
|
||||
<handler event="dragover" phase="capturing" action="this.onDragOver(event, this);"/>
|
||||
<handler event="drop" phase="capturing" action="this.onDrop(event, this);"/>
|
||||
<handler event="select"><![CDATA[
|
||||
|
@ -19,7 +19,7 @@ Our views use [React](http://facebook.github.io/react/) written in JSX files
|
||||
and transpiled to JS before we commit. You need to install the JSX compiler
|
||||
using npm in order to compile the .jsx files into regular .js ones:
|
||||
|
||||
npm install -g react-tools
|
||||
npm install -g react-tools@0.12.2
|
||||
|
||||
Once installed, run build-jsx with the --watch option from
|
||||
browser/components/loop, eg.:
|
||||
|
@ -7,7 +7,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
Components.utils.import("resource://testing-common/TelemetryArchiveTesting.jsm", this);
|
||||
|
||||
function test() {
|
||||
|
@ -7,8 +7,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
@ -7,9 +7,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
function test() {
|
||||
|
@ -13,8 +13,6 @@ let gContentWindow;
|
||||
let highlight = document.getElementById("UITourHighlightContainer");
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
let hasWebIDE = Services.prefs.getBoolPref("devtools.webide.widget.enabled");
|
||||
|
||||
let hasPocket = false;
|
||||
|
@ -12,8 +12,6 @@ let gContentAPI;
|
||||
let gContentWindow;
|
||||
let gContentDoc;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
registerCleanupFunction(function() {
|
||||
gContentDoc = null;
|
||||
|
@ -4,8 +4,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ let gContentAPI;
|
||||
let gContentWindow;
|
||||
let notificationBox = document.getElementById("high-priority-global-notificationbox");
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ let gContentWindow;
|
||||
let loopButton;
|
||||
let loopPanel = document.getElementById("loop-notification-panel");
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
const { LoopRooms } = Components.utils.import("resource:///modules/loop/LoopRooms.jsm", {});
|
||||
const { MozLoopServiceInternal } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
|
||||
|
||||
|
@ -74,8 +74,6 @@ function getDialogDoc() {
|
||||
return null;
|
||||
}
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
requestLongerTimeout(2);
|
||||
UITourTest();
|
||||
|
@ -13,8 +13,6 @@ let gContentWindow;
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
registerCleanupFunction(() => {
|
||||
// Close the find bar in case it's open in the remaining tab
|
||||
|
@ -8,8 +8,6 @@ let gContentAPI;
|
||||
let gContentWindow;
|
||||
let button;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
Components.utils.import("resource://gre/modules/UITelemetry.jsm");
|
||||
Components.utils.import("resource:///modules/BrowserUITelemetry.jsm");
|
||||
|
||||
|
@ -7,8 +7,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("services.sync.username");
|
||||
|
@ -4,8 +4,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
requestLongerTimeout(2);
|
||||
UITourTest();
|
||||
|
@ -9,8 +9,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Cu.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
@ -7,9 +7,6 @@ let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
@ -204,8 +204,6 @@ function UITourTest() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.UITourMetricsProvider;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
|
@ -1886,6 +1886,12 @@ EventListeners.prototype = {
|
||||
definitionSite = fetchedDefinitions.get(listener.function.actor);
|
||||
} else if (listener.function.class == "Function") {
|
||||
definitionSite = yield this._getDefinitionSite(listener.function);
|
||||
if (!definitionSite) {
|
||||
// We don't know where this listener comes from so don't show it in
|
||||
// the UI as breaking on it doesn't work (bug 942899).
|
||||
continue;
|
||||
}
|
||||
|
||||
fetchedDefinitions.set(listener.function.actor, definitionSite);
|
||||
}
|
||||
listener.function.url = definitionSite;
|
||||
@ -1926,8 +1932,10 @@ EventListeners.prototype = {
|
||||
// Don't make this error fatal, because it would break the entire events pane.
|
||||
const msg = "Error getting function definition site: " + aResponse.message;
|
||||
DevToolsUtils.reportException("_getDefinitionSite", msg);
|
||||
deferred.resolve(null);
|
||||
} else {
|
||||
deferred.resolve(aResponse.source.url);
|
||||
}
|
||||
deferred.resolve(aResponse.source.url);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
|
@ -235,6 +235,8 @@ skip-if = e10s && debug
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_event-listeners-03.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_event-listeners-04.js]
|
||||
skip-if = debug || e10s # debug bug 1142597, e10s bug 1146603.
|
||||
[browser_dbg_file-reload.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_function-display-name.js]
|
||||
@ -351,7 +353,7 @@ skip-if = e10s && debug
|
||||
[browser_dbg_promises-allocation-stack.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_promises-chrome-allocation-stack.js]
|
||||
skip-if = e10s && debug
|
||||
skip-if = (e10s || os == "linux") && debug
|
||||
[browser_dbg_reload-preferred-script-01.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_reload-preferred-script-02.js]
|
||||
|
@ -0,0 +1,43 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that event listeners are properly fetched even if one of the listeners
|
||||
* don't have a Debugger.Source object (bug 942899).
|
||||
*
|
||||
* This test is skipped on debug and e10s builds for following reasons:
|
||||
* - debug: requiring sdk/tabs causes memory leaks when new windows are opened
|
||||
* in tests executed after this one. Bug 1142597.
|
||||
* - e10s: tab.attach is not e10s safe and only works when add-on compatibility
|
||||
* shims are in place. Bug 1146603.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html";
|
||||
|
||||
add_task(function* () {
|
||||
let tab = yield addTab(TAB_URL);
|
||||
|
||||
// Create a sandboxed content script the Add-on SDK way. Inspired by bug
|
||||
// 1145996.
|
||||
let tabs = require('sdk/tabs');
|
||||
let sdkTab = [...tabs].find(tab => tab.url === TAB_URL);
|
||||
ok(sdkTab, "Add-on SDK found the loaded tab.");
|
||||
|
||||
info("Attaching an event handler via add-on sdk content scripts.");
|
||||
let worker = sdkTab.attach({
|
||||
contentScript: "document.body.addEventListener('click', e => alert(e))",
|
||||
onError: ok.bind(this, false)
|
||||
});
|
||||
|
||||
let [,, panel, win] = yield initDebugger(tab);
|
||||
let gDebugger = panel.panelWin;
|
||||
let fetched = waitForDebuggerEvents(panel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
|
||||
|
||||
info("Scheduling event listener fetch.");
|
||||
gDebugger.DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch();
|
||||
|
||||
info("Waiting for updated event listeners to arrive.");
|
||||
yield fetched;
|
||||
|
||||
ok(true, "The listener update did not hang.");
|
||||
});
|
@ -34,6 +34,9 @@ support-files =
|
||||
[browser_filter-editor-08.js]
|
||||
[browser_filter-editor-09.js]
|
||||
[browser_filter-editor-10.js]
|
||||
[browser_filter-presets-01.js]
|
||||
[browser_filter-presets-02.js]
|
||||
[browser_filter-presets-03.js]
|
||||
[browser_flame-graph-01.js]
|
||||
[browser_flame-graph-02.js]
|
||||
[browser_flame-graph-03a.js]
|
||||
|
@ -68,7 +68,7 @@ add_task(function*() {
|
||||
widget.setCssValue(cssValue);
|
||||
|
||||
if (cssValue === "none") {
|
||||
const text = container.querySelector(".filters").textContent;
|
||||
const text = container.querySelector("#filters").textContent;
|
||||
ok(text.indexOf(L10N.getStr("emptyFilterList")) > -1,
|
||||
"Contains |emptyFilterList| string when given value 'none'");
|
||||
ok(text.indexOf(L10N.getStr("addUsingList")) > -1,
|
||||
|
@ -17,7 +17,7 @@ add_task(function*() {
|
||||
const initialValue = "blur(2px) contrast(200%) brightness(200%)";
|
||||
let widget = new CSSFilterEditorWidget(container, initialValue);
|
||||
|
||||
const filters = widget.el.querySelector(".filters");
|
||||
const filters = widget.el.querySelector("#filters");
|
||||
function first() {
|
||||
return filters.children[0];
|
||||
}
|
||||
@ -28,6 +28,7 @@ add_task(function*() {
|
||||
return filters.children[2];
|
||||
}
|
||||
|
||||
|
||||
info("Test re-ordering neighbour filters");
|
||||
widget._mouseDown({
|
||||
target: first().querySelector("i"),
|
||||
@ -67,7 +68,7 @@ add_task(function*() {
|
||||
pageY: 0
|
||||
});
|
||||
widget._mouseMove({ pageY: -LIST_ITEM_HEIGHT * 5 });
|
||||
ok(first().offsetTop >= boundaries.top,
|
||||
ok(first().getBoundingClientRect().top >= boundaries.top,
|
||||
"First filter should not move outside filter list");
|
||||
|
||||
widget._mouseUp();
|
||||
@ -78,7 +79,7 @@ add_task(function*() {
|
||||
pageY: 0
|
||||
});
|
||||
widget._mouseMove({ pageY: -LIST_ITEM_HEIGHT * 5 });
|
||||
ok(last().offsetTop <= boundaries.bottom,
|
||||
ok(last().getBoundingClientRect().bottom <= boundaries.bottom,
|
||||
"Last filter should not move outside filter list");
|
||||
|
||||
widget._mouseUp();
|
||||
|
@ -22,7 +22,7 @@ add_task(function*() {
|
||||
const container = doc.querySelector("#container");
|
||||
let widget = new CSSFilterEditorWidget(container, "grayscale(0%) url(test.svg)");
|
||||
|
||||
const filters = widget.el.querySelector(".filters");
|
||||
const filters = widget.el.querySelector("#filters");
|
||||
const grayscale = filters.children[0],
|
||||
url = filters.children[1];
|
||||
|
||||
|
@ -22,7 +22,7 @@ add_task(function*() {
|
||||
let widget = new CSSFilterEditorWidget(container, "none");
|
||||
|
||||
const select = widget.el.querySelector("select"),
|
||||
add = widget.el.querySelector("button");
|
||||
add = widget.el.querySelector("#add-filter");
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
@ -62,7 +62,8 @@ add_task(function*() {
|
||||
is(widget.getValueAt(index), `0${filter.unit}`,
|
||||
`Should add ${filter.unit} to ${filter.type} filters`);
|
||||
} else if (filter.placeholder) {
|
||||
const input = widget.el.querySelector(`.filter:nth-child(${index + 1}) input`);
|
||||
let i = index + 1;
|
||||
const input = widget.el.querySelector(`.filter:nth-child(${i}) input`);
|
||||
is(input.placeholder, filter.placeholder,
|
||||
"Should set the appropriate placeholder for string-type filters");
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ add_task(function*() {
|
||||
// Triggers the specified keyCode and modifier key on
|
||||
// first filter's input
|
||||
function triggerKey(key, modifier) {
|
||||
const filter = this.el.querySelector(".filters").children[0];
|
||||
const filter = this.el.querySelector("#filters").children[0];
|
||||
const input = filter.querySelector("input");
|
||||
|
||||
this._keyDown({
|
||||
|
@ -20,7 +20,7 @@ add_task(function*() {
|
||||
const container = doc.querySelector("#container");
|
||||
const initialValue = "drop-shadow(rgb(0, 0, 0) 1px 1px 0px)";
|
||||
let widget = new CSSFilterEditorWidget(container, initialValue);
|
||||
widget.el.querySelector("input").setSelectionRange(13, 13);
|
||||
widget.el.querySelector("#filters input").setSelectionRange(13, 13);
|
||||
|
||||
let value = 1;
|
||||
|
||||
@ -102,7 +102,7 @@ add_task(function*() {
|
||||
// Triggers the specified keyCode and modifier key on
|
||||
// first filter's input
|
||||
function triggerKey(key, modifier) {
|
||||
const filter = this.el.querySelector(".filters").children[0];
|
||||
const filter = this.el.querySelector("#filters").children[0];
|
||||
const input = filter.querySelector("input");
|
||||
|
||||
this._keyDown({
|
||||
|
@ -20,7 +20,7 @@ add_task(function*() {
|
||||
const container = doc.querySelector("#container");
|
||||
const initialValue = "drop-shadow(rgb(0, 0, 0) 10px 1px 0px)";
|
||||
let widget = new CSSFilterEditorWidget(container, initialValue);
|
||||
const input = widget.el.querySelector("input");
|
||||
const input = widget.el.querySelector("#filters input");
|
||||
|
||||
let value = 10;
|
||||
|
||||
@ -66,7 +66,7 @@ add_task(function*() {
|
||||
// Triggers the specified keyCode and modifier key on
|
||||
// first filter's input
|
||||
function triggerKey(key, modifier) {
|
||||
const filter = this.el.querySelector(".filters").children[0];
|
||||
const filter = this.el.querySelector("#filters").children[0];
|
||||
const input = filter.querySelector("input");
|
||||
|
||||
this._keyDown({
|
||||
|
98
browser/devtools/shared/test/browser_filter-presets-01.js
Normal file
@ -0,0 +1,98 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests saving presets
|
||||
|
||||
const TEST_URI = "chrome://browser/content/devtools/filter-frame.xhtml";
|
||||
const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget");
|
||||
|
||||
add_task(function* () {
|
||||
yield promiseTab("about:blank");
|
||||
|
||||
let [host, win, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
const container = doc.querySelector("#container");
|
||||
let widget = new CSSFilterEditorWidget(container, "none");
|
||||
// First render
|
||||
yield widget.once("render");
|
||||
|
||||
const VALUE = "blur(2px) contrast(150%)";
|
||||
const NAME = "Test";
|
||||
|
||||
yield showFilterPopupPresetsAndCreatePreset(widget, NAME, VALUE);
|
||||
|
||||
let preset = widget.el.querySelector(".preset");
|
||||
is(preset.querySelector("label").textContent, NAME,
|
||||
"Should show preset name correctly");
|
||||
is(preset.querySelector("span").textContent, VALUE,
|
||||
"Should show preset value preview correctly");
|
||||
|
||||
let list = yield widget.getPresets();
|
||||
let input = widget.el.querySelector(".presets-list .footer input");
|
||||
let data = list[0];
|
||||
|
||||
is(data.name, NAME,
|
||||
"Should add the preset to asyncStorage - name property");
|
||||
is(data.value, VALUE,
|
||||
"Should add the preset to asyncStorage - name property");
|
||||
|
||||
info("Test overriding preset by using the same name");
|
||||
|
||||
const VALUE_2 = "saturate(50%) brightness(10%)";
|
||||
|
||||
widget.setCssValue(VALUE_2);
|
||||
|
||||
yield savePreset(widget);
|
||||
|
||||
is(widget.el.querySelectorAll(".preset").length, 1,
|
||||
"Should override the preset with the same name - render");
|
||||
|
||||
list = yield widget.getPresets();
|
||||
data = list[0];
|
||||
|
||||
is(list.length, 1,
|
||||
"Should override the preset with the same name - asyncStorage");
|
||||
|
||||
is(data.name, NAME,
|
||||
"Should override the preset with the same name - prop name");
|
||||
is(data.value, VALUE_2,
|
||||
"Should override the preset with the same name - prop value");
|
||||
|
||||
yield widget.setPresets([]);
|
||||
|
||||
info("Test saving a preset without name");
|
||||
input.value = "";
|
||||
|
||||
yield savePreset(widget, "preset-save-error");
|
||||
|
||||
list = yield widget.getPresets();
|
||||
is(list.length, 0,
|
||||
"Should not add a preset without name");
|
||||
|
||||
info("Test saving a preset without filters");
|
||||
|
||||
input.value = NAME;
|
||||
widget.setCssValue("none");
|
||||
|
||||
yield savePreset(widget, "preset-save-error");
|
||||
|
||||
list = yield widget.getPresets();
|
||||
is(list.length, 0,
|
||||
"Should not add a preset without filters (value: none)");
|
||||
});
|
||||
|
||||
/**
|
||||
* Call savePreset on widget and wait for the specified event to emit
|
||||
* @param {CSSFilterWidget} widget
|
||||
* @param {string} expectEvent="render" The event to listen on
|
||||
* @return {Promise}
|
||||
*/
|
||||
function savePreset(widget, expectEvent = "render") {
|
||||
let onEvent = widget.once(expectEvent);
|
||||
widget._savePreset({
|
||||
preventDefault: () => {},
|
||||
});
|
||||
return onEvent;
|
||||
}
|
44
browser/devtools/shared/test/browser_filter-presets-02.js
Normal file
@ -0,0 +1,44 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests loading presets
|
||||
|
||||
const TEST_URI = "chrome://browser/content/devtools/filter-frame.xhtml";
|
||||
const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget");
|
||||
|
||||
add_task(function* () {
|
||||
yield promiseTab("about:blank");
|
||||
|
||||
let [host, win, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
const container = doc.querySelector("#container");
|
||||
let widget = new CSSFilterEditorWidget(container, "none");
|
||||
// First render
|
||||
yield widget.once("render");
|
||||
|
||||
const VALUE = "blur(2px) contrast(150%)";
|
||||
const NAME = "Test";
|
||||
|
||||
yield showFilterPopupPresetsAndCreatePreset(widget, NAME, VALUE);
|
||||
|
||||
let onRender = widget.once("render");
|
||||
// reset value
|
||||
widget.setCssValue("saturate(100%) brightness(150%)");
|
||||
yield onRender;
|
||||
|
||||
let preset = widget.el.querySelector(".preset");
|
||||
|
||||
onRender = widget.once("render");
|
||||
widget._presetClick({
|
||||
target: preset
|
||||
});
|
||||
|
||||
yield onRender;
|
||||
|
||||
is(widget.getCssValue(), VALUE,
|
||||
"Should set widget's value correctly");
|
||||
is(widget.el.querySelector(".presets-list .footer input").value, NAME,
|
||||
"Should set input's value to name");
|
||||
});
|
39
browser/devtools/shared/test/browser_filter-presets-03.js
Normal file
@ -0,0 +1,39 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests deleting presets
|
||||
|
||||
const TEST_URI = "chrome://browser/content/devtools/filter-frame.xhtml";
|
||||
const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget");
|
||||
|
||||
add_task(function* () {
|
||||
yield promiseTab("about:blank");
|
||||
|
||||
let [host, win, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
const container = doc.querySelector("#container");
|
||||
let widget = new CSSFilterEditorWidget(container, "none");
|
||||
// First render
|
||||
yield widget.once("render");
|
||||
|
||||
const NAME = "Test";
|
||||
const VALUE = "blur(2px) contrast(150%)";
|
||||
|
||||
yield showFilterPopupPresetsAndCreatePreset(widget, NAME, VALUE);
|
||||
|
||||
let removeButton = widget.el.querySelector(".preset .remove-button");
|
||||
let onRender = widget.once("render");
|
||||
widget._presetClick({
|
||||
target: removeButton
|
||||
});
|
||||
|
||||
yield onRender;
|
||||
is(widget.el.querySelector(".preset"), null,
|
||||
"Should re-render after removing preset");
|
||||
|
||||
let list = yield widget.getPresets();
|
||||
is(list.length, 0,
|
||||
"Should remove presets from asyncStorage");
|
||||
});
|
@ -283,3 +283,39 @@ function waitUntil(predicate, interval = 10) {
|
||||
}, interval);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the presets list sidebar in the cssfilter widget popup
|
||||
* @param {CSSFilterWidget} widget
|
||||
* @return {Promise}
|
||||
*/
|
||||
function showFilterPopupPresets(widget) {
|
||||
let onRender = widget.once("render");
|
||||
widget._togglePresets();
|
||||
return onRender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show presets list and create a sample preset with the name and value provided
|
||||
* @param {CSSFilterWidget} widget
|
||||
* @param {string} name
|
||||
* @param {string} value
|
||||
* @return {Promise}
|
||||
*/
|
||||
let showFilterPopupPresetsAndCreatePreset = Task.async(function*(widget, name, value) {
|
||||
yield showFilterPopupPresets(widget);
|
||||
|
||||
let onRender = widget.once("render");
|
||||
widget.setCssValue(value);
|
||||
yield onRender;
|
||||
|
||||
let footer = widget.el.querySelector(".presets-list .footer");
|
||||
footer.querySelector("input").value = name;
|
||||
|
||||
onRender = widget.once("render");
|
||||
widget._savePreset({
|
||||
preventDefault: () => {}
|
||||
});
|
||||
|
||||
yield onRender;
|
||||
});
|
||||
|
@ -16,6 +16,9 @@ const STRINGS_URI = "chrome://browser/locale/devtools/filterwidget.properties";
|
||||
const L10N = new ViewHelpers.L10N(STRINGS_URI);
|
||||
const {cssTokenizer} = require("devtools/sourceeditor/css-tokenizer");
|
||||
|
||||
loader.lazyGetter(this, "asyncStorage",
|
||||
() => require("devtools/toolkit/shared/async-storage"));
|
||||
|
||||
const DEFAULT_FILTER_TYPE = "length";
|
||||
const UNIT_MAPPING = {
|
||||
percentage: "%",
|
||||
@ -116,31 +119,54 @@ function CSSFilterEditorWidget(el, value = "") {
|
||||
this._mouseDown = this._mouseDown.bind(this);
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this._input = this._input.bind(this);
|
||||
this._presetClick = this._presetClick.bind(this);
|
||||
this._savePreset = this._savePreset.bind(this);
|
||||
this._togglePresets = this._togglePresets.bind(this);
|
||||
|
||||
// Passed to asyncStorage, requires binding
|
||||
this.renderPresets = this.renderPresets.bind(this);
|
||||
|
||||
this._initMarkup();
|
||||
this._buildFilterItemMarkup();
|
||||
this._buildPresetItemMarkup();
|
||||
this._addEventListeners();
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.filters = [];
|
||||
this.setCssValue(value);
|
||||
this.renderPresets();
|
||||
}
|
||||
|
||||
exports.CSSFilterEditorWidget = CSSFilterEditorWidget;
|
||||
|
||||
CSSFilterEditorWidget.prototype = {
|
||||
_initMarkup: function() {
|
||||
const list = this.el.querySelector(".filters");
|
||||
this.el.appendChild(list);
|
||||
this.el.insertBefore(list, this.el.firstChild);
|
||||
this.container = this.el;
|
||||
this.list = list;
|
||||
|
||||
this.filtersList = this.el.querySelector("#filters");
|
||||
this.presetsList = this.el.querySelector("#presets");
|
||||
this.togglePresets = this.el.querySelector("#toggle-presets");
|
||||
this.filterSelect = this.el.querySelector("select");
|
||||
this.addPresetButton = this.el.querySelector(".presets-list .add");
|
||||
this.addPresetInput = this.el.querySelector(".presets-list .footer input");
|
||||
|
||||
this.el.querySelector(".presets-list input").value = "";
|
||||
|
||||
this._populateFilterSelect();
|
||||
},
|
||||
|
||||
_destroyMarkup: function() {
|
||||
this._filterItemMarkup.remove();
|
||||
this.el.remove();
|
||||
this.el = this.filtersList = this._filterItemMarkup = null;
|
||||
this.presetsList = this.togglePresets = this.filterSelect = null;
|
||||
this.addPresetButton = null;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._removeEventListeners();
|
||||
this._destroyMarkup();
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates <option> elements for each filter definition
|
||||
* in filterList
|
||||
@ -184,20 +210,42 @@ CSSFilterEditorWidget.prototype = {
|
||||
|
||||
let removeButton = this.doc.createElement("button");
|
||||
removeButton.className = "remove-button";
|
||||
value.appendChild(removeButton);
|
||||
|
||||
base.appendChild(name);
|
||||
base.appendChild(value);
|
||||
base.appendChild(removeButton);
|
||||
|
||||
this._filterItemMarkup = base;
|
||||
},
|
||||
|
||||
_buildPresetItemMarkup: function() {
|
||||
let base = this.doc.createElement("div");
|
||||
base.classList.add("preset");
|
||||
|
||||
let name = this.doc.createElement("label");
|
||||
base.appendChild(name);
|
||||
|
||||
let value = this.doc.createElement("span");
|
||||
base.appendChild(value);
|
||||
|
||||
let removeButton = this.doc.createElement("button");
|
||||
removeButton.classList.add("remove-button");
|
||||
|
||||
base.appendChild(removeButton);
|
||||
|
||||
this._presetItemMarkup = base;
|
||||
},
|
||||
|
||||
_addEventListeners: function() {
|
||||
this.addButton = this.el.querySelector("#add-filter");
|
||||
this.addButton.addEventListener("click", this._addButtonClick);
|
||||
this.list.addEventListener("click", this._removeButtonClick);
|
||||
this.list.addEventListener("mousedown", this._mouseDown);
|
||||
this.list.addEventListener("keydown", this._keyDown);
|
||||
this.filtersList.addEventListener("click", this._removeButtonClick);
|
||||
this.filtersList.addEventListener("mousedown", this._mouseDown);
|
||||
this.filtersList.addEventListener("keydown", this._keyDown);
|
||||
|
||||
this.presetsList.addEventListener("click", this._presetClick);
|
||||
this.togglePresets.addEventListener("click", this._togglePresets);
|
||||
this.addPresetButton.addEventListener("click", this._savePreset);
|
||||
|
||||
// These events are event delegators for
|
||||
// drag-drop re-ordering and label-dragging
|
||||
@ -205,11 +253,29 @@ CSSFilterEditorWidget.prototype = {
|
||||
this.win.addEventListener("mouseup", this._mouseUp);
|
||||
|
||||
// Used to workaround float-precision problems
|
||||
this.list.addEventListener("input", this._input);
|
||||
this.filtersList.addEventListener("input", this._input);
|
||||
},
|
||||
|
||||
_removeEventListeners: function() {
|
||||
this.addButton.removeEventListener("click", this._addButtonClick);
|
||||
this.filtersList.removeEventListener("click", this._removeButtonClick);
|
||||
this.filtersList.removeEventListener("mousedown", this._mouseDown);
|
||||
this.filtersList.removeEventListener("keydown", this._keyDown);
|
||||
|
||||
this.presetsList.removeEventListener("click", this._presetClick);
|
||||
this.togglePresets.removeEventListener("click", this._togglePresets);
|
||||
this.addPresetButton.removeEventListener("click", this._savePreset);
|
||||
|
||||
// These events are used for drag drop re-ordering
|
||||
this.win.removeEventListener("mousemove", this._mouseMove);
|
||||
this.win.removeEventListener("mouseup", this._mouseUp);
|
||||
|
||||
// Used to workaround float-precision problems
|
||||
this.filtersList.removeEventListener("input", this._input);
|
||||
},
|
||||
|
||||
_getFilterElementIndex: function(el) {
|
||||
return [...this.list.children].indexOf(el);
|
||||
return [...this.filtersList.children].indexOf(el);
|
||||
},
|
||||
|
||||
_keyDown: function(e) {
|
||||
@ -297,7 +363,7 @@ CSSFilterEditorWidget.prototype = {
|
||||
filterEl.startingY = e.pageY;
|
||||
filterEl.classList.add("dragging");
|
||||
|
||||
this.container.classList.add("dragging");
|
||||
this.el.classList.add("dragging");
|
||||
// label-dragging
|
||||
} else if (e.target.classList.contains("devtools-draglabel")) {
|
||||
let label = e.target,
|
||||
@ -357,7 +423,7 @@ CSSFilterEditorWidget.prototype = {
|
||||
},
|
||||
|
||||
_dragFilterElement: function(e) {
|
||||
const rect = this.list.getBoundingClientRect(),
|
||||
const rect = this.filtersList.getBoundingClientRect(),
|
||||
top = e.pageY - LIST_PADDING,
|
||||
bottom = e.pageY + LIST_PADDING;
|
||||
// don't allow dragging over top/bottom of list
|
||||
@ -365,7 +431,7 @@ CSSFilterEditorWidget.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterEl = this.list.querySelector(".dragging");
|
||||
const filterEl = this.filtersList.querySelector(".dragging");
|
||||
|
||||
const delta = e.pageY - filterEl.startingY;
|
||||
filterEl.style.top = delta + "px";
|
||||
@ -376,7 +442,7 @@ CSSFilterEditorWidget.prototype = {
|
||||
change = change > 0 ? Math.floor(change) :
|
||||
change < 0 ? Math.ceil(change) : change;
|
||||
|
||||
const children = this.list.children;
|
||||
const children = this.filtersList.children;
|
||||
const index = [...children].indexOf(filterEl);
|
||||
const destination = index + change;
|
||||
|
||||
@ -392,9 +458,9 @@ CSSFilterEditorWidget.prototype = {
|
||||
const target = change > 0 ? children[destination + 1]
|
||||
: children[destination];
|
||||
if (target) {
|
||||
this.list.insertBefore(filterEl, target);
|
||||
this.filtersList.insertBefore(filterEl, target);
|
||||
} else {
|
||||
this.list.appendChild(filterEl);
|
||||
this.filtersList.appendChild(filterEl);
|
||||
}
|
||||
|
||||
filterEl.removeAttribute("style");
|
||||
@ -442,30 +508,83 @@ CSSFilterEditorWidget.prototype = {
|
||||
if (!this.isReorderingFilter) {
|
||||
return;
|
||||
}
|
||||
let filterEl = this.list.querySelector(".dragging");
|
||||
let filterEl = this.filtersList.querySelector(".dragging");
|
||||
|
||||
this.isReorderingFilter = false;
|
||||
filterEl.classList.remove("dragging");
|
||||
this.container.classList.remove("dragging");
|
||||
this.el.classList.remove("dragging");
|
||||
filterEl.removeAttribute("style");
|
||||
|
||||
this.emit("updated", this.getCssValue());
|
||||
this.render();
|
||||
},
|
||||
|
||||
_presetClick: function(e) {
|
||||
let el = e.target;
|
||||
let preset = el.closest(".preset");
|
||||
if (!preset) {
|
||||
return;
|
||||
}
|
||||
|
||||
let id = +preset.dataset.id;
|
||||
|
||||
this.getPresets().then(presets => {
|
||||
if (el.classList.contains("remove-button")) {
|
||||
// If the click happened on the remove button.
|
||||
presets.splice(id, 1);
|
||||
this.setPresets(presets).then(this.renderPresets, Cu.reportError);
|
||||
} else {
|
||||
// Or if the click happened on a preset.
|
||||
let p = presets[id];
|
||||
|
||||
this.setCssValue(p.value);
|
||||
this.addPresetInput.value = p.name;
|
||||
}
|
||||
}, Cu.reportError);
|
||||
},
|
||||
|
||||
_togglePresets: function() {
|
||||
this.el.classList.toggle("show-presets");
|
||||
this.emit("render");
|
||||
},
|
||||
|
||||
_savePreset: function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
let name = this.addPresetInput.value,
|
||||
value = this.getCssValue();
|
||||
|
||||
if (!name || !value || value === "none") {
|
||||
this.emit("preset-save-error");
|
||||
return;
|
||||
}
|
||||
|
||||
this.getPresets().then(presets => {
|
||||
let index = presets.findIndex(preset => preset.name === name);
|
||||
|
||||
if (index > -1) {
|
||||
presets[index].value = value;
|
||||
} else {
|
||||
presets.push({name, value});
|
||||
}
|
||||
|
||||
this.setPresets(presets).then(this.renderPresets, Cu.reportError);
|
||||
}, Cu.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the list and renders filters, binding required events.
|
||||
* There are some delegated events bound in _addEventListeners method
|
||||
*/
|
||||
render: function() {
|
||||
if (!this.filters.length) {
|
||||
this.list.innerHTML = `<p> ${L10N.getStr("emptyFilterList")} <br />
|
||||
this.filtersList.innerHTML = `<p> ${L10N.getStr("emptyFilterList")} <br />
|
||||
${L10N.getStr("addUsingList")} </p>`;
|
||||
this.emit("render");
|
||||
return;
|
||||
}
|
||||
|
||||
this.list.innerHTML = "";
|
||||
this.filtersList.innerHTML = "";
|
||||
|
||||
let base = this._filterItemMarkup;
|
||||
|
||||
@ -515,19 +634,48 @@ CSSFilterEditorWidget.prototype = {
|
||||
unitPreview.remove();
|
||||
}
|
||||
|
||||
this.list.appendChild(el);
|
||||
this.filtersList.appendChild(el);
|
||||
}
|
||||
|
||||
let el = this.list.querySelector(`.filter:last-of-type input`);
|
||||
if (el) {
|
||||
el.focus();
|
||||
let lastInput = this.filtersList.querySelector(`.filter:last-of-type input`);
|
||||
if (lastInput) {
|
||||
lastInput.focus();
|
||||
// move cursor to end of input
|
||||
el.setSelectionRange(el.value.length, el.value.length);
|
||||
const end = lastInput.value.length;
|
||||
lastInput.setSelectionRange(end, end);
|
||||
}
|
||||
|
||||
this.emit("render");
|
||||
},
|
||||
|
||||
renderPresets: function() {
|
||||
this.getPresets().then(presets => {
|
||||
if (!presets || !presets.length) {
|
||||
this.presetsList.innerHTML = `<p>${L10N.getStr("emptyPresetList")}</p>`;
|
||||
this.emit("render");
|
||||
return;
|
||||
}
|
||||
let base = this._presetItemMarkup;
|
||||
|
||||
this.presetsList.innerHTML = "";
|
||||
|
||||
for (let [index, preset] of presets.entries()) {
|
||||
let el = base.cloneNode(true);
|
||||
|
||||
let [label, span] = el.children;
|
||||
|
||||
el.dataset.id = index;
|
||||
|
||||
label.textContent = preset.name;
|
||||
span.textContent = preset.value;
|
||||
|
||||
this.presetsList.appendChild(el);
|
||||
}
|
||||
|
||||
this.emit("render");
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* returns definition of a filter as defined in filterList
|
||||
*
|
||||
@ -689,29 +837,19 @@ CSSFilterEditorWidget.prototype = {
|
||||
this.emit("updated", this.getCssValue());
|
||||
},
|
||||
|
||||
_removeEventListeners: function() {
|
||||
this.addButton.removeEventListener("click", this._addButtonClick);
|
||||
this.list.removeEventListener("click", this._removeButtonClick);
|
||||
this.list.removeEventListener("mousedown", this._mouseDown);
|
||||
this.list.removeEventListener("keydown", this._keyDown);
|
||||
getPresets: function() {
|
||||
return asyncStorage.getItem("cssFilterPresets").then(presets => {
|
||||
if (!presets) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// These events are used for drag drop re-ordering
|
||||
this.win.removeEventListener("mousemove", this._mouseMove);
|
||||
this.win.removeEventListener("mouseup", this._mouseUp);
|
||||
|
||||
// Used to workaround float-precision problems
|
||||
this.list.removeEventListener("input", this._input);
|
||||
return presets;
|
||||
}, Cu.reportError);
|
||||
},
|
||||
|
||||
_destroyMarkup: function() {
|
||||
this._filterItemMarkup.remove();
|
||||
this.el.remove();
|
||||
this.el = this.list = this.container = this._filterItemMarkup = null;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._removeEventListeners();
|
||||
this._destroyMarkup();
|
||||
setPresets: function(presets) {
|
||||
return asyncStorage.setItem("cssFilterPresets", presets)
|
||||
.catch(Cu.reportError);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -863,8 +863,9 @@ Tooltip.prototype = {
|
||||
* that resolves to the instance of the widget when ready.
|
||||
*/
|
||||
setFilterContent: function(filter) {
|
||||
let dimensions = {width: "350", height: "350"};
|
||||
let dimensions = {width: "500", height: "200"};
|
||||
let panel = this.panel;
|
||||
|
||||
return this.setIFrameContent(dimensions, FILTER_FRAME).then(onLoaded);
|
||||
|
||||
function onLoaded(iframe) {
|
||||
@ -874,14 +875,8 @@ Tooltip.prototype = {
|
||||
let container = win.document.getElementById("container");
|
||||
let widget = new CSSFilterEditorWidget(container, filter);
|
||||
|
||||
iframe.height = doc.offsetHeight;
|
||||
|
||||
widget.on("render", () => {
|
||||
iframe.height = doc.offsetHeight;
|
||||
});
|
||||
|
||||
// Resolve to the widget instance whenever the popup becomes visible
|
||||
if (panel.state == "open") {
|
||||
if (panel.state === "open") {
|
||||
def.resolve(widget);
|
||||
} else {
|
||||
panel.addEventListener("popupshown", function shown() {
|
||||
|
@ -16,14 +16,26 @@
|
||||
<body>
|
||||
|
||||
<div id="container">
|
||||
<div class="filters">
|
||||
<div class="filters-list">
|
||||
<div id="filters"></div>
|
||||
<div class="footer">
|
||||
<select value="">
|
||||
<option value="">&filterListSelectPlaceholder;</option>
|
||||
</select>
|
||||
<button id="add-filter" class="add">&addNewFilterButton;</button>
|
||||
<button id="toggle-presets">&presetsToggleButton;</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editor-footer">
|
||||
<select value="">
|
||||
<option value="">&filterListSelectPlaceholder;</option>
|
||||
</select>
|
||||
<button id="add-filter">&addNewFilterButton;</button>
|
||||
|
||||
<div class="presets-list">
|
||||
<div id="presets"></div>
|
||||
<div class="footer">
|
||||
<input value="" class="devtools-textinput"
|
||||
placeholder="&newPresetPlaceholder;"></input>
|
||||
<button class="add">&savePresetButton;</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -2,74 +2,103 @@
|
||||
* 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/. */
|
||||
|
||||
#container {
|
||||
color: var(--theme-body-color);
|
||||
padding: 5px;
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font: message-box;
|
||||
color: var(--theme-body-color);
|
||||
}
|
||||
|
||||
/* Main container: Displays the filters and presets in 2 columns */
|
||||
|
||||
#container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#container.dragging {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.theme-light #add-filter,
|
||||
.theme-light .remove-button {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.filter {
|
||||
.filters-list,
|
||||
.presets-list {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.filters-list {
|
||||
/* Allow the filters list to take the full width when the presets list is
|
||||
hidden */
|
||||
flex-grow: 1;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.presets-list {
|
||||
/* Make sure that when the presets list is shown, it has a fixed width */
|
||||
width: 200px;
|
||||
padding-left: 6px;
|
||||
transition: width .1s;
|
||||
flex-shrink: 0;
|
||||
border-left: 1px solid var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
.presets-list .add {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#container:not(.show-presets) .presets-list {
|
||||
width: 0;
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
#container.show-presets .filters-list {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
/* The list of filters and list of presets should push their footers to the
|
||||
bottom, so they can take as much space as there is */
|
||||
|
||||
#filters,
|
||||
#presets {
|
||||
flex-grow: 1;
|
||||
/* Avoid pushing below the tooltip's area */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* The filters and presets list both have footers displayed at the bottom.
|
||||
These footers have some input (taking up as much space as possible) and an
|
||||
add button next */
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
margin: 10px 3px;
|
||||
}
|
||||
|
||||
.footer :not(button) {
|
||||
flex-grow: 1;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
/* Styles for 1 filter function item */
|
||||
|
||||
.filter,
|
||||
.filter-name,
|
||||
.filter-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.filter-name {
|
||||
padding-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-value {
|
||||
min-width: 150px;
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(chrome://browser/skin/devtools/close@2x.png);
|
||||
background-size: 16px;
|
||||
font-size: 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* drag/drop handle */
|
||||
#container i {
|
||||
width: 10px;
|
||||
margin-right: 15px;
|
||||
padding: 10px 0;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
#container i::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 1px;
|
||||
background: currentColor;
|
||||
box-shadow: 0 3px 0 0 currentColor,
|
||||
0 -3px 0 0 currentColor;
|
||||
}
|
||||
|
||||
#container .dragging {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
cursor: grab;
|
||||
width: 120px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.filter-name label {
|
||||
@ -81,14 +110,105 @@
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
/* drag/drop handle */
|
||||
|
||||
.filter-name i {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 10px;
|
||||
cursor: grab;
|
||||
background: linear-gradient(to bottom,
|
||||
currentColor 0,
|
||||
currentcolor 1px,
|
||||
transparent 1px,
|
||||
transparent 2px);
|
||||
background-repeat: repeat-y;
|
||||
background-size: auto 4px;
|
||||
background-position: 0 1px;
|
||||
}
|
||||
|
||||
.filter-value {
|
||||
min-width: 150px;
|
||||
margin-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-value input {
|
||||
min-width: 50%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.filter-value span {
|
||||
max-width: 20px;
|
||||
width: 20px;
|
||||
|
||||
.theme-light .add,
|
||||
.theme-light .remove-button {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.preset {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
padding: 3px 5px;
|
||||
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.preset label,
|
||||
.preset span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preset label {
|
||||
flex: 1 0;
|
||||
cursor: pointer;
|
||||
color: var(--theme-body-color);
|
||||
}
|
||||
|
||||
.preset:hover {
|
||||
background: var(--theme-selection-background);
|
||||
}
|
||||
|
||||
.preset:hover label, .preset:hover span {
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.theme-light .preset:hover .remove-button {
|
||||
filter: invert(0);
|
||||
}
|
||||
|
||||
.preset .remove-button {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.preset span {
|
||||
flex: 2 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
order: 3;
|
||||
color: var(--theme-body-color-alt);
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(chrome://browser/skin/devtools/close@2x.png);
|
||||
background-size: cover;
|
||||
font-size: 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#container .dragging {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
/* message shown when there's no filter specified */
|
||||
@ -97,20 +217,7 @@
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
#editor-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#editor-footer select {
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
font: inherit;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
#add-filter {
|
||||
-moz-appearance: none;
|
||||
.add {
|
||||
background: url(chrome://browser/skin/devtools/add.svg);
|
||||
background-size: cover;
|
||||
border: none;
|
||||
|
@ -18,7 +18,6 @@ const overlays = require("devtools/styleinspector/style-inspector-overlays");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Templater.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||
"resource://gre/modules/PluralForm.jsm");
|
||||
@ -221,37 +220,6 @@ CssHtmlTree.l10n = function CssHtmlTree_l10n(aName)
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone the given template node, and process it by resolving ${} references
|
||||
* in the template.
|
||||
*
|
||||
* @param {nsIDOMElement} aTemplate the template note to use.
|
||||
* @param {nsIDOMElement} aDestination the destination node where the
|
||||
* processed nodes will be displayed.
|
||||
* @param {object} aData the data to pass to the template.
|
||||
* @param {Boolean} aPreserveDestination If true then the template will be
|
||||
* appended to aDestination's content else aDestination.innerHTML will be
|
||||
* cleared before the template is appended.
|
||||
*/
|
||||
CssHtmlTree.processTemplate = function CssHtmlTree_processTemplate(aTemplate,
|
||||
aDestination, aData, aPreserveDestination)
|
||||
{
|
||||
if (!aPreserveDestination) {
|
||||
aDestination.innerHTML = "";
|
||||
}
|
||||
|
||||
// All the templater does is to populate a given DOM tree with the given
|
||||
// values, so we need to clone the template first.
|
||||
let duplicated = aTemplate.cloneNode(true);
|
||||
|
||||
// See https://github.com/mozilla/domtemplate/blob/master/README.md
|
||||
// for docs on the template() function
|
||||
template(duplicated, aData, { allowEval: true });
|
||||
while (duplicated.firstChild) {
|
||||
aDestination.appendChild(duplicated.firstChild);
|
||||
}
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(CssHtmlTree, "_strings", function() {
|
||||
return Services.strings.createBundle(
|
||||
"chrome://global/locale/devtools/styleinspector.properties");
|
||||
@ -1407,6 +1375,7 @@ PropertyView.prototype = {
|
||||
promises.push(selector.ready);
|
||||
}
|
||||
|
||||
this.matchedSelectorsContainer.innerHTML = "";
|
||||
this.matchedSelectorsContainer.appendChild(frag);
|
||||
return promise.all(promises);
|
||||
},
|
||||
|
@ -12,3 +12,13 @@
|
||||
|
||||
<!-- LOCALIZATION NOTE (addNewFilterButton): This string is displayed on a button used to add new filters -->
|
||||
<!ENTITY addNewFilterButton "Add">
|
||||
|
||||
<!-- LOCALIZATION NOTE (newPresetPlaceholder): This string is used as
|
||||
- a placeholder in the list of presets which is used to save a new preset -->
|
||||
<!ENTITY newPresetPlaceholder "Preset Name">
|
||||
|
||||
<!-- LOCALIZATION NOTE (savePresetButton): This string is displayed on a button used to save a new preset -->
|
||||
<!ENTITY savePresetButton "Save">
|
||||
|
||||
<!-- LOCALIZATION NOTE(presetsToggleButton): This string is used in a button which toggles the presets list -->
|
||||
<!ENTITY presetsToggleButton "Presets">
|
||||
|
@ -11,6 +11,12 @@
|
||||
# (no filter specified / all removed)
|
||||
emptyFilterList=No filter specified
|
||||
|
||||
# LOCALIZATION NOTE (emptyPresetList):
|
||||
# This string is displayed when preset's list is empty
|
||||
emptyPresetList=You don't have any saved presets, \
|
||||
You can store filter presets by choosing a name and saving them. \
|
||||
Presets are quickly accessible and you can re-use them with ease.
|
||||
|
||||
# LOCALIZATION NOTE (addUsingList):
|
||||
# This string is displayed under [emptyFilterList] when filter's
|
||||
# list is empty, guiding user to add a filter using the list below it
|
||||
|
@ -4,4 +4,8 @@
|
||||
|
||||
#filter substitution
|
||||
|
||||
# LOCALIZATION NOTE: this preference is set to true for en-US specifically,
|
||||
# locales without this line have the setting set to false by default.
|
||||
pref("browser.search.geoSpecificDefaults", true);
|
||||
|
||||
pref("general.useragent.locale", "@AB_CD@");
|
||||
|
@ -124,11 +124,6 @@ body {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
/* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */
|
||||
.rule-text {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.matched {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
@ -3,20 +3,13 @@
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<svg height="0" xmlns="http://www.w3.org/2000/svg">
|
||||
<filter id="invert" x="0%" y="0%" width="100%" height="100%" >
|
||||
<feComponentTransfer>
|
||||
<feFuncR type="table" tableValues=".1 0"/>
|
||||
<feFuncG type="table" tableValues=".1 0"/>
|
||||
<feFuncB type="table" tableValues=".1 0"/>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
<filter id="invert-white" x="0%" y="0%" width="100%" height="100%" >
|
||||
<feComponentTransfer>
|
||||
<feFuncR type="table" tableValues=".6 0"/>
|
||||
<feFuncG type="table" tableValues=".6 0"/>
|
||||
<feFuncB type="table" tableValues=".6 0"/>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
<filter id="invert" x="0%" y="0%" width="100%" height="100%" >
|
||||
<feComponentTransfer>
|
||||
<feFuncR type="table" tableValues=".1 0"/>
|
||||
<feFuncG type="table" tableValues=".1 0"/>
|
||||
<feFuncB type="table" tableValues=".1 0"/>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
|
||||
<!-- Web Audio Gradients -->
|
||||
<linearGradient id="bypass-light" x1="6%" y1="8%" x2="12%" y2="12%" spreadMethod="repeat">
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.0 KiB |
@ -2,6 +2,6 @@
|
||||
<!-- 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/. -->
|
||||
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="#000" width="16" height="16">
|
||||
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="#0A0805" width="16" height="16">
|
||||
<path d="M 0.00,160.00 L 512.00,160.00 L 480.00,480.00 L 32.00,480.00 L 0.00,160.00 Z M 464.00,96.00 L 480.00,128.00 L 32.00,128.00 L 64.00,64.00 L 240.00,64.00 L 256.00,96.00 L 464.00,96.00 Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 564 B |
@ -2,6 +2,6 @@
|
||||
<!-- 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/. -->
|
||||
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="#000" width="16" height="16">
|
||||
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="#0A0805" width="16" height="16">
|
||||
<path d="M 416.00,480.00L 512.00,224.00L 96.00,224.00L0.00,480.00 zM 64.00,192.00 L 0.00,480.00 L 0.00,64.00 L 144.00,64.00 L 208.00,128.00 L 416.00,128.00 L 416.00,192.00 Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 542 B After Width: | Height: | Size: 545 B |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
@ -1,7 +1,7 @@
|
||||
<!-- 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/. -->
|
||||
<svg width="16" xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 16 16">
|
||||
<svg width="16" xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 16 16" fill="#0A0805">
|
||||
<path d="m1.3,12.5v-2.4c0,0 0,2.5 6.7,2.5 6.7,0 6.7-2.5 6.7-2.5v2.4c0,0 0,2.7-6.8,2.7-6.6,0-6.6-2.7-6.6-2.7z"/>
|
||||
<path d="m14.7,3.4c0-1.4-3-2.5-6.7-2.5s-6.7,1.1-6.7,2.5c0,.2 0,.3 .1,.5-.1-.3-.1-.4-.1-.4v1.5c0,0 0,2.7 6.7,2.7 6.7,0 6.8-2.7 6.8-2.7v-1.6c0,.1 0,.2-.1,.5-0-.2-0-.3-0-.5z"/>
|
||||
<path d="m1.3,8.7v-2.4c0,0 0,2.5 6.7,2.5 6.7,0 6.7-2.5 6.7-2.5v2.4c0,0 0,2.7-6.8,2.7-6.6-0-6.6-2.7-6.6-2.7z"/>
|
||||
|
Before Width: | Height: | Size: 706 B After Width: | Height: | Size: 721 B |
@ -10,8 +10,8 @@
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.theme-dark #storage-tree {
|
||||
background: #343c45; /* Toolbars */
|
||||
#storage-tree {
|
||||
background: var(--theme-sidebar-background);
|
||||
}
|
||||
|
||||
#storage-tree .tree-widget-item[type="store"]:after {
|
||||
|
@ -10,10 +10,14 @@
|
||||
%define smw_itemDarkBottomBorder rgba(128,128,128,0.15)
|
||||
%define smw_itemLightTopBorder rgba(128,128,128,0.15)
|
||||
%define smw_itemLightBottomBorder transparent
|
||||
%define table_itemDarkStartBorder rgba(0,0,0,0.2)
|
||||
%define table_itemDarkEndBorder rgba(128,128,128,0.15)
|
||||
%define table_itemLightStartBorder rgba(128,128,128,0.25)
|
||||
%define table_itemLightEndBorder transparent
|
||||
.theme-dark {
|
||||
--table-splitter-color: rgba(255,255,255,0.15);
|
||||
--table-zebra-background: rgba(255,255,255,0.05);
|
||||
}
|
||||
.theme-light {
|
||||
--table-splitter-color: rgba(0,0,0,0.15);
|
||||
--table-zebra-background: rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* Generic pane helpers */
|
||||
|
||||
@ -1206,43 +1210,16 @@
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.theme-light .table-widget-body {
|
||||
background: var(--theme-sidebar-background);
|
||||
}
|
||||
|
||||
.theme-dark .table-widget-body,
|
||||
.theme-dark .table-widget-empty-text {
|
||||
background-color: var(--theme-toolbar-background);
|
||||
}
|
||||
|
||||
.theme-dark .table-widget-body:-moz-locale-dir(ltr) {
|
||||
box-shadow: inset -1px 0 0 @smw_marginDark@;
|
||||
}
|
||||
|
||||
.theme-dark .table-widget-body:-moz-locale-dir(rtl) {
|
||||
box-shadow: inset 1px 0 0 @smw_marginDark@;
|
||||
}
|
||||
|
||||
.table-widget-body:-moz-locale-dir(ltr) {
|
||||
box-shadow: inset -1px 0 0 @smw_marginLight@;
|
||||
}
|
||||
|
||||
.table-widget-body:-moz-locale-dir(rtl) {
|
||||
box-shadow: inset 1px 0 0 @smw_marginLight@;
|
||||
.table-widget-body,
|
||||
.table-widget-empty-text {
|
||||
background-color: var(--theme-body-background);
|
||||
}
|
||||
|
||||
/* Column Headers */
|
||||
|
||||
.theme-dark .table-widget-column-header,
|
||||
.theme-dark .table-widget-cell {
|
||||
-moz-border-end: 1px solid @table_itemDarkStartBorder@;
|
||||
box-shadow: inset 1px 0 0 @table_itemDarkEndBorder@;
|
||||
}
|
||||
|
||||
.theme-light .table-widget-column-header,
|
||||
.theme-light .table-widget-cell {
|
||||
-moz-border-end: 1px solid @table_itemLightStartBorder@;
|
||||
box-shadow: inset 1px 0 0 @table_itemLightEndBorder@;
|
||||
.table-widget-column-header,
|
||||
.table-widget-cell {
|
||||
-moz-border-end: 1px solid var(--table-splitter-color) !important;
|
||||
}
|
||||
|
||||
/* Table widget column header colors are taken from netmonitor.inc.css to match
|
||||
@ -1253,10 +1230,8 @@
|
||||
background: rgba(0,0,0,0);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
min-height: 32px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 8px 0 0 !important;
|
||||
padding: 5px 0 0 !important;
|
||||
color: inherit;
|
||||
text-align: center;
|
||||
font-weight: inherit !important;
|
||||
@ -1264,27 +1239,29 @@
|
||||
}
|
||||
|
||||
.table-widget-column-header:hover {
|
||||
background: rgba(0,0,0,0.10);
|
||||
background-image: linear-gradient(rgba(0,0,0,0.10), rgba(0,0,0,0.10));
|
||||
}
|
||||
|
||||
.table-widget-column-header:hover:active {
|
||||
background: rgba(0,0,0,0.25);
|
||||
background-image: linear-gradient(rgba(0,0,0,0.25), rgba(0,0,0,0.25));
|
||||
}
|
||||
|
||||
.table-widget-column-header:not(:active)[sorted] {
|
||||
background: rgba(0,0,0,0.15);
|
||||
background-image: linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.15));
|
||||
}
|
||||
|
||||
.table-widget-column-header:not(:active)[sorted=ascending] {
|
||||
background-image: radial-gradient(farthest-side at center top, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3));
|
||||
background-size: 100% 1px;
|
||||
background-repeat: no-repeat;
|
||||
background-image: radial-gradient(farthest-side at center top, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3)),
|
||||
linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.15));
|
||||
background-size: 100% 1px, auto;
|
||||
background-repeat: no-repeat, repeat;
|
||||
}
|
||||
|
||||
.table-widget-column-header:not(:active)[sorted=descending] {
|
||||
background-image: radial-gradient(farthest-side at center bottom, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3));
|
||||
background-size: 100% 1px;
|
||||
background-repeat: no-repeat;
|
||||
background-image: radial-gradient(farthest-side at center bottom, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3)),
|
||||
linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.15));
|
||||
background-size: 100% 1px, auto;
|
||||
background-repeat: no-repeat, repeat;
|
||||
background-position: bottom;
|
||||
}
|
||||
|
||||
@ -1292,44 +1269,22 @@
|
||||
|
||||
.table-widget-cell {
|
||||
width: 100%;
|
||||
margin: -1px 0 !important;
|
||||
padding: 3px 4px;
|
||||
background-clip: padding-box;
|
||||
min-width: 100px;
|
||||
-moz-user-focus: normal;
|
||||
}
|
||||
|
||||
.theme-dark .table-widget-cell {
|
||||
border-top: 1px solid @smw_itemDarkTopBorder@;
|
||||
border-bottom: 1px solid @smw_itemDarkBottomBorder@;
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.theme-dark:not(.filtering) .table-widget-cell:nth-child(odd):not(.theme-selected),
|
||||
.theme-dark .table-widget-cell:not(.theme-selected)[odd] {
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.theme-dark .table-widget-cell:last-of-type {
|
||||
box-shadow: inset 0 -1px 0 @smw_itemDarkTopBorder@;
|
||||
}
|
||||
|
||||
.theme-light .table-widget-cell {
|
||||
border-top: 1px solid @smw_itemLightTopBorder@;
|
||||
border-bottom: 1px solid @smw_itemLightBottomBorder@;
|
||||
}
|
||||
|
||||
.theme-light .table-widget-cell:not(.theme-selected) {
|
||||
margin-bottom: -1px !important;
|
||||
border-bottom: 1px solid transparent;
|
||||
color: var(--theme-body-color);
|
||||
}
|
||||
|
||||
.theme-light:not(.filtering) .table-widget-cell:nth-child(odd):not(.theme-selected),
|
||||
.theme-light .table-widget-cell:not(.theme-selected)[odd] {
|
||||
background: rgba(128,128,128,0.05);
|
||||
.table-widget-cell:last-child {
|
||||
border-bottom: 1px solid var(--table-splitter-color);
|
||||
}
|
||||
|
||||
.theme-light .table-widget-cell:last-of-type {
|
||||
box-shadow: inset 0 -1px 0 @smw_itemLightTopBorder@;
|
||||
:root:not(.filtering) .table-widget-cell:nth-child(odd):not(.theme-selected),
|
||||
.table-widget-cell:not(.theme-selected)[odd] {
|
||||
background: var(--table-zebra-background);
|
||||
}
|
||||
|
||||
.table-widget-cell.flash-out {
|
||||
@ -1351,10 +1306,6 @@
|
||||
margin-top: -20px !important;
|
||||
}
|
||||
|
||||
.theme-light .table-widget-empty-text {
|
||||
background: #F7F7F7; /* Background-Sidebar */
|
||||
}
|
||||
|
||||
.table-widget-body:empty + .table-widget-empty-text:not([value=""]),
|
||||
.table-widget-body[empty] + .table-widget-empty-text:not([value=""]) {
|
||||
display: block;
|
||||
@ -1406,11 +1357,11 @@
|
||||
}
|
||||
|
||||
.tree-widget-item[level="1"] {
|
||||
font-weight: 800;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Twisties */
|
||||
.tree-widget-item:before {
|
||||
.tree-widget-item::before {
|
||||
content: "";
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
@ -1423,24 +1374,24 @@
|
||||
background-position: -28px -14px;
|
||||
}
|
||||
|
||||
.tree-widget-item:-moz-locale-dir(rtl):before {
|
||||
.tree-widget-item:-moz-locale-dir(rtl)::before {
|
||||
float: right;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.theme-light .tree-widget-item:before {
|
||||
.theme-light .tree-widget-item:not(.theme-selected)::before {
|
||||
background-position: 0 -14px;
|
||||
}
|
||||
|
||||
.tree-widget-item[empty]:before {
|
||||
.tree-widget-item[empty]::before {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.tree-widget-item[expanded]:before {
|
||||
.tree-widget-item[expanded]::before {
|
||||
background-position: -42px -14px;
|
||||
}
|
||||
|
||||
.theme-light .tree-widget-item[expanded]:before {
|
||||
.theme-light .tree-widget-item:not(.theme-selected)[expanded]:before {
|
||||
background-position: -14px -14px;
|
||||
}
|
||||
|
||||
@ -1518,7 +1469,7 @@
|
||||
|
||||
/* Custom icons for certain tree items indicating the type of the item */
|
||||
|
||||
.tree-widget-item[type]:after {
|
||||
.tree-widget-item[type]::after {
|
||||
content: "";
|
||||
float: left;
|
||||
width: 16px;
|
||||
@ -1526,31 +1477,36 @@
|
||||
-moz-margin-end: 4px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 20px auto;
|
||||
filter: url('filters.svg#invert');
|
||||
background-position: 0 0;
|
||||
background-size: auto 20px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.tree-widget-item:-moz-locale-dir(rtl):after {
|
||||
.tree-widget-item.theme-selected[type]::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tree-widget-item:-moz-locale-dir(rtl)::after {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.theme-dark .tree-widget-item[type]:after {
|
||||
filter: url('filters.svg#invert-white');
|
||||
.theme-light .tree-widget-item.theme-selected[type]::after,
|
||||
.theme-dark .tree-widget-item[type]::after {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.tree-widget-item[type="dir"]:after {
|
||||
.tree-widget-item[type="dir"]::after {
|
||||
background-image: url(chrome://browser/skin/devtools/filetype-dir-close.svg);
|
||||
background-position: 2px 0;
|
||||
background-size: auto 16px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.tree-widget-item[type="dir"][expanded]:not([empty]):after {
|
||||
.tree-widget-item[type="dir"][expanded]:not([empty])::after {
|
||||
background-image: url(chrome://browser/skin/devtools/filetype-dir-open.svg);
|
||||
}
|
||||
|
||||
.tree-widget-item[type="url"]:after {
|
||||
.tree-widget-item[type="url"]::after {
|
||||
background-image: url(chrome://browser/skin/devtools/filetype-globe.svg);
|
||||
background-size: auto 18px;
|
||||
width: 18px;
|
||||
|
@ -1538,15 +1538,18 @@ html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
|
||||
|
||||
.ac-result-type-bookmark,
|
||||
.autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
|
||||
list-style-image: url("chrome://browser/skin/places/bookmark.png");
|
||||
-moz-image-region: rect(0px 32px 16px 16px);
|
||||
list-style-image: url("chrome://browser/skin/places/autocomplete-star.png");
|
||||
-moz-image-region: rect(0 16px 16px 0);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
richlistitem[selected="true"][current="true"] > .ac-title-box > .ac-result-type-bookmark,
|
||||
.autocomplete-treebody::-moz-tree-image(selected, current, bookmark, treecolAutoCompleteImage) {
|
||||
-moz-image-region: rect(0px 48px 16px 32px);
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
.ac-result-type-bookmark,
|
||||
.autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
|
||||
list-style-image: url("chrome://browser/skin/places/autocomplete-star@2x.png");
|
||||
-moz-image-region: rect(0 32px 32px 0);
|
||||
}
|
||||
}
|
||||
|
||||
.ac-result-type-keyword,
|
||||
@ -1559,6 +1562,18 @@ richlistitem[type~="action"][actiontype="searchengine"] > .ac-title-box > .ac-si
|
||||
|
||||
@media not all and (-moz-os-version: windows-vista) and (-moz-windows-default-theme) {
|
||||
@media not all and (-moz-os-version: windows-win7) and (-moz-windows-default-theme) {
|
||||
richlistitem[selected="true"][current="true"] > .ac-title-box > .ac-result-type-bookmark,
|
||||
.autocomplete-treebody::-moz-tree-image(selected, current, bookmark, treecolAutoCompleteImage) {
|
||||
-moz-image-region: rect(0 32px 16px 16px);
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
richlistitem[selected="true"][current="true"] > .ac-title-box > .ac-result-type-bookmark,
|
||||
.autocomplete-treebody::-moz-tree-image(selected, current, bookmark, treecolAutoCompleteImage) {
|
||||
-moz-image-region: rect(0 64px 32px 32px);
|
||||
}
|
||||
}
|
||||
|
||||
.ac-result-type-keyword[selected="true"],
|
||||
.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage, selected),
|
||||
richlistitem[type~="action"][actiontype="searchengine"][selected="true"] > .ac-title-box > .ac-site-icon {
|
||||
@ -1864,15 +1879,6 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
||||
|
||||
/* bookmarks menu-button */
|
||||
|
||||
#bookmarks-menu-button.bookmark-item {
|
||||
list-style-image: url("chrome://browser/skin/places/bookmark.png");
|
||||
-moz-image-region: rect(0px 16px 16px 0px);
|
||||
}
|
||||
|
||||
#bookmarks-menu-button.bookmark-item[starred] {
|
||||
-moz-image-region: rect(0px 32px 16px 16px);
|
||||
}
|
||||
|
||||
#bookmarks-menu-button.bookmark-item > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
|
||||
-moz-margin-start: 5px;
|
||||
}
|
||||
@ -2860,7 +2866,13 @@ chatbox {
|
||||
%include ../shared/UITour.inc.css
|
||||
|
||||
#UITourTooltipButtons {
|
||||
margin: 24px -4px -4px;
|
||||
/**
|
||||
* Override the --panel-arrowcontent-padding so the background extends
|
||||
* to the sides and bottom of the panel.
|
||||
*/
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
|
||||
%include ../shared/contextmenu.inc.css
|
||||
|
@ -240,10 +240,11 @@ browser.jar:
|
||||
skin/classic/browser/panic-panel/header-small@2x.png (../shared/panic-panel/header-small@2x.png)
|
||||
skin/classic/browser/panic-panel/icons.png (../shared/panic-panel/icons.png)
|
||||
skin/classic/browser/panic-panel/icons@2x.png (../shared/panic-panel/icons@2x.png)
|
||||
skin/classic/browser/places/autocomplete-star.png (places/autocomplete-star.png)
|
||||
skin/classic/browser/places/autocomplete-star@2x.png (places/autocomplete-star@2x.png)
|
||||
skin/classic/browser/places/autocomplete-star-XPVista7.png (places/autocomplete-star-XPVista7.png)
|
||||
skin/classic/browser/places/places.css (places/places.css)
|
||||
* skin/classic/browser/places/organizer.css (places/organizer.css)
|
||||
skin/classic/browser/places/bookmark.png (places/bookmark.png)
|
||||
skin/classic/browser/places/bookmark-XP.png (places/bookmark-XP.png)
|
||||
skin/classic/browser/places/query.png (places/query.png)
|
||||
skin/classic/browser/places/query-XP.png (places/query-XP.png)
|
||||
skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
|
||||
@ -603,7 +604,6 @@ browser.jar:
|
||||
% override chrome://browser/skin/downloads/buttons.png chrome://browser/skin/downloads/buttons-XP.png os=WINNT osversion<6
|
||||
% override chrome://browser/skin/feeds/feedIcon.png chrome://browser/skin/feeds/feedIcon-XP.png os=WINNT osversion<6
|
||||
% override chrome://browser/skin/feeds/feedIcon16.png chrome://browser/skin/feeds/feedIcon16-XP.png os=WINNT osversion<6
|
||||
% override chrome://browser/skin/places/bookmark.png chrome://browser/skin/places/bookmark-XP.png os=WINNT osversion<6
|
||||
% override chrome://browser/skin/places/query.png chrome://browser/skin/places/query-XP.png os=WINNT osversion<6
|
||||
% override chrome://browser/skin/places/bookmarksMenu.png chrome://browser/skin/places/bookmarksMenu-XP.png os=WINNT osversion<6
|
||||
% override chrome://browser/skin/places/bookmarksToolbar.png chrome://browser/skin/places/bookmarksToolbar-XP.png os=WINNT osversion<6
|
||||
@ -630,6 +630,7 @@ browser.jar:
|
||||
% override chrome://browser/skin/syncProgress-toolbar.png chrome://browser/skin/syncProgress-toolbar-XPVista7.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/syncProgress-toolbar@2x.png chrome://browser/skin/syncProgress-toolbar-XPVista7@2x.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/toolbarbutton-dropdown-arrow.png chrome://browser/skin/toolbarbutton-dropdown-arrow-XPVista7.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/places/autocomplete-star.png chrome://browser/skin/places/autocomplete-star-XPVista7.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/tabbrowser/newtab.png chrome://browser/skin/tabbrowser/newtab-XPVista7.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/tabbrowser/newtab@2x.png chrome://browser/skin/tabbrowser/newtab-XPVista7@2x.png os=WINNT osversion<=6.1
|
||||
% override chrome://browser/skin/tabbrowser/tab-arrow-left.png chrome://browser/skin/tabbrowser/tab-arrow-left-XPVista7.png os=WINNT osversion<=6.1
|
||||
|
BIN
browser/themes/windows/places/autocomplete-star-XPVista7.png
Normal file
After Width: | Height: | Size: 813 B |
BIN
browser/themes/windows/places/autocomplete-star.png
Normal file
After Width: | Height: | Size: 493 B |
BIN
browser/themes/windows/places/autocomplete-star@2x.png
Normal file
After Width: | Height: | Size: 870 B |
Before Width: | Height: | Size: 993 B |
Before Width: | Height: | Size: 986 B |
@ -265,8 +265,13 @@ pref("browser.search.order.1", "chrome://browser/locale/region.properties");
|
||||
pref("browser.search.order.2", "chrome://browser/locale/region.properties");
|
||||
pref("browser.search.order.3", "chrome://browser/locale/region.properties");
|
||||
|
||||
// Market-specific search defaults (US market only)
|
||||
pref("browser.search.geoSpecificDefaults", true);
|
||||
// Market-specific search defaults
|
||||
// This is disabled globally, and then enabled for individual locales
|
||||
// in firefox-l10n.js (eg. it's enabled for en-US).
|
||||
pref("browser.search.geoSpecificDefaults", false);
|
||||
pref("browser.search.geoSpecificDefaults.url", "");
|
||||
|
||||
// US specific default (used as a fallback if the geoSpecificDefaults request fails).
|
||||
pref("browser.search.defaultenginename.US", "chrome://browser/locale/region.properties");
|
||||
pref("browser.search.order.US.1", "chrome://browser/locale/region.properties");
|
||||
pref("browser.search.order.US.2", "chrome://browser/locale/region.properties");
|
||||
|
@ -2,29 +2,15 @@
|
||||
# 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/.
|
||||
|
||||
ifneq (,$(findstring -march=armv7,$(OS_CFLAGS)))
|
||||
MIN_CPU_VERSION=7
|
||||
else
|
||||
MIN_CPU_VERSION=5
|
||||
endif
|
||||
|
||||
MOZ_APP_BUILDID=$(shell cat $(DEPTH)/config/buildid)
|
||||
|
||||
# See Bug 1137586 for more details on version code computation.
|
||||
ifeq (,$(ANDROID_VERSION_CODE))
|
||||
ifeq ($(CPU_ARCH),arm)
|
||||
# Increment by MIN_SDK_VERSION -- this adds 9 to every build ID as a minimum.
|
||||
# Our split APK starts at 11.
|
||||
ANDROID_VERSION_CODE=$(shell echo $$((`cat $(DEPTH)/config/buildid | cut -c1-10` + $(MOZ_ANDROID_MIN_SDK_VERSION) + 0)))
|
||||
else # not ARM, so x86.
|
||||
# Increment the version code by 3 for x86 builds so they are offered to x86 phones that have ARM emulators,
|
||||
# beating the 2-point advantage that the v11+ ARMv7 APK has.
|
||||
# If we change our splits in the future, we'll need to do this further still.
|
||||
ANDROID_VERSION_CODE=$(shell echo $$((`cat $(DEPTH)/config/buildid | cut -c1-10` + $(MOZ_ANDROID_MIN_SDK_VERSION) + 3)))
|
||||
endif
|
||||
endif
|
||||
|
||||
UA_BUILDID=$(shell echo $(ANDROID_VERSION_CODE) | cut -c1-8)
|
||||
ANDROID_VERSION_CODE:=$(shell $(PYTHON) \
|
||||
$(topsrcdir)/python/mozbuild/mozbuild/android_version_code.py \
|
||||
--verbose \
|
||||
--with-android-cpu-arch=$(ANDROID_CPU_ARCH) \
|
||||
$(if $(MOZ_ANDROID_MIN_SDK_VERSION),--with-android-min-sdk=$(MOZ_ANDROID_MIN_SDK_VERSION)) \
|
||||
$(if $(MOZ_ANDROID_MAX_SDK_VERSION),--with-android-max-sdk=$(MOZ_ANDROID_MAX_SDK_VERSION)) \
|
||||
$(MOZ_APP_BUILDID))
|
||||
|
||||
DEFINES += \
|
||||
-DANDROID_VERSION_CODE=$(ANDROID_VERSION_CODE) \
|
||||
@ -32,7 +18,6 @@ DEFINES += \
|
||||
-DMOZ_ANDROID_SHARED_ACCOUNT_TYPE="$(MOZ_ANDROID_SHARED_ACCOUNT_TYPE)" \
|
||||
-DMOZ_ANDROID_SHARED_FXACCOUNT_TYPE="$(MOZ_ANDROID_SHARED_FXACCOUNT_TYPE)" \
|
||||
-DMOZ_APP_BUILDID=$(MOZ_APP_BUILDID) \
|
||||
-DUA_BUILDID=$(UA_BUILDID) \
|
||||
$(NULL)
|
||||
|
||||
GARBAGE += \
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
package org.mozilla.gecko.fxa.sync;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Activity;
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
@ -15,6 +16,7 @@ import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient;
|
||||
import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException;
|
||||
import org.mozilla.gecko.background.fxa.profile.FxAccountProfileClient10;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
@ -66,6 +68,14 @@ public class FxAccountProfileService extends IntentService {
|
||||
@Override
|
||||
public void handleFailure(FxAccountAbstractClientException.FxAccountAbstractClientRemoteException e) {
|
||||
Logger.warn(LOG_TAG, "Failed to fetch Account profile.", e);
|
||||
|
||||
if (e.isInvalidAuthentication()) {
|
||||
// The profile server rejected the cached oauth token! Invalidate it.
|
||||
// A new token will be generated upon next request.
|
||||
Logger.info(LOG_TAG, "Invalidating oauth token after 401!");
|
||||
AccountManager.get(FxAccountProfileService.this).invalidateAuthToken(FxAccountConstants.ACCOUNT_TYPE, authToken);
|
||||
}
|
||||
|
||||
sendResult("Failed to fetch Account profile.", resultReceiver, Activity.RESULT_CANCELED);
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
||||
|
||||
<org.mozilla.gecko.toolbar.ToolbarEditLayout android:id="@+id/edit_layout"
|
||||
style="@style/UrlBar.Button"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="12dp"
|
||||
android:visibility="gone"
|
||||
android:orientation="horizontal"
|
||||
|
@ -8,11 +8,13 @@ package org.mozilla.gecko.toolbar;
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.CustomEditText;
|
||||
import org.mozilla.gecko.InputMethods;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
|
||||
import org.mozilla.gecko.util.GamepadUtils;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
@ -88,12 +90,19 @@ public class ToolbarEditText extends CustomEditText
|
||||
setOnKeyPreImeListener(new KeyPreImeListener());
|
||||
setOnSelectionChangedListener(new SelectionChangeListener());
|
||||
addTextChangedListener(new TextChangeListener());
|
||||
// Set an inactive search icon on tablet devices when in editing mode
|
||||
updateSearchIcon(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
|
||||
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
|
||||
|
||||
// Make search icon inactive when edit toolbar search term isn't a user entered
|
||||
// search term
|
||||
final boolean isActive = !TextUtils.isEmpty(getText());
|
||||
updateSearchIcon(isActive);
|
||||
|
||||
if (gainFocus) {
|
||||
resetAutocompleteState();
|
||||
return;
|
||||
@ -138,6 +147,26 @@ public class ToolbarEditText extends CustomEditText
|
||||
mPrefs = prefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the search icon at the left of the edittext based
|
||||
* on its state.
|
||||
*
|
||||
* @param isActive The state of the edittext. Active is when the initialized
|
||||
* text has changed and is not empty.
|
||||
*/
|
||||
void updateSearchIcon(boolean isActive) {
|
||||
if (!HardwareUtils.isTablet()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When on tablet show a magnifying glass in editing mode
|
||||
if (isActive) {
|
||||
setCompoundDrawablesWithIntrinsicBounds(R.drawable.search_icon_active, 0, 0, 0);
|
||||
} else {
|
||||
setCompoundDrawablesWithIntrinsicBounds(R.drawable.search_icon_inactive, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the start of autocomplete changes so our text change
|
||||
* listener does not react to changes in autocomplete text
|
||||
@ -514,6 +543,9 @@ public class ToolbarEditText extends CustomEditText
|
||||
removeAutocomplete(editable);
|
||||
}
|
||||
|
||||
// Update search icon with an active state since user is typing
|
||||
updateSearchIcon(textLength > 0);
|
||||
|
||||
if (mFilterListener != null) {
|
||||
mFilterListener.onFilter(text, doAutocomplete ? ToolbarEditText.this : null);
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ const FILTER_DELAY = 500;
|
||||
let Logins = {
|
||||
_logins: [],
|
||||
_filterTimer: null,
|
||||
_selectedLogin: null,
|
||||
|
||||
_getLogins: function() {
|
||||
let logins;
|
||||
@ -60,6 +61,8 @@ let Logins = {
|
||||
window.addEventListener("popstate", this , false);
|
||||
|
||||
Services.obs.addObserver(this, "passwordmgr-storage-changed", false);
|
||||
document.getElementById("save-btn").addEventListener("click", this._onSaveEditLogin.bind(this), false);
|
||||
document.getElementById("password-btn").addEventListener("click", this._onPasswordBtn.bind(this), false);
|
||||
|
||||
this._loadList(this._getLogins());
|
||||
|
||||
@ -101,6 +104,8 @@ let Logins = {
|
||||
}, false);
|
||||
|
||||
this._showList();
|
||||
|
||||
this._updatePasswordBtn(true);
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
@ -121,8 +126,130 @@ let Logins = {
|
||||
},
|
||||
|
||||
_showList: function () {
|
||||
let list = document.getElementById("logins-list");
|
||||
list.removeAttribute("hidden");
|
||||
let loginsListPage = document.getElementById("logins-list-page");
|
||||
loginsListPage.classList.remove("hidden");
|
||||
|
||||
let editLoginPage = document.getElementById("edit-login-page");
|
||||
editLoginPage.classList.add("hidden");
|
||||
|
||||
// If the Show/Hide password button has been flipped, reset it
|
||||
if (this._isPasswordBtnInHideMode()) {
|
||||
this._updatePasswordBtn(true);
|
||||
}
|
||||
},
|
||||
|
||||
_onPopState: function (event) {
|
||||
// Called when back/forward is used to change the state of the page
|
||||
if (event.state) {
|
||||
this._showEditLoginDialog(event.state.id);
|
||||
} else {
|
||||
this._selectedLogin = null;
|
||||
this._showList();
|
||||
}
|
||||
},
|
||||
_showEditLoginDialog: function (login) {
|
||||
let listPage = document.getElementById("logins-list-page");
|
||||
listPage.classList.add("hidden");
|
||||
|
||||
let editLoginPage = document.getElementById("edit-login-page");
|
||||
editLoginPage.classList.remove("hidden");
|
||||
|
||||
let usernameField = document.getElementById("username");
|
||||
usernameField.value = login.username;
|
||||
let passwordField = document.getElementById("password");
|
||||
passwordField.value = login.password;
|
||||
let domainField = document.getElementById("hostname");
|
||||
domainField.value = login.hostname;
|
||||
|
||||
let img = document.getElementById("favicon");
|
||||
this._loadFavicon(img, login.hostname);
|
||||
|
||||
let headerText = document.getElementById("edit-login-header-text");
|
||||
if (login.hostname && (login.hostname != "")) {
|
||||
headerText.textContent = login.hostname;
|
||||
}
|
||||
else {
|
||||
headerText.textContent = gStringBundle.GetStringFromName("editLogin.fallbackTitle");
|
||||
}
|
||||
},
|
||||
|
||||
_onSaveEditLogin: function() {
|
||||
let newUsername = document.getElementById("username").value;
|
||||
let newPassword = document.getElementById("password").value;
|
||||
let newDomain = document.getElementById("hostname").value;
|
||||
let origUsername = this._selectedLogin.username;
|
||||
let origPassword = this._selectedLogin.password;
|
||||
let origDomain = this._selectedLogin.hostname;
|
||||
|
||||
try {
|
||||
if ((newUsername === origUsername) &&
|
||||
(newPassword === origPassword) &&
|
||||
(newDomain === origDomain) ) {
|
||||
gChromeWin.NativeWindow.toast.show(gStringBundle.GetStringFromName("editLogin.saved"), "short");
|
||||
this._showList();
|
||||
return;
|
||||
}
|
||||
|
||||
let logins = Services.logins.findLogins({}, origDomain, origDomain, null);
|
||||
|
||||
for (let i = 0; i < logins.length; i++) {
|
||||
if (logins[i].username == origUsername) {
|
||||
let clone = logins[i].clone();
|
||||
clone.username = newUsername;
|
||||
clone.password = newPassword;
|
||||
clone.hostname = newDomain;
|
||||
Services.logins.removeLogin(logins[i]);
|
||||
Services.logins.addLogin(clone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
gChromeWin.NativeWindow.toast.show(gStringBundle.GetStringFromName("editLogin.couldNotSave"), "short");
|
||||
return;
|
||||
}
|
||||
gChromeWin.NativeWindow.toast.show(gStringBundle.GetStringFromName("editLogin.saved"), "short");
|
||||
this._showList();
|
||||
},
|
||||
|
||||
_onPasswordBtn: function () {
|
||||
this._updatePasswordBtn(this._isPasswordBtnInHideMode());
|
||||
},
|
||||
|
||||
_updatePasswordBtn: function (aShouldShow) {
|
||||
let passwordField = document.getElementById("password");
|
||||
let button = document.getElementById("password-btn");
|
||||
let show = gStringBundle.GetStringFromName("password-btn.show");
|
||||
let hide = gStringBundle.GetStringFromName("password-btn.hide");
|
||||
if (aShouldShow) {
|
||||
passwordField.type = "password";
|
||||
button.textContent = show;
|
||||
button.classList.remove("password-btn-hide");
|
||||
} else {
|
||||
passwordField.type = "text";
|
||||
button.textContent= hide;
|
||||
button.classList.add("password-btn-hide");
|
||||
}
|
||||
},
|
||||
|
||||
_isPasswordBtnInHideMode: function () {
|
||||
let button = document.getElementById("password-btn");
|
||||
return button.classList.contains("password-btn-hide");
|
||||
},
|
||||
|
||||
_showPassword: function(password) {
|
||||
let passwordPrompt = new Prompt({
|
||||
window: window,
|
||||
message: password,
|
||||
buttons: [
|
||||
gStringBundle.GetStringFromName("loginsDialog.copy"),
|
||||
gStringBundle.GetStringFromName("loginsDialog.cancel") ]
|
||||
}).show((data) => {
|
||||
switch (data.button) {
|
||||
case 0:
|
||||
// Corresponds to "Copy password" button.
|
||||
copyStringAndToast(password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied"));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_onLoginClick: function (event) {
|
||||
@ -140,6 +267,7 @@ let Logins = {
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.showPassword") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.copyPassword") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.copyUsername") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.editLogin") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.delete") }
|
||||
];
|
||||
|
||||
@ -148,19 +276,7 @@ let Logins = {
|
||||
// Switch on indices of buttons, as they were added when creating login item.
|
||||
switch (data.button) {
|
||||
case 0:
|
||||
let passwordPrompt = new Prompt({
|
||||
window: window,
|
||||
message: login.password,
|
||||
buttons: [
|
||||
gStringBundle.GetStringFromName("loginsDialog.copy"),
|
||||
gStringBundle.GetStringFromName("loginsDialog.cancel") ]
|
||||
}).show((data) => {
|
||||
switch (data.button) {
|
||||
case 0:
|
||||
// Corresponds to "Copy password" button.
|
||||
copyStringAndToast(login.password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied"));
|
||||
}
|
||||
});
|
||||
this._showPassword(login.password);
|
||||
break;
|
||||
case 1:
|
||||
copyStringAndToast(login.password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied"));
|
||||
@ -169,6 +285,11 @@ let Logins = {
|
||||
copyStringAndToast(login.username, gStringBundle.GetStringFromName("loginsDetails.usernameCopied"));
|
||||
break;
|
||||
case 3:
|
||||
this._selectedLogin = login;
|
||||
this._showEditLoginDialog(login);
|
||||
history.pushState({ id: login.guid }, document.title);
|
||||
break;
|
||||
case 4:
|
||||
let confirmPrompt = new Prompt({
|
||||
window: window,
|
||||
message: gStringBundle.GetStringFromName("loginsDialog.confirmDelete"),
|
||||
@ -187,6 +308,20 @@ let Logins = {
|
||||
});
|
||||
},
|
||||
|
||||
_loadFavicon: function (aImg, aHostname) {
|
||||
// Load favicon from cache.
|
||||
Messaging.sendRequestForResult({
|
||||
type: "Favicon:CacheLoad",
|
||||
url: aHostname,
|
||||
}).then(function(faviconUrl) {
|
||||
aImg.style.backgroundImage= "url('" + faviconUrl + "')";
|
||||
aImg.style.visibility = "visible";
|
||||
}, function(data) {
|
||||
debug("Favicon cache failure : " + data);
|
||||
aImg.style.visibility = "visible";
|
||||
});
|
||||
},
|
||||
|
||||
_createItemForLogin: function (login) {
|
||||
let loginItem = document.createElement("div");
|
||||
|
||||
@ -199,17 +334,7 @@ let Logins = {
|
||||
let img = document.createElement("div");
|
||||
img.className = "icon";
|
||||
|
||||
// Load favicon from cache.
|
||||
Messaging.sendRequestForResult({
|
||||
type: "Favicon:CacheLoad",
|
||||
url: login.hostname,
|
||||
}).then(function(faviconUrl) {
|
||||
img.style.backgroundImage= "url('" + faviconUrl + "')";
|
||||
img.style.visibility = "visible";
|
||||
}, function(data) {
|
||||
debug("Favicon cache failure : " + data);
|
||||
img.style.visibility = "visible";
|
||||
});
|
||||
this._loadFavicon(img, login.hostname);
|
||||
loginItem.appendChild(img);
|
||||
|
||||
// Create item details.
|
||||
|
@ -21,17 +21,42 @@
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/aboutLogins.js"></script>
|
||||
</head>
|
||||
<body dir="&locale.dir;">
|
||||
<div id="logins-header" class="header">
|
||||
<div>&aboutLogins.title;</div>
|
||||
<ul class="toolbar-buttons">
|
||||
<li id="filter-button"></li>
|
||||
</ul>
|
||||
|
||||
<div id="logins-list-page">
|
||||
<div id="logins-header" class="header">
|
||||
<div>&aboutLogins.title;</div>
|
||||
<ul class="toolbar-buttons">
|
||||
<li id="filter-button"></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="logins-list-nonempty-body">
|
||||
<div id="logins-list" class="list"/>
|
||||
<div id="filter-input-container" hidden="true">
|
||||
<input id="filter-input" type="search"/>
|
||||
<div id="filter-clear"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="logins-list" class="list" hidden="true">
|
||||
</div>
|
||||
<div id="filter-input-container" hidden="true">
|
||||
<input id="filter-input" type="search"/>
|
||||
<div id="filter-clear"></div>
|
||||
|
||||
<div id="edit-login-page" class="hidden">
|
||||
<div id="edit-login-header" class="header">
|
||||
<div id="edit-login-header-text"/>
|
||||
</div>
|
||||
<div class="edit-login-div">
|
||||
<div id="favicon" class="edit-login-icon"/>
|
||||
<input type="text" name="hostname" id="hostname" class="edit-login-input"/>
|
||||
</div>
|
||||
<div class="edit-login-div">
|
||||
<input type="text" name="username" id="username" class="edit-login-input"/>
|
||||
</div>
|
||||
<div class="edit-login-div">
|
||||
<input type="password" id="password" name="password" value="password" class="edit-login-input" />
|
||||
<button id="password-btn"></button>
|
||||
</div>
|
||||
<div class="edit-login-div">
|
||||
<button id="save-btn">&aboutLogins.save;</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,3 +3,4 @@
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<!ENTITY aboutLogins.title "Logins">
|
||||
<!ENTITY aboutLogins.save "Save">
|
||||
|
@ -5,6 +5,7 @@
|
||||
loginsMenu.showPassword=Show password
|
||||
loginsMenu.copyPassword=Copy password
|
||||
loginsMenu.copyUsername=Copy username
|
||||
loginsMenu.editLogin=Edit login
|
||||
loginsMenu.delete=Delete
|
||||
|
||||
loginsDialog.confirmDelete=Delete this login?
|
||||
@ -12,8 +13,15 @@ loginsDialog.copy=Copy
|
||||
loginsDialog.confirm=OK
|
||||
loginsDialog.cancel=Cancel
|
||||
|
||||
editLogin.fallbackTitle=Edit Login
|
||||
editLogin.saved=Saved your changes
|
||||
editLogin.couldNotSave=Changes could not be saved
|
||||
|
||||
loginsDetails.age=Age: %S days
|
||||
|
||||
loginsDetails.copyFailed=Copy failed
|
||||
loginsDetails.passwordCopied=Password copied
|
||||
loginsDetails.usernameCopied=Username copied
|
||||
|
||||
password-btn.show=Show
|
||||
password-btn.hide=Hide
|
||||
|
@ -4,4 +4,8 @@
|
||||
|
||||
#filter substitution
|
||||
|
||||
# LOCALIZATION NOTE: this preference is set to true for en-US specifically,
|
||||
# locales without this line have the setting set to false by default.
|
||||
pref("browser.search.geoSpecificDefaults", true);
|
||||
|
||||
pref("general.useragent.locale", "@AB_CD@");
|
||||
|
@ -5,6 +5,14 @@
|
||||
%filter substitution
|
||||
%include defines.inc
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
@ -75,6 +83,70 @@
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.edit-login-icon {
|
||||
background-image: url("resource://android/res/drawable-hdpi-v4/favicon_globe.png");
|
||||
background-position: center;
|
||||
background-size: 32px 32px;
|
||||
background-repeat: no-repeat;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#edit-login-page {
|
||||
background-color: #FFFFFF;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
#save-btn {
|
||||
flex: 1;
|
||||
height: 60px;
|
||||
background-color: #E66000; /*matched to action_orange in java codebase*/
|
||||
color: #FFFFFF;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
border-width: 0px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.password-btn-hide {
|
||||
background-color: #777777;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.edit-login-input {
|
||||
flex: 1;
|
||||
height: 36px;
|
||||
font-size: 15px;
|
||||
color: #222222;
|
||||
border-radius: 4px;
|
||||
border: solid 1px #AFB1B3;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.edit-login-div {
|
||||
margin: 20px 30px;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#password-btn {
|
||||
border-radius: 4px;
|
||||
border-top-left-radius: 0em 0em;
|
||||
border-bottom-left-radius: 0em 0em;
|
||||
font-size: 15px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#password {
|
||||
margin: none;
|
||||
border-top-right-radius: 0em 0em;
|
||||
border-bottom-right-radius: 0em 0em;
|
||||
}
|
||||
|
||||
.icon {
|
||||
background-image: url("resource://android/res/drawable-mdpi-v4/favicon_globe.png");
|
||||
background-position: center;
|
||||
|
57
python/mozbuild/mozbuild/android_version_code.py
Normal file
@ -0,0 +1,57 @@
|
||||
# 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/.
|
||||
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
def android_version_code(buildid, cpu_arch=None, min_sdk=0, max_sdk=0):
|
||||
base = int(str(buildid)[:10])
|
||||
# None is interpreted as arm.
|
||||
if not cpu_arch or cpu_arch in ['armeabi', 'armeabi-v7a']:
|
||||
# Increment by MIN_SDK_VERSION -- this adds 9 to every build ID as a
|
||||
# minimum. Our split APK starts at 11.
|
||||
return base + min_sdk + 0
|
||||
elif cpu_arch in ['x86']:
|
||||
# Increment the version code by 3 for x86 builds so they are offered to
|
||||
# x86 phones that have ARM emulators, beating the 2-point advantage that
|
||||
# the v11+ ARMv7 APK has. If we change our splits in the future, we'll
|
||||
# need to do this further still.
|
||||
return base + min_sdk + 3
|
||||
else:
|
||||
raise ValueError("Don't know how to compute android:versionCode "
|
||||
"for CPU arch " + cpu_arch)
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser('Generate an android:versionCode',
|
||||
add_help=False)
|
||||
parser.add_argument('--verbose', action='store_true',
|
||||
default=False,
|
||||
help='Be verbose')
|
||||
parser.add_argument('--with-android-cpu-arch', dest='cpu_arch',
|
||||
choices=['armeabi', 'armeabi-v7a', 'mips', 'x86'],
|
||||
help='The target CPU architecture')
|
||||
parser.add_argument('--with-android-min-sdk-version', dest='min_sdk',
|
||||
type=int, default=0,
|
||||
help='The minimum target SDK')
|
||||
parser.add_argument('--with-android-max-sdk-version', dest='max_sdk',
|
||||
type=int, default=0,
|
||||
help='The maximum target SDK')
|
||||
parser.add_argument('buildid', type=int,
|
||||
help='The input build ID')
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
code = android_version_code(args.buildid,
|
||||
cpu_arch=args.cpu_arch,
|
||||
min_sdk=args.min_sdk,
|
||||
max_sdk=args.max_sdk)
|
||||
print(code)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
23
python/mozbuild/mozbuild/test/test_android_version_code.py
Normal file
@ -0,0 +1,23 @@
|
||||
# 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/.
|
||||
|
||||
from mozunit import main
|
||||
import unittest
|
||||
|
||||
from mozbuild.android_version_code import android_version_code
|
||||
|
||||
class TestAndroidVersionCode(unittest.TestCase):
|
||||
def test_basic(self):
|
||||
# From https://treeherder.mozilla.org/#/jobs?repo=mozilla-central&revision=e25de9972a77.
|
||||
buildid = '20150708104620'
|
||||
arm_api9 = 2015070819
|
||||
arm_api11 = 2015070821
|
||||
x86_api9 = 2015070822
|
||||
self.assertEqual(android_version_code(buildid, cpu_arch='armeabi', min_sdk=9, max_sdk=None), arm_api9)
|
||||
self.assertEqual(android_version_code(buildid, cpu_arch='armeabi-v7a', min_sdk=11, max_sdk=None), arm_api11)
|
||||
self.assertEqual(android_version_code(buildid, cpu_arch='x86', min_sdk=9, max_sdk=None), x86_api9)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -290,6 +290,8 @@ user_pref("browser.uitour.url", "http://%(server)s/uitour-dummy/tour");
|
||||
// side-effect of preventing our geoip lookup.
|
||||
user_pref("browser.search.isUS", true);
|
||||
user_pref("browser.search.countryCode", "US");
|
||||
// This will prevent HTTP requests for region defaults.
|
||||
user_pref("browser.search.geoSpecificDefaults", false);
|
||||
|
||||
// Make sure the self support tab doesn't hit the network.
|
||||
user_pref("browser.selfsupport.url", "https://%(server)s/selfsupport-dummy/");
|
||||
@ -319,7 +321,6 @@ user_pref("network.proxy.pac_generator", false);
|
||||
// Make tests run consistently on DevEdition (which has a lightweight theme
|
||||
// selected by default).
|
||||
user_pref("lightweightThemes.selectedThemeID", "");
|
||||
user_pref("browser.devedition.theme.enabled", false);
|
||||
|
||||
// Disable periodic updates of service workers.
|
||||
user_pref("dom.serviceWorkers.periodic-updates.enabled", false);
|
||||
|
@ -5,7 +5,7 @@
|
||||
},
|
||||
"global": {
|
||||
"talos_repo": "https://hg.mozilla.org/build/talos",
|
||||
"talos_revision": "554aa164ba0b"
|
||||
"talos_revision": "deac3ce69268"
|
||||
},
|
||||
"extra_options": {
|
||||
"android": [ "--apkPath=%(apk_path)s" ]
|
||||
|
@ -16,6 +16,8 @@ const LoginInfo =
|
||||
Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
|
||||
"nsILoginInfo", "init");
|
||||
|
||||
const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";
|
||||
|
||||
/**
|
||||
* Constants for password prompt telemetry.
|
||||
* Mirrored in mobile/android/components/LoginManagerPrompter.js */
|
||||
@ -775,22 +777,27 @@ LoginManagerPrompter.prototype = {
|
||||
let { browser } = this._getNotifyWindow();
|
||||
|
||||
let saveMsgNames = {
|
||||
prompt: "rememberPasswordMsgNoUsername",
|
||||
buttonLabel: "notifyBarRememberPasswordButtonText",
|
||||
buttonAccessKey: "notifyBarRememberPasswordButtonAccessKey",
|
||||
prompt: login.username === "" ? "rememberLoginMsgNoUser"
|
||||
: "rememberLoginMsg",
|
||||
buttonLabel: "rememberLoginButtonText",
|
||||
buttonAccessKey: "rememberLoginButtonAccessKey",
|
||||
};
|
||||
|
||||
let changeMsgNames = {
|
||||
// We reuse the existing message, even if it expects a username, until we
|
||||
// switch to the final terminology in bug 1144856.
|
||||
prompt: "updatePasswordMsg",
|
||||
buttonLabel: "notifyBarUpdateButtonText",
|
||||
buttonAccessKey: "notifyBarUpdateButtonAccessKey",
|
||||
prompt: login.username === "" ? "updateLoginMsgNoUser"
|
||||
: "updateLoginMsg",
|
||||
buttonLabel: "updateLoginButtonText",
|
||||
buttonAccessKey: "updateLoginButtonAccessKey",
|
||||
};
|
||||
|
||||
let initialMsgNames = type == "password-save" ? saveMsgNames
|
||||
: changeMsgNames;
|
||||
|
||||
let brandBundle = Services.strings.createBundle(BRAND_BUNDLE);
|
||||
let brandShortName = brandBundle.GetStringFromName("brandShortName");
|
||||
let promptMsg = type == "password-save" ? this._getLocalizedString(saveMsgNames.prompt, [brandShortName])
|
||||
: this._getLocalizedString(changeMsgNames.prompt);
|
||||
|
||||
let histogramName = type == "password-save" ? "PWMGR_PROMPT_REMEMBER_ACTION"
|
||||
: "PWMGR_PROMPT_UPDATE_ACTION";
|
||||
let histogram = Services.telemetry.getHistogramById(histogramName);
|
||||
@ -960,7 +967,7 @@ LoginManagerPrompter.prototype = {
|
||||
this._getPopupNote().show(
|
||||
browser,
|
||||
"password",
|
||||
this._getLocalizedString(initialMsgNames.prompt, [displayHost]),
|
||||
promptMsg,
|
||||
"password-notification-icon",
|
||||
mainAction,
|
||||
secondaryActions,
|
||||
|
@ -21,6 +21,10 @@ Login Manager test: notifications
|
||||
|
||||
/** Test for Login Manager: notifications. **/
|
||||
|
||||
const { Services } = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";
|
||||
|
||||
// Set testpath to the directory where we live. Used to load tests from
|
||||
// alternate Mochitest servers (different hostnames, same content).
|
||||
var testpath = document.location.pathname + "/../";
|
||||
@ -95,6 +99,8 @@ function checkTest() {
|
||||
var gotUser = SpecialPowers.wrap(iframe).contentDocument.getElementById("user").textContent;
|
||||
var gotPass = SpecialPowers.wrap(iframe).contentDocument.getElementById("pass").textContent;
|
||||
|
||||
let brandBundle = Services.strings.createBundle(BRAND_BUNDLE);
|
||||
let brandShortName = brandBundle.GetStringFromName("brandShortName");
|
||||
|
||||
switch(testNum) {
|
||||
|
||||
@ -356,8 +362,8 @@ function checkTest() {
|
||||
ok(popup, "got notification popup");
|
||||
// Check the text, which comes from the localized saveLoginText string.
|
||||
notificationText = popup.message;
|
||||
expectedText = /^Would you like to remember the password on example.org\?$/;
|
||||
ok(expectedText.test(notificationText), "Checking text: " + notificationText);
|
||||
expectedText = "Would you like " + brandShortName + " to remember this login?";
|
||||
is(expectedText, notificationText, "Checking text: " + notificationText);
|
||||
popup.remove();
|
||||
break;
|
||||
|
||||
@ -369,8 +375,8 @@ function checkTest() {
|
||||
ok(popup, "got notification popup");
|
||||
// Check the text, which comes from the localized saveLoginText string.
|
||||
notificationText = popup.message;
|
||||
expectedText = /^Would you like to remember the password on example.org\?$/;
|
||||
ok(expectedText.test(notificationText), "Checking text: " + notificationText);
|
||||
expectedText = "Would you like " + brandShortName + " to remember this login\?";
|
||||
is(expectedText, notificationText, "Checking text: " + notificationText);
|
||||
popup.remove();
|
||||
break;
|
||||
|
||||
@ -382,8 +388,8 @@ function checkTest() {
|
||||
ok(popup, "got notification popup");
|
||||
// Check the text, which comes from the localized saveLoginTextNoUser string.
|
||||
notificationText = popup.message;
|
||||
expectedText = /^Would you like to remember the password on example.org\?$/;
|
||||
ok(expectedText.test(notificationText), "Checking text: " + notificationText);
|
||||
expectedText = "Would you like " + brandShortName + " to remember this password\?";
|
||||
is(expectedText, notificationText, "Checking text: " + notificationText);
|
||||
popup.remove();
|
||||
break;
|
||||
|
||||
|
@ -9,6 +9,7 @@ function run_test() {
|
||||
// desired side-effect of preventing our geoip lookup.
|
||||
Services.prefs.setBoolPref("browser.search.isUS", true);
|
||||
Services.prefs.setCharPref("browser.search.countryCode", "US");
|
||||
Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
|
@ -210,6 +210,11 @@ var OS_UNSUPPORTED_PARAMS = [
|
||||
// specifies an updateURL, but not an updateInterval.
|
||||
const SEARCH_DEFAULT_UPDATE_INTERVAL = 7;
|
||||
|
||||
// The default interval before checking again for the name of the
|
||||
// default engine for the region, in seconds. Only used if the response
|
||||
// from the server doesn't specify an interval.
|
||||
const SEARCH_GEO_DEFAULT_UPDATE_INTERVAL = 2592000; // 30 days.
|
||||
|
||||
// Returns false for whitespace-only or commented out lines in a
|
||||
// Sherlock file, true otherwise.
|
||||
function isUsefulLine(aLine) {
|
||||
@ -408,20 +413,21 @@ loadListener.prototype = {
|
||||
onStatus: function (aRequest, aContext, aStatus, aStatusArg) {}
|
||||
}
|
||||
|
||||
// Method to determine if we should be using geo-specific defaults
|
||||
function geoSpecificDefaultsEnabled() {
|
||||
// check to see if this is a partner build. Partner builds should not use geo-specific defaults.
|
||||
let distroID;
|
||||
function isPartnerBuild() {
|
||||
try {
|
||||
distroID = Services.prefs.getCharPref("distribution.id");
|
||||
let distroID = Services.prefs.getCharPref("distribution.id");
|
||||
|
||||
// Mozilla-provided builds (i.e. funnelcake) are not partner builds
|
||||
if (distroID && !distroID.startsWith("mozilla")) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// if we make it here, the pref should dictate behaviour
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method to determine if we should be using geo-specific defaults
|
||||
function geoSpecificDefaultsEnabled() {
|
||||
let geoSpecificDefaults = false;
|
||||
try {
|
||||
geoSpecificDefaults = Services.prefs.getBoolPref("browser.search.geoSpecificDefaults");
|
||||
@ -500,7 +506,7 @@ function getIsUS() {
|
||||
|
||||
// Helper method to modify preference keys with geo-specific modifiers, if needed.
|
||||
function getGeoSpecificPrefName(basepref) {
|
||||
if (!geoSpecificDefaultsEnabled())
|
||||
if (!geoSpecificDefaultsEnabled() || isPartnerBuild())
|
||||
return basepref;
|
||||
if (getIsUS())
|
||||
return basepref + ".US";
|
||||
@ -532,16 +538,53 @@ function isUSTimezone() {
|
||||
// If it fails we don't touch that pref so isUS() does its normal thing.
|
||||
let ensureKnownCountryCode = Task.async(function* () {
|
||||
// If we have a country-code already stored in our prefs we trust it.
|
||||
let countryCode;
|
||||
try {
|
||||
Services.prefs.getCharPref("browser.search.countryCode");
|
||||
return; // pref exists, so we've done this before.
|
||||
countryCode = Services.prefs.getCharPref("browser.search.countryCode");
|
||||
} catch(e) {}
|
||||
// We don't have it cached, so fetch it. fetchCountryCode() will call
|
||||
// storeCountryCode if it gets a result (even if that happens after the
|
||||
// promise resolves)
|
||||
yield fetchCountryCode();
|
||||
|
||||
if (!countryCode) {
|
||||
// We don't have it cached, so fetch it. fetchCountryCode() will call
|
||||
// storeCountryCode if it gets a result (even if that happens after the
|
||||
// promise resolves) and fetchRegionDefault.
|
||||
yield fetchCountryCode();
|
||||
} else {
|
||||
// if nothing to do, return early.
|
||||
if (!geoSpecificDefaultsEnabled())
|
||||
return;
|
||||
|
||||
let expir = engineMetadataService.getGlobalAttr("searchDefaultExpir") || 0;
|
||||
if (expir > Date.now()) {
|
||||
// The territory default we have already fetched hasn't expired yet.
|
||||
// If we have an engine saved, the hash should be valid, verify it now.
|
||||
let defaultEngine = engineMetadataService.getGlobalAttr("searchDefault");
|
||||
if (!defaultEngine ||
|
||||
engineMetadataService.getGlobalAttr("searchDefaultHash") == getVerificationHash(defaultEngine)) {
|
||||
// No geo default, or valid hash; nothing to do.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
yield new Promise(resolve => {
|
||||
let timeoutMS = Services.prefs.getIntPref("browser.search.geoip.timeout");
|
||||
let timerId = setTimeout(() => {
|
||||
timerId = null;
|
||||
resolve();
|
||||
}, timeoutMS);
|
||||
|
||||
let callback = () => {
|
||||
clearTimeout(timerId);
|
||||
resolve();
|
||||
};
|
||||
fetchRegionDefault().then(callback).catch(err => {
|
||||
Components.utils.reportError(err);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// If gInitialized is true then the search service was forced to perform
|
||||
// a sync initialization during our XHR - capture this via telemetry.
|
||||
// a sync initialization during our XHRs - capture this via telemetry.
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT").add(gInitialized);
|
||||
});
|
||||
|
||||
@ -628,9 +671,11 @@ function fetchCountryCode() {
|
||||
// large value just incase the request never completes - we don't want the
|
||||
// XHR object to live forever)
|
||||
let timeoutMS = Services.prefs.getIntPref("browser.search.geoip.timeout");
|
||||
let geoipTimeoutPossible = true;
|
||||
let timerId = setTimeout(() => {
|
||||
LOG("_fetchCountryCode: timeout fetching country information");
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(1);
|
||||
if (geoipTimeoutPossible)
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(1);
|
||||
timerId = null;
|
||||
resolve();
|
||||
}, timeoutMS);
|
||||
@ -646,14 +691,29 @@ function fetchCountryCode() {
|
||||
// This notification is just for tests...
|
||||
Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "geoip-lookup-xhr-complete");
|
||||
|
||||
// If we've already timed out then we've already resolved the promise,
|
||||
// so there's nothing else to do.
|
||||
if (timerId == null) {
|
||||
return;
|
||||
if (timerId) {
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(0);
|
||||
geoipTimeoutPossible = false;
|
||||
}
|
||||
|
||||
let callback = () => {
|
||||
// If we've already timed out then we've already resolved the promise,
|
||||
// so there's nothing else to do.
|
||||
if (timerId == null) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(timerId);
|
||||
resolve();
|
||||
};
|
||||
|
||||
if (result && geoSpecificDefaultsEnabled()) {
|
||||
fetchRegionDefault().then(callback).catch(err => {
|
||||
Components.utils.reportError(err);
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(0);
|
||||
clearTimeout(timerId);
|
||||
resolve();
|
||||
};
|
||||
|
||||
let request = new XMLHttpRequest();
|
||||
@ -683,6 +743,123 @@ function fetchCountryCode() {
|
||||
});
|
||||
}
|
||||
|
||||
// This will make an HTTP request to a Mozilla server that will return
|
||||
// JSON data telling us what engine should be set as the default for
|
||||
// the current region, and how soon we should check again.
|
||||
//
|
||||
// The optional cohort value returned by the server is to be kept locally
|
||||
// and sent to the server the next time we ping it. It lets the server
|
||||
// identify profiles that have been part of a specific experiment.
|
||||
//
|
||||
// This promise may take up to 100s to resolve, it's the caller's
|
||||
// responsibility to ensure with a timer that we are not going to
|
||||
// block the async init for too long.
|
||||
let fetchRegionDefault = () => new Promise(resolve => {
|
||||
let urlTemplate = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
|
||||
.getCharPref("geoSpecificDefaults.url");
|
||||
let endpoint = Services.urlFormatter.formatURL(urlTemplate);
|
||||
|
||||
// As an escape hatch, no endpoint means no region specific defaults.
|
||||
if (!endpoint) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Append the optional cohort value.
|
||||
const cohortPref = "browser.search.cohort";
|
||||
let cohort;
|
||||
try {
|
||||
cohort = Services.prefs.getCharPref(cohortPref);
|
||||
} catch(e) {}
|
||||
if (cohort)
|
||||
endpoint += "/" + cohort;
|
||||
|
||||
LOG("fetchRegionDefault starting with endpoint " + endpoint);
|
||||
|
||||
let startTime = Date.now();
|
||||
let request = new XMLHttpRequest();
|
||||
request.timeout = 100000; // 100 seconds as the last-chance fallback
|
||||
request.onload = function(event) {
|
||||
let took = Date.now() - startTime;
|
||||
|
||||
let status = event.target.status;
|
||||
if (status != 200) {
|
||||
LOG("fetchRegionDefault failed with HTTP code " + status);
|
||||
let retryAfter = request.getResponseHeader("retry-after");
|
||||
if (retryAfter) {
|
||||
engineMetadataService.setGlobalAttr("searchDefaultExpir",
|
||||
Date.now() + retryAfter * 1000);
|
||||
}
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let response = event.target.response || {};
|
||||
LOG("received " + response.toSource());
|
||||
|
||||
if (response.cohort) {
|
||||
Services.prefs.setCharPref(cohortPref, response.cohort);
|
||||
} else {
|
||||
Services.prefs.clearUserPref(cohortPref);
|
||||
}
|
||||
|
||||
if (response.settings && response.settings.searchDefault) {
|
||||
let defaultEngine = response.settings.searchDefault;
|
||||
engineMetadataService.setGlobalAttr("searchDefault", defaultEngine);
|
||||
let hash = getVerificationHash(defaultEngine);
|
||||
LOG("fetchRegionDefault saved searchDefault: " + defaultEngine +
|
||||
" with verification hash: " + hash);
|
||||
engineMetadataService.setGlobalAttr("searchDefaultHash", hash);
|
||||
}
|
||||
|
||||
let interval = response.interval || SEARCH_GEO_DEFAULT_UPDATE_INTERVAL;
|
||||
let milliseconds = interval * 1000; // |interval| is in seconds.
|
||||
engineMetadataService.setGlobalAttr("searchDefaultExpir",
|
||||
Date.now() + milliseconds);
|
||||
|
||||
LOG("fetchRegionDefault got success response in " + took + "ms");
|
||||
resolve();
|
||||
};
|
||||
request.ontimeout = function(event) {
|
||||
LOG("fetchRegionDefault: XHR finally timed-out");
|
||||
resolve();
|
||||
};
|
||||
request.onerror = function(event) {
|
||||
LOG("fetchRegionDefault: failed to retrieve territory default information");
|
||||
resolve();
|
||||
};
|
||||
request.open("GET", endpoint, true);
|
||||
request.setRequestHeader("Content-Type", "application/json");
|
||||
request.responseType = "json";
|
||||
request.send();
|
||||
});
|
||||
|
||||
function getVerificationHash(aName) {
|
||||
let disclaimer = "By modifying this file, I agree that I am doing so " +
|
||||
"only within $appName itself, using official, user-driven search " +
|
||||
"engine selection processes, and in a way which does not circumvent " +
|
||||
"user consent. I acknowledge that any attempt to change this file " +
|
||||
"from outside of $appName is a malicious act, and will be responded " +
|
||||
"to accordingly."
|
||||
|
||||
let salt = OS.Path.basename(OS.Constants.Path.profileDir) + aName +
|
||||
disclaimer.replace(/\$appName/g, Services.appinfo.name);
|
||||
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
|
||||
// Data is an array of bytes.
|
||||
let data = converter.convertToByteArray(salt, {});
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA256);
|
||||
hasher.update(data, data.length);
|
||||
|
||||
return hasher.finish(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to verify a given DOM node's localName and namespaceURI.
|
||||
* @param aElement
|
||||
@ -3368,25 +3545,34 @@ SearchService.prototype = {
|
||||
return this.__sortedEngines;
|
||||
},
|
||||
|
||||
// Get the original Engine object that belongs to the defaultenginename pref
|
||||
// of the default branch.
|
||||
// Get the original Engine object that is the default for this region,
|
||||
// ignoring changes the user may have subsequently made.
|
||||
get _originalDefaultEngine() {
|
||||
let defaultPrefB = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
|
||||
let nsIPLS = Ci.nsIPrefLocalizedString;
|
||||
let defaultEngine;
|
||||
|
||||
let defPref = getGeoSpecificPrefName("defaultenginename");
|
||||
try {
|
||||
defaultEngine = defaultPrefB.getComplexValue(defPref, nsIPLS).data;
|
||||
} catch (ex) {
|
||||
// If the default pref is invalid (e.g. an add-on set it to a bogus value)
|
||||
// getEngineByName will just return null, which is the best we can do.
|
||||
let defaultEngine = engineMetadataService.getGlobalAttr("searchDefault");
|
||||
if (defaultEngine &&
|
||||
engineMetadataService.getGlobalAttr("searchDefaultHash") != getVerificationHash(defaultEngine)) {
|
||||
LOG("get _originalDefaultEngine, invalid searchDefaultHash for: " + defaultEngine);
|
||||
defaultEngine = "";
|
||||
}
|
||||
|
||||
if (!defaultEngine) {
|
||||
let defaultPrefB = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
|
||||
let nsIPLS = Ci.nsIPrefLocalizedString;
|
||||
|
||||
let defPref = getGeoSpecificPrefName("defaultenginename");
|
||||
try {
|
||||
defaultEngine = defaultPrefB.getComplexValue(defPref, nsIPLS).data;
|
||||
} catch (ex) {
|
||||
// If the default pref is invalid (e.g. an add-on set it to a bogus value)
|
||||
// getEngineByName will just return null, which is the best we can do.
|
||||
}
|
||||
}
|
||||
|
||||
return this.getEngineByName(defaultEngine);
|
||||
},
|
||||
|
||||
resetToOriginalDefaultEngine: function SRCH_SVC__resetToOriginalDefaultEngine() {
|
||||
this.defaultEngine = this._originalDefaultEngine;
|
||||
this.currentEngine = this._originalDefaultEngine;
|
||||
},
|
||||
|
||||
_buildCache: function SRCH_SVC__buildCache() {
|
||||
@ -3714,6 +3900,7 @@ SearchService.prototype = {
|
||||
},
|
||||
|
||||
_asyncReInit: function () {
|
||||
LOG("_asyncReInit");
|
||||
// Start by clearing the initialized state, so we don't abort early.
|
||||
gInitialized = false;
|
||||
|
||||
@ -3729,8 +3916,14 @@ SearchService.prototype = {
|
||||
|
||||
Task.spawn(function* () {
|
||||
try {
|
||||
LOG("Restarting engineMetadataService");
|
||||
yield engineMetadataService.init();
|
||||
yield this._asyncLoadEngines();
|
||||
yield ensureKnownCountryCode();
|
||||
|
||||
// Due to the HTTP requests done by ensureKnownCountryCode, it's possible that
|
||||
// at this point a synchronous init has been forced by other code.
|
||||
if (!gInitialized)
|
||||
yield this._asyncLoadEngines();
|
||||
|
||||
// Typically we'll re-init as a result of a pref observer,
|
||||
// so signal to 'callers' that we're done.
|
||||
@ -4295,31 +4488,6 @@ SearchService.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
_getVerificationHash: function SRCH_SVC__getVerificationHash(aName) {
|
||||
let disclaimer = "By modifying this file, I agree that I am doing so " +
|
||||
"only within $appName itself, using official, user-driven search " +
|
||||
"engine selection processes, and in a way which does not circumvent " +
|
||||
"user consent. I acknowledge that any attempt to change this file " +
|
||||
"from outside of $appName is a malicious act, and will be responded " +
|
||||
"to accordingly."
|
||||
|
||||
let salt = OS.Path.basename(OS.Constants.Path.profileDir) + aName +
|
||||
disclaimer.replace(/\$appName/g, Services.appinfo.name);
|
||||
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
|
||||
// Data is an array of bytes.
|
||||
let data = converter.convertToByteArray(salt, {});
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA256);
|
||||
hasher.update(data, data.length);
|
||||
|
||||
return hasher.finish(true);
|
||||
},
|
||||
|
||||
// nsIBrowserSearchService
|
||||
init: function SRCH_SVC_init(observer) {
|
||||
LOG("SearchService.init");
|
||||
@ -4673,7 +4841,7 @@ SearchService.prototype = {
|
||||
this._ensureInitialized();
|
||||
if (!this._currentEngine) {
|
||||
let name = engineMetadataService.getGlobalAttr("current");
|
||||
if (engineMetadataService.getGlobalAttr("hash") == this._getVerificationHash(name)) {
|
||||
if (engineMetadataService.getGlobalAttr("hash") == getVerificationHash(name)) {
|
||||
this._currentEngine = this.getEngineByName(name);
|
||||
}
|
||||
}
|
||||
@ -4713,7 +4881,7 @@ SearchService.prototype = {
|
||||
}
|
||||
|
||||
engineMetadataService.setGlobalAttr("current", newName);
|
||||
engineMetadataService.setGlobalAttr("hash", this._getVerificationHash(newName));
|
||||
engineMetadataService.setGlobalAttr("hash", getVerificationHash(newName));
|
||||
|
||||
notifyAction(this._currentEngine, SEARCH_ENGINE_CURRENT);
|
||||
},
|
||||
|
@ -251,6 +251,22 @@ function isUSTimezone() {
|
||||
return UTCOffset >= 150 && UTCOffset <= 600;
|
||||
}
|
||||
|
||||
const kDefaultenginenamePref = "browser.search.defaultenginename";
|
||||
const kTestEngineName = "Test search engine";
|
||||
const kLocalePref = "general.useragent.locale";
|
||||
|
||||
function getDefaultEngineName(isUS) {
|
||||
const nsIPLS = Ci.nsIPrefLocalizedString;
|
||||
// Copy the logic from nsSearchService
|
||||
let pref = kDefaultenginenamePref;
|
||||
if (isUS === undefined)
|
||||
isUS = Services.prefs.getCharPref(kLocalePref) == "en-US" && isUSTimezone();
|
||||
if (isUS) {
|
||||
pref += ".US";
|
||||
}
|
||||
return Services.prefs.getComplexValue(pref, nsIPLS).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for metadata being committed.
|
||||
* @return {Promise} Resolved when the metadata is committed to disk.
|
||||
@ -317,6 +333,8 @@ if (!isChild) {
|
||||
Services.prefs.setIntPref("browser.search.geoip.timeout", 2000);
|
||||
// But still disable geoip lookups - tests that need it will re-configure this.
|
||||
Services.prefs.setCharPref("browser.search.geoip.url", "");
|
||||
// Also disable region defaults - tests using it will also re-configure it.
|
||||
Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref("geoSpecificDefaults.url", "");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -489,6 +507,24 @@ function waitForSearchNotification(aExpectedData) {
|
||||
});
|
||||
}
|
||||
|
||||
function asyncInit() {
|
||||
return new Promise(resolve => {
|
||||
Services.search.init(function() {
|
||||
do_check_true(Services.search.isInitialized);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function asyncReInit() {
|
||||
let promise = waitForSearchNotification("reinit-complete");
|
||||
|
||||
Services.search.QueryInterface(Ci.nsIObserver)
|
||||
.observe(null, "nsPref:changed", kLocalePref);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
// This "enum" from nsSearchService.js
|
||||
const TELEMETRY_RESULT_ENUM = {
|
||||
SUCCESS: 0,
|
||||
|
284
toolkit/components/search/tests/xpcshell/test_geodefaults.js
Normal file
@ -0,0 +1,284 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var requests = [];
|
||||
var gServerCohort = "";
|
||||
|
||||
const kUrlPref = "geoSpecificDefaults.url";
|
||||
|
||||
const kDayInSeconds = 86400;
|
||||
const kYearInSeconds = kDayInSeconds * 365;
|
||||
|
||||
function run_test() {
|
||||
updateAppInfo();
|
||||
installTestEngine();
|
||||
|
||||
let srv = new HttpServer();
|
||||
|
||||
srv.registerPathHandler("/lookup_defaults", (metadata, response) => {
|
||||
response.setStatusLine("1.1", 200, "OK");
|
||||
let data = {interval: kYearInSeconds,
|
||||
settings: {searchDefault: "Test search engine"}};
|
||||
if (gServerCohort)
|
||||
data.cohort = gServerCohort;
|
||||
response.write(JSON.stringify(data));
|
||||
requests.push(metadata);
|
||||
});
|
||||
|
||||
srv.registerPathHandler("/lookup_fail", (metadata, response) => {
|
||||
response.setStatusLine("1.1", 404, "Not Found");
|
||||
requests.push(metadata);
|
||||
});
|
||||
|
||||
srv.registerPathHandler("/lookup_unavailable", (metadata, response) => {
|
||||
response.setStatusLine("1.1", 503, "Service Unavailable");
|
||||
response.setHeader("Retry-After", kDayInSeconds.toString());
|
||||
requests.push(metadata);
|
||||
});
|
||||
|
||||
srv.start(-1);
|
||||
do_register_cleanup(() => srv.stop(() => {}));
|
||||
|
||||
let url = "http://localhost:" + srv.identity.primaryPort + "/lookup_defaults?";
|
||||
Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, url);
|
||||
// Set a bogus user value so that running the test ensures we ignore it.
|
||||
Services.prefs.setCharPref(BROWSER_SEARCH_PREF + kUrlPref, "about:blank");
|
||||
Services.prefs.setCharPref("browser.search.geoip.url",
|
||||
'data:application/json,{"country_code": "FR"}');
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function checkNoRequest() {
|
||||
do_check_eq(requests.length, 0);
|
||||
}
|
||||
|
||||
function checkRequest(cohort = "") {
|
||||
do_check_eq(requests.length, 1);
|
||||
let req = requests.pop();
|
||||
do_check_eq(req._method, "GET");
|
||||
do_check_eq(req._queryString, cohort ? "/" + cohort : "");
|
||||
}
|
||||
|
||||
function promiseGlobalMetadata() {
|
||||
return new Promise(resolve => Task.spawn(function* () {
|
||||
let path = OS.Path.join(OS.Constants.Path.profileDir, "search-metadata.json");
|
||||
let bytes = yield OS.File.read(path);
|
||||
resolve(JSON.parse(new TextDecoder().decode(bytes))["[global]"]);
|
||||
}));
|
||||
}
|
||||
|
||||
function promiseSaveGlobalMetadata(globalData) {
|
||||
return new Promise(resolve => Task.spawn(function* () {
|
||||
let path = OS.Path.join(OS.Constants.Path.profileDir, "search-metadata.json");
|
||||
let bytes = yield OS.File.read(path);
|
||||
let data = JSON.parse(new TextDecoder().decode(bytes));
|
||||
data["[global]"] = globalData;
|
||||
yield OS.File.writeAtomic(path,
|
||||
new TextEncoder().encode(JSON.stringify(data)));
|
||||
resolve();
|
||||
}));
|
||||
}
|
||||
|
||||
let forceExpiration = Task.async(function* () {
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
|
||||
// Make the current geodefaults expire 1s ago.
|
||||
metadata.searchdefaultexpir = Date.now() - 1000;
|
||||
yield promiseSaveGlobalMetadata(metadata);
|
||||
});
|
||||
|
||||
add_task(function* no_request_if_prefed_off() {
|
||||
// Disable geoSpecificDefaults and check no HTTP request is made.
|
||||
Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
|
||||
yield asyncInit();
|
||||
checkNoRequest();
|
||||
|
||||
// The default engine should be set based on the prefs.
|
||||
do_check_eq(Services.search.currentEngine.name, getDefaultEngineName(false));
|
||||
|
||||
// Change the default engine and then revert to default to ensure
|
||||
// the metadata file exists.
|
||||
let commitPromise = promiseAfterCommit();
|
||||
Services.search.currentEngine = Services.search.getEngineByName(kTestEngineName);
|
||||
Services.search.resetToOriginalDefaultEngine();
|
||||
yield commitPromise;
|
||||
|
||||
// Ensure nothing related to geoSpecificDefaults has been written in the metadata.
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
do_check_eq(typeof metadata.searchdefaultexpir, "undefined");
|
||||
do_check_eq(typeof metadata.searchdefault, "undefined");
|
||||
do_check_eq(typeof metadata.searchdefaulthash, "undefined");
|
||||
|
||||
Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", true);
|
||||
});
|
||||
|
||||
add_task(function* should_get_geo_defaults_only_once() {
|
||||
// (Re)initializing the search service should trigger a request,
|
||||
// and set the default engine based on it.
|
||||
let commitPromise = promiseAfterCommit();
|
||||
// Due to the previous initialization, we expect the countryCode to already be set.
|
||||
do_check_true(Services.prefs.prefHasUserValue("browser.search.countryCode"));
|
||||
do_check_eq(Services.prefs.getCharPref("browser.search.countryCode"), "FR");
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
do_check_eq(Services.search.currentEngine.name, kTestEngineName);
|
||||
yield commitPromise;
|
||||
|
||||
// Verify the metadata was written correctly.
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
do_check_eq(typeof metadata.searchdefaultexpir, "number");
|
||||
do_check_true(metadata.searchdefaultexpir > Date.now());
|
||||
do_check_eq(typeof metadata.searchdefault, "string");
|
||||
do_check_eq(metadata.searchdefault, "Test search engine");
|
||||
do_check_eq(typeof metadata.searchdefaulthash, "string");
|
||||
do_check_eq(metadata.searchdefaulthash.length, 44);
|
||||
|
||||
// The next restart shouldn't trigger a request.
|
||||
yield asyncReInit();
|
||||
checkNoRequest();
|
||||
do_check_eq(Services.search.currentEngine.name, kTestEngineName);
|
||||
});
|
||||
|
||||
add_task(function* should_request_when_countryCode_not_set() {
|
||||
Services.prefs.clearUserPref("browser.search.countryCode");
|
||||
let commitPromise = promiseAfterCommit();
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
yield commitPromise;
|
||||
});
|
||||
|
||||
add_task(function* should_recheck_if_interval_expired() {
|
||||
yield forceExpiration();
|
||||
|
||||
let commitPromise = promiseAfterCommit();
|
||||
let date = Date.now();
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
yield commitPromise;
|
||||
|
||||
// Check that the expiration timestamp has been updated.
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
do_check_eq(typeof metadata.searchdefaultexpir, "number");
|
||||
do_check_true(metadata.searchdefaultexpir >= date + kYearInSeconds * 1000);
|
||||
do_check_true(metadata.searchdefaultexpir < date + (kYearInSeconds + 3600) * 1000);
|
||||
});
|
||||
|
||||
add_task(function* should_recheck_when_broken_hash() {
|
||||
// This test verifies both that we ignore saved geo-defaults if the
|
||||
// hash is invalid, and that we keep the local preferences-based
|
||||
// default for all of the session in case a synchronous
|
||||
// initialization was triggered before our HTTP request completed.
|
||||
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
|
||||
// Break the hash.
|
||||
let hash = metadata.searchdefaulthash;
|
||||
metadata.searchdefaulthash = "broken";
|
||||
yield promiseSaveGlobalMetadata(metadata);
|
||||
|
||||
let commitPromise = promiseAfterCommit();
|
||||
let reInitPromise = asyncReInit();
|
||||
|
||||
// Synchronously check the current default engine, to force a sync init.
|
||||
// The hash is wrong, so we should fallback to the default engine from prefs.
|
||||
// XXX For some reason forcing a sync init while already asynchronously
|
||||
// reinitializing causes a shutdown warning related to engineMetadataService's
|
||||
// finalize method having already been called. Seems harmless for the purpose
|
||||
// of this test.
|
||||
do_check_false(Services.search.isInitialized)
|
||||
do_check_eq(Services.search.currentEngine.name, getDefaultEngineName(false));
|
||||
do_check_true(Services.search.isInitialized)
|
||||
|
||||
yield reInitPromise;
|
||||
checkRequest();
|
||||
yield commitPromise;
|
||||
|
||||
// Check that the hash is back to its previous value.
|
||||
metadata = yield promiseGlobalMetadata();
|
||||
do_check_eq(typeof metadata.searchdefaulthash, "string");
|
||||
do_check_eq(metadata.searchdefaulthash, hash);
|
||||
|
||||
// The current default engine shouldn't change during a session.
|
||||
do_check_eq(Services.search.currentEngine.name, getDefaultEngineName(false));
|
||||
|
||||
// After another restart, the current engine should be back to the geo default,
|
||||
// without doing yet another request.
|
||||
yield asyncReInit();
|
||||
checkNoRequest();
|
||||
do_check_eq(Services.search.currentEngine.name, kTestEngineName);
|
||||
});
|
||||
|
||||
add_task(function* should_remember_cohort_id() {
|
||||
// Check that initially the cohort pref doesn't exist.
|
||||
const cohortPref = "browser.search.cohort";
|
||||
do_check_eq(Services.prefs.getPrefType(cohortPref), Services.prefs.PREF_INVALID);
|
||||
|
||||
// Make the server send a cohort id.
|
||||
let cohort = gServerCohort = "xpcshell";
|
||||
|
||||
// Trigger a new request.
|
||||
yield forceExpiration();
|
||||
let commitPromise = promiseAfterCommit();
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
yield commitPromise;
|
||||
|
||||
// Check that the cohort was saved.
|
||||
do_check_eq(Services.prefs.getPrefType(cohortPref), Services.prefs.PREF_STRING);
|
||||
do_check_eq(Services.prefs.getCharPref(cohortPref), cohort);
|
||||
|
||||
// Make the server stop sending the cohort.
|
||||
gServerCohort = "";
|
||||
|
||||
// Check that the next request sends the previous cohort id, and
|
||||
// will remove it from the prefs due to the server no longer sending it.
|
||||
yield forceExpiration();
|
||||
commitPromise = promiseAfterCommit();
|
||||
yield asyncReInit();
|
||||
checkRequest(cohort);
|
||||
yield commitPromise;
|
||||
do_check_eq(Services.prefs.getPrefType(cohortPref), Services.prefs.PREF_INVALID);
|
||||
});
|
||||
|
||||
add_task(function* should_retry_after_failure() {
|
||||
let defaultBranch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
|
||||
let originalUrl = defaultBranch.getCharPref(kUrlPref);
|
||||
defaultBranch.setCharPref(kUrlPref, originalUrl.replace("defaults", "fail"));
|
||||
|
||||
// Trigger a new request.
|
||||
yield forceExpiration();
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
|
||||
// After another restart, a new request should be triggered automatically without
|
||||
// the test having to call forceExpiration again.
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
});
|
||||
|
||||
add_task(function* should_honor_retry_after_header() {
|
||||
let defaultBranch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
|
||||
let originalUrl = defaultBranch.getCharPref(kUrlPref);
|
||||
defaultBranch.setCharPref(kUrlPref, originalUrl.replace("fail", "unavailable"));
|
||||
|
||||
// Trigger a new request.
|
||||
yield forceExpiration();
|
||||
let date = Date.now();
|
||||
let commitPromise = promiseAfterCommit();
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
yield commitPromise;
|
||||
|
||||
// Check that the expiration timestamp has been updated.
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
do_check_eq(typeof metadata.searchdefaultexpir, "number");
|
||||
do_check_true(metadata.searchdefaultexpir >= date + kDayInSeconds * 1000);
|
||||
do_check_true(metadata.searchdefaultexpir < date + (kDayInSeconds + 3600) * 1000);
|
||||
|
||||
// After another restart, a new request should not be triggered.
|
||||
yield asyncReInit();
|
||||
checkNoRequest();
|
||||
});
|
@ -3,73 +3,11 @@
|
||||
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
const kDefaultenginenamePref = "browser.search.defaultenginename";
|
||||
const kSelectedEnginePref = "browser.search.selectedEngine";
|
||||
|
||||
const kTestEngineName = "Test search engine";
|
||||
|
||||
// These two functions (getLocale and getIsUS) are copied from nsSearchService.js
|
||||
function getLocale() {
|
||||
let LOCALE_PREF = "general.useragent.locale";
|
||||
return Services.prefs.getCharPref(LOCALE_PREF);
|
||||
}
|
||||
|
||||
function getIsUS() {
|
||||
if (getLocale() != "en-US") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Timezone assumptions! We assume that if the system clock's timezone is
|
||||
// between Newfoundland and Hawaii, that the user is in North America.
|
||||
|
||||
// This includes all of South America as well, but we have relatively few
|
||||
// en-US users there, so that's OK.
|
||||
|
||||
// 150 minutes = 2.5 hours (UTC-2.5), which is
|
||||
// Newfoundland Daylight Time (http://www.timeanddate.com/time/zones/ndt)
|
||||
|
||||
// 600 minutes = 10 hours (UTC-10), which is
|
||||
// Hawaii-Aleutian Standard Time (http://www.timeanddate.com/time/zones/hast)
|
||||
|
||||
let UTCOffset = (new Date()).getTimezoneOffset();
|
||||
let isNA = UTCOffset >= 150 && UTCOffset <= 600;
|
||||
|
||||
return isNA;
|
||||
}
|
||||
|
||||
function getDefaultEngineName() {
|
||||
const nsIPLS = Ci.nsIPrefLocalizedString;
|
||||
// Copy the logic from nsSearchService
|
||||
let pref = kDefaultenginenamePref;
|
||||
if (getIsUS()) {
|
||||
pref += ".US";
|
||||
}
|
||||
return Services.prefs.getComplexValue(pref, nsIPLS).data;
|
||||
}
|
||||
|
||||
// waitForSearchNotification is in head_search.js
|
||||
let waitForNotification = waitForSearchNotification;
|
||||
|
||||
function asyncInit() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
Services.search.init(function() {
|
||||
do_check_true(Services.search.isInitialized);
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function asyncReInit() {
|
||||
let promise = waitForNotification("reinit-complete");
|
||||
|
||||
Services.search.QueryInterface(Ci.nsIObserver)
|
||||
.observe(null, "nsPref:changed", "general.useragent.locale");
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Check that the default engine matches the defaultenginename pref
|
||||
add_task(function* test_defaultEngine() {
|
||||
yield asyncInit();
|
||||
@ -138,7 +76,7 @@ add_task(function* test_ignoreInvalidHash() {
|
||||
json["[global]"].hash = "invalid";
|
||||
|
||||
let data = new TextEncoder().encode(JSON.stringify(json));
|
||||
let promise = OS.File.writeAtomic(path, data);//, { tmpPath: path + ".tmp" });
|
||||
yield OS.File.writeAtomic(path, data);//, { tmpPath: path + ".tmp" });
|
||||
|
||||
// Re-init the search service, and check that the json file is ignored.
|
||||
yield asyncReInit();
|
||||
|
@ -75,3 +75,4 @@ support-files =
|
||||
[test_sync_profile_engine.js]
|
||||
[test_rel_searchform.js]
|
||||
[test_selectedEngine.js]
|
||||
[test_geodefaults.js]
|
||||
|
@ -0,0 +1,67 @@
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["UrlClassifierTestUtils"];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
this.UrlClassifierTestUtils = {
|
||||
|
||||
addTestTrackers() {
|
||||
const TABLE_PREF = "urlclassifier.trackingTable";
|
||||
const TABLE_NAME = "test-track-simple";
|
||||
|
||||
// Add some URLs to the tracking database (to be blocked)
|
||||
let testData = "tracking.example.com/";
|
||||
let testUpdate =
|
||||
"n:1000\ni:test-track-simple\nad:1\n" +
|
||||
"a:524:32:" + testData.length + "\n" +
|
||||
testData;
|
||||
|
||||
return this.useTestDatabase(TABLE_PREF, TABLE_NAME, testUpdate);
|
||||
},
|
||||
|
||||
cleanupTestTrackers() {
|
||||
const TABLE_PREF = "urlclassifier.trackingTable";
|
||||
Services.prefs.clearUserPref(TABLE_PREF);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add some entries to a test tracking protection database, and resets
|
||||
* back to the default database after the test ends.
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
useTestDatabase(tablePref, tableName, update) {
|
||||
Services.prefs.setCharPref(tablePref, tableName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"].
|
||||
getService(Ci.nsIUrlClassifierDBService);
|
||||
let listener = {
|
||||
QueryInterface: iid => {
|
||||
if (iid.equals(Ci.nsISupports) ||
|
||||
iid.equals(Ci.nsIUrlClassifierUpdateObserver))
|
||||
return listener;
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
updateUrlRequested: url => { },
|
||||
streamFinished: status => { },
|
||||
updateError: errorCode => {
|
||||
reject("Couldn't update classifier.");
|
||||
},
|
||||
updateSuccess: requestedTimeout => {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
dbService.beginUpdate(listener, tableName, "");
|
||||
dbService.beginStream("", "");
|
||||
dbService.updateStream(update);
|
||||
dbService.finishStream();
|
||||
dbService.finishUpdate();
|
||||
});
|
||||
},
|
||||
};
|
@ -17,21 +17,13 @@
|
||||
var Cc = SpecialPowers.Cc;
|
||||
var Ci = SpecialPowers.Ci;
|
||||
|
||||
Components.utils.import("resource://testing-common/UrlClassifierTestUtils.jsm");
|
||||
|
||||
// Add https://allowlisted.example.com to the permissions manager
|
||||
SpecialPowers.addPermission("trackingprotection",
|
||||
Ci.nsIPermissionManager.ALLOW_ACTION,
|
||||
{ url: "https://allowlisted.example.com" });
|
||||
|
||||
// Add some URLs to the tracking database
|
||||
var testData = "tracking.example.com/";
|
||||
var testUpdate =
|
||||
"n:1000\ni:test-track-simple\nad:1\n" +
|
||||
"a:524:32:" + testData.length + "\n" +
|
||||
testData;
|
||||
|
||||
var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
|
||||
.getService(Ci.nsIUrlClassifierDBService);
|
||||
|
||||
function clearPermissions() {
|
||||
SpecialPowers.removePermission("trackingprotection",
|
||||
{ url: "https://allowlisted.example.com" });
|
||||
@ -40,40 +32,18 @@ function clearPermissions() {
|
||||
{ url: "https://allowlisted.example.com" }));
|
||||
}
|
||||
|
||||
function doUpdate(update) {
|
||||
var listener = {
|
||||
QueryInterface: function(iid)
|
||||
{
|
||||
if (iid.equals(Ci.nsISupports) ||
|
||||
iid.equals(Ci.nsIUrlClassifierUpdateObserver))
|
||||
return this;
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
updateUrlRequested: function(url) { },
|
||||
streamFinished: function(status) { },
|
||||
updateError: function(errorCode) {
|
||||
ok(false, "Couldn't update classifier.");
|
||||
// Abort test.
|
||||
SimpleTest.finish();
|
||||
},
|
||||
updateSuccess: function(requestedTimeout) {
|
||||
document.getElementById("testFrame").src = "allowlistAnnotatedFrame.html";
|
||||
}
|
||||
};
|
||||
|
||||
dbService.beginUpdate(listener, "test-track-simple", "");
|
||||
dbService.beginStream("", "");
|
||||
dbService.updateStream(update);
|
||||
dbService.finishStream();
|
||||
dbService.finishUpdate();
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{"set" : [["urlclassifier.trackingTable", "test-track-simple"],
|
||||
["privacy.trackingprotection.enabled", true],
|
||||
["channelclassifier.allowlist_example", true]]},
|
||||
function() { doUpdate(testUpdate); });
|
||||
test);
|
||||
|
||||
function test() {
|
||||
SimpleTest.registerCleanupFunction(UrlClassifierTestUtils.cleanupTestTrackers);
|
||||
UrlClassifierTestUtils.addTestTrackers().then(() => {
|
||||
document.getElementById("testFrame").src = "allowlistAnnotatedFrame.html";
|
||||
});
|
||||
}
|
||||
|
||||
// Expected finish() call is in "allowlistedAnnotatedFrame.html".
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
@ -17,58 +17,27 @@
|
||||
var Cc = SpecialPowers.Cc;
|
||||
var Ci = SpecialPowers.Ci;
|
||||
|
||||
// Add some URLs to the tracking database
|
||||
var testData = "tracking.example.com/";
|
||||
var testUpdate =
|
||||
"n:1000\ni:test-track-simple\nad:1\n" +
|
||||
"a:524:32:" + testData.length + "\n" +
|
||||
testData;
|
||||
Components.utils.import("resource://testing-common/UrlClassifierTestUtils.jsm");
|
||||
|
||||
function cleanup() {
|
||||
SpecialPowers.clearUserPref("privacy.trackingprotection.enabled");
|
||||
SpecialPowers.clearUserPref("channelclassifier.allowlist_example");
|
||||
}
|
||||
|
||||
var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
|
||||
.getService(Ci.nsIUrlClassifierDBService);
|
||||
|
||||
function doUpdate(update) {
|
||||
var listener = {
|
||||
QueryInterface: function(iid)
|
||||
{
|
||||
if (iid.equals(Ci.nsISupports) ||
|
||||
iid.equals(Ci.nsIUrlClassifierUpdateObserver))
|
||||
return this;
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
updateUrlRequested: function(url) { },
|
||||
streamFinished: function(status) { },
|
||||
updateError: function(errorCode) {
|
||||
ok(false, "Couldn't update classifier.");
|
||||
// Abort test.
|
||||
SimpleTest.finish();
|
||||
},
|
||||
updateSuccess: function(requestedTimeout) {
|
||||
SpecialPowers.setBoolPref("privacy.trackingprotection.enabled", true);
|
||||
// Make sure chrome:// URIs are processed. This does not white-list
|
||||
// any URIs unless 'https://allowlisted.example.com' is added in the
|
||||
// permission manager (see test_allowlisted_annotations.html)
|
||||
SpecialPowers.setBoolPref("channelclassifier.allowlist_example", true);
|
||||
document.getElementById("testFrame").src = "classifiedAnnotatedFrame.html";
|
||||
}
|
||||
};
|
||||
|
||||
dbService.beginUpdate(listener, "test-track-simple", "");
|
||||
dbService.beginStream("", "");
|
||||
dbService.updateStream(update);
|
||||
dbService.finishStream();
|
||||
dbService.finishUpdate();
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{"set" : [["urlclassifier.trackingTable", "test-track-simple"]]},
|
||||
function() { doUpdate(testUpdate); });
|
||||
test);
|
||||
|
||||
function test() {
|
||||
UrlClassifierTestUtils.addTestTrackers().then(() => {
|
||||
SpecialPowers.setBoolPref("privacy.trackingprotection.enabled", true);
|
||||
// Make sure chrome:// URIs are processed. This does not white-list
|
||||
// any URIs unless 'https://allowlisted.example.com' is added in the
|
||||
// permission manager (see test_allowlisted_annotations.html)
|
||||
SpecialPowers.setBoolPref("channelclassifier.allowlist_example", true);
|
||||
document.getElementById("testFrame").src = "classifiedAnnotatedFrame.html";
|
||||
});
|
||||
}
|
||||
|
||||
// Expected finish() call is in "classifiedAnnotatedFrame.html".
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
@ -28,6 +28,7 @@ var mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
var contentPage = "chrome://mochitests/content/chrome/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html"
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://testing-common/UrlClassifierTestUtils.jsm");
|
||||
|
||||
function whenDelayedStartupFinished(aWindow, aCallback) {
|
||||
Services.obs.addObserver(function observer(aSubject, aTopic) {
|
||||
@ -60,13 +61,6 @@ function testOnWindow(aPrivate, aCallback) {
|
||||
}, true);
|
||||
}
|
||||
|
||||
// Add some URLs to the tracking database
|
||||
var testData = "tracking.example.com/";
|
||||
var testUpdate =
|
||||
"n:1000\ni:test-track-simple\nad:1\n" +
|
||||
"a:524:32:" + testData.length + "\n" +
|
||||
testData;
|
||||
|
||||
var badids = [
|
||||
"badscript",
|
||||
"badimage",
|
||||
@ -117,62 +111,37 @@ function checkLoads(aWindow, aBlocked) {
|
||||
is(allNodeMatch, aBlocked, "All tracking nodes are expected to be annotated as such");
|
||||
}
|
||||
|
||||
var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
|
||||
.getService(Ci.nsIUrlClassifierDBService);
|
||||
|
||||
function doUpdate(update) {
|
||||
var listener = {
|
||||
QueryInterface: function(iid)
|
||||
{
|
||||
if (iid.equals(Ci.nsISupports) ||
|
||||
iid.equals(Ci.nsIUrlClassifierUpdateObserver))
|
||||
return this;
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
updateUrlRequested: function(url) { },
|
||||
streamFinished: function(status) { },
|
||||
updateError: function(errorCode) {
|
||||
ok(false, "Couldn't update classifier.");
|
||||
// Abort test.
|
||||
SimpleTest.finish();
|
||||
},
|
||||
updateSuccess: function(requestedTimeout) {
|
||||
// Normal mode, with the pref (trackers should be loaded)
|
||||
testOnWindow(false, function(aWindow) {
|
||||
checkLoads(aWindow, false);
|
||||
aWindow.close();
|
||||
|
||||
// Private Browsing, with the pref (trackers should be blocked)
|
||||
testOnWindow(true, function(aWindow) {
|
||||
checkLoads(aWindow, true);
|
||||
aWindow.close();
|
||||
|
||||
// Private Browsing, without the pref (trackers should be loaded)
|
||||
SpecialPowers.setBoolPref("privacy.trackingprotection.pbmode.enabled", false);
|
||||
testOnWindow(true, function(aWindow) {
|
||||
checkLoads(aWindow, false);
|
||||
aWindow.close();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
dbService.beginUpdate(listener, "test-track-simple", "");
|
||||
dbService.beginStream("", "");
|
||||
dbService.updateStream(update);
|
||||
dbService.finishStream();
|
||||
dbService.finishUpdate();
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{"set" : [["urlclassifier.trackingTable", "test-track-simple"],
|
||||
["privacy.trackingprotection.enabled", false],
|
||||
["privacy.trackingprotection.pbmode.enabled", true],
|
||||
["channelclassifier.allowlist_example", true]]},
|
||||
function() { doUpdate(testUpdate); });
|
||||
test);
|
||||
|
||||
function test() {
|
||||
SimpleTest.registerCleanupFunction(UrlClassifierTestUtils.cleanupTestTrackers);
|
||||
UrlClassifierTestUtils.addTestTrackers().then(() => {
|
||||
// Normal mode, with the pref (trackers should be loaded)
|
||||
testOnWindow(false, function(aWindow) {
|
||||
checkLoads(aWindow, false);
|
||||
aWindow.close();
|
||||
|
||||
// Private Browsing, with the pref (trackers should be blocked)
|
||||
testOnWindow(true, function(aWindow) {
|
||||
checkLoads(aWindow, true);
|
||||
aWindow.close();
|
||||
|
||||
// Private Browsing, without the pref (trackers should be loaded)
|
||||
SpecialPowers.setBoolPref("privacy.trackingprotection.pbmode.enabled", false);
|
||||
testOnWindow(true, function(aWindow) {
|
||||
checkLoads(aWindow, false);
|
||||
aWindow.close();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
|
@ -28,6 +28,7 @@ var mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
var contentPage = "chrome://mochitests/content/chrome/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html"
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://testing-common/UrlClassifierTestUtils.jsm");
|
||||
|
||||
function whenDelayedStartupFinished(aWindow, aCallback) {
|
||||
Services.obs.addObserver(function observer(aSubject, aTopic) {
|
||||
@ -60,13 +61,6 @@ function testOnWindow(aCallback) {
|
||||
}, true);
|
||||
}
|
||||
|
||||
// Add some URLs to the tracking database
|
||||
var testData = "tracking.example.com/";
|
||||
var testUpdate =
|
||||
"n:1000\ni:test-track-simple\nad:1\n" +
|
||||
"a:524:32:" + testData.length + "\n" +
|
||||
testData;
|
||||
|
||||
var badids = [
|
||||
"badscript"
|
||||
];
|
||||
@ -76,57 +70,32 @@ function checkLoads(aWindow, aBlocked) {
|
||||
is(win.document.getElementById("badscript").dataset.touched, aBlocked ? "no" : "yes", "Should not load tracking javascript");
|
||||
}
|
||||
|
||||
var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
|
||||
.getService(Ci.nsIUrlClassifierDBService);
|
||||
|
||||
function doUpdate(update) {
|
||||
var listener = {
|
||||
QueryInterface: function(iid)
|
||||
{
|
||||
if (iid.equals(Ci.nsISupports) ||
|
||||
iid.equals(Ci.nsIUrlClassifierUpdateObserver))
|
||||
return this;
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
updateUrlRequested: function(url) { },
|
||||
streamFinished: function(status) { },
|
||||
updateError: function(errorCode) {
|
||||
ok(false, "Couldn't update classifier.");
|
||||
// Abort test.
|
||||
SimpleTest.finish();
|
||||
},
|
||||
updateSuccess: function(requestedTimeout) {
|
||||
// Safe Browsing turned OFF, tracking protection should work nevertheless
|
||||
testOnWindow(function(aWindow) {
|
||||
checkLoads(aWindow, true);
|
||||
aWindow.close();
|
||||
|
||||
// Safe Browsing turned ON, tracking protection should still work
|
||||
SpecialPowers.setBoolPref("browser.safebrowsing.enabled", true);
|
||||
testOnWindow(function(aWindow) {
|
||||
checkLoads(aWindow, true);
|
||||
aWindow.close();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
dbService.beginUpdate(listener, "test-track-simple", "");
|
||||
dbService.beginStream("", "");
|
||||
dbService.updateStream(update);
|
||||
dbService.finishStream();
|
||||
dbService.finishUpdate();
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{"set" : [["urlclassifier.trackingTable", "test-track-simple"],
|
||||
["privacy.trackingprotection.enabled", true],
|
||||
["browser.safebrowsing.malware.enabled", false],
|
||||
["browser.safebrowsing.enabled", false],
|
||||
["channelclassifier.allowlist_example", true]]},
|
||||
function() { doUpdate(testUpdate); });
|
||||
test);
|
||||
|
||||
function test() {
|
||||
SimpleTest.registerCleanupFunction(UrlClassifierTestUtils.cleanupTestTrackers);
|
||||
UrlClassifierTestUtils.addTestTrackers().then(() => {
|
||||
// Safe Browsing turned OFF, tracking protection should work nevertheless
|
||||
testOnWindow(function(aWindow) {
|
||||
checkLoads(aWindow, true);
|
||||
aWindow.close();
|
||||
|
||||
// Safe Browsing turned ON, tracking protection should still work
|
||||
SpecialPowers.setBoolPref("browser.safebrowsing.enabled", true);
|
||||
testOnWindow(function(aWindow) {
|
||||
checkLoads(aWindow, true);
|
||||
aWindow.close();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
|
@ -10,6 +10,10 @@ XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
TESTING_JS_MODULES += [
|
||||
'UrlClassifierTestUtils.jsm',
|
||||
]
|
||||
|
||||
#DEFINES['MOZILLA_INTERNAL_API'] = True
|
||||
|
||||
# XXX Get this to work in libxul builds.
|
||||
|
@ -90,6 +90,15 @@ nsURLFormatterService.prototype = {
|
||||
LOCALE: function() Cc["@mozilla.org/chrome/chrome-registry;1"].
|
||||
getService(Ci.nsIXULChromeRegistry).
|
||||
getSelectedLocale('global'),
|
||||
REGION: function() {
|
||||
try {
|
||||
// When the geoip lookup failed to identify the region, we fallback to
|
||||
// the 'ZZ' region code to mean 'unknown'.
|
||||
return Services.prefs.getCharPref("browser.search.region") || "ZZ";
|
||||
} catch(e) {
|
||||
return "ZZ";
|
||||
}
|
||||
},
|
||||
VENDOR: function() this.appInfo.vendor,
|
||||
NAME: function() this.appInfo.name,
|
||||
ID: function() this.appInfo.ID,
|
||||
|
@ -133,10 +133,13 @@ exports.items = [
|
||||
}
|
||||
|
||||
// Click handler
|
||||
if (imageSummary.filename) {
|
||||
if (imageSummary.href || imageSummary.filename) {
|
||||
root.style.cursor = "pointer";
|
||||
root.addEventListener("click", () => {
|
||||
if (imageSummary.filename) {
|
||||
if (imageSummary.href) {
|
||||
const gBrowser = context.environment.chromeWindow.gBrowser;
|
||||
gBrowser.selectedTab = gBrowser.addTab(imageSummary.href);
|
||||
} else if (imageSummary.filename) {
|
||||
const file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath(imageSummary.filename);
|
||||
file.reveal();
|
||||
@ -406,16 +409,15 @@ function uploadToImgur(reply) {
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200) {
|
||||
reply.href = xhr.response.data.link;
|
||||
reply.destinations.push(l10n.lookupFormat("screenshotImgurError",
|
||||
reply.destinations.push(l10n.lookupFormat("screenshotImgurUploaded",
|
||||
[ reply.href ]));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
reply.destinations.push(l10n.lookup("screenshotImgurError"));
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,11 @@ function addParamGroups(command) {
|
||||
* Get a data block for the help_man.html/help_man.txt templates
|
||||
*/
|
||||
function getHelpManData(commandData, context) {
|
||||
// Filter out hidden parameters
|
||||
commandData.command.params = commandData.command.params.filter(
|
||||
param => !param.hidden
|
||||
);
|
||||
|
||||
addParamGroups(commandData.command);
|
||||
commandData.subcommands.forEach(addParamGroups);
|
||||
|
||||
|