mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-26 20:30:41 +00:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
243b8c2559
@ -435,8 +435,8 @@ loop.store.ActiveRoomStore = (function() {
|
||||
// XXX Ideally we'd do this check before joining a room, but we're waiting
|
||||
// for the UX for that. See bug 1166824. In the meantime this gives us
|
||||
// additional information for analysis.
|
||||
loop.shared.utils.hasAudioOrVideoDevices(function(hasAudio) {
|
||||
if (hasAudio) {
|
||||
loop.shared.utils.hasAudioOrVideoDevices(function(hasDevices) {
|
||||
if (hasDevices) {
|
||||
// MEDIA_WAIT causes the views to dispatch sharedActions.SetupStreamElements,
|
||||
// which in turn starts the sdk obtaining the device permission.
|
||||
this.setStoreState({roomState: ROOM_STATES.MEDIA_WAIT});
|
||||
@ -571,7 +571,19 @@ loop.store.ActiveRoomStore = (function() {
|
||||
* Used to note the current state of receiving screenshare data.
|
||||
*/
|
||||
receivingScreenShare: function(actionData) {
|
||||
this.setStoreState({receivingScreenShare: actionData.receiving});
|
||||
if (!actionData.receiving &&
|
||||
this.getStoreState().remoteVideoDimensions.screen) {
|
||||
// Remove the remote video dimensions for type screen as we're not
|
||||
// getting the share anymore.
|
||||
var newDimensions = _.extend(this.getStoreState().remoteVideoDimensions);
|
||||
delete newDimensions.screen;
|
||||
this.setStoreState({
|
||||
receivingScreenShare: actionData.receiving,
|
||||
remoteVideoDimensions: newDimensions
|
||||
});
|
||||
} else {
|
||||
this.setStoreState({receivingScreenShare: actionData.receiving});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -337,6 +337,13 @@ loop.shared.mixins = (function() {
|
||||
cache[videoType].aspectRatio = this.getAspectRatio(cache[videoType]);
|
||||
}
|
||||
}, this);
|
||||
// Remove any streams that are no longer being published.
|
||||
cacheKeys.forEach(function(videoType) {
|
||||
if (!(videoType in newDimensions)) {
|
||||
delete cache[videoType];
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
return changed;
|
||||
},
|
||||
|
||||
|
@ -314,7 +314,9 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
||||
*/
|
||||
function hasAudioOrVideoDevices(callback) {
|
||||
// mediaDevices is the official API for the spec.
|
||||
if ("mediaDevices" in rootNavigator) {
|
||||
// Older versions of FF had mediaDevices but not enumerateDevices.
|
||||
if ("mediaDevices" in rootNavigator &&
|
||||
"enumerateDevices" in rootNavigator.mediaDevices) {
|
||||
rootNavigator.mediaDevices.enumerateDevices().then(function(result) {
|
||||
function checkForInput(device) {
|
||||
return device.kind === "audioinput" || device.kind === "videoinput";
|
||||
@ -326,7 +328,8 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
||||
});
|
||||
// MediaStreamTrack is the older version of the API, implemented originally
|
||||
// by Google Chrome.
|
||||
} else if ("MediaStreamTrack" in rootObject) {
|
||||
} else if ("MediaStreamTrack" in rootObject &&
|
||||
"getSources" in rootObject.MediaStreamTrack) {
|
||||
rootObject.MediaStreamTrack.getSources(function(result) {
|
||||
function checkForInput(device) {
|
||||
return device.kind === "audio" || device.kind === "video";
|
||||
|
2
browser/components/loop/standalone/content/robots.txt
Normal file
2
browser/components/loop/standalone/content/robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
@ -1018,6 +1018,23 @@ describe("loop.store.ActiveRoomStore", function () {
|
||||
|
||||
expect(store.getStoreState().receivingScreenShare).eql(true);
|
||||
});
|
||||
|
||||
it("should delete the screen remote video dimensions if screen sharing is not active", function() {
|
||||
store.setStoreState({
|
||||
remoteVideoDimensions: {
|
||||
screen: {fake: 10},
|
||||
camera: {fake: 20}
|
||||
}
|
||||
});
|
||||
|
||||
store.receivingScreenShare(new sharedActions.ReceivingScreenShare({
|
||||
receiving: false
|
||||
}));
|
||||
|
||||
expect(store.getStoreState().remoteVideoDimensions).eql({
|
||||
camera: {fake: 20}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#startScreenShare", function() {
|
||||
|
@ -465,11 +465,9 @@ describe("loop.shared.mixins", function() {
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
view.updateVideoDimensions(localVideoDimensions, remoteVideoDimensions);
|
||||
});
|
||||
|
||||
it("should register video dimension updates correctly", function() {
|
||||
view.updateVideoDimensions(localVideoDimensions, remoteVideoDimensions);
|
||||
|
||||
expect(view._videoDimensionsCache.local.camera.width)
|
||||
.eql(localVideoDimensions.camera.width);
|
||||
expect(view._videoDimensionsCache.local.camera.height)
|
||||
@ -485,7 +483,16 @@ describe("loop.shared.mixins", function() {
|
||||
.eql(0.32857142857142857);
|
||||
});
|
||||
|
||||
it("should unregister video dimension updates correctly", function() {
|
||||
view.updateVideoDimensions(localVideoDimensions, {});
|
||||
|
||||
expect("camera" in view._videoDimensionsCache.local).eql(true);
|
||||
expect("camera" in view._videoDimensionsCache.remote).eql(false);
|
||||
});
|
||||
|
||||
it("should not populate the cache on another component instance", function() {
|
||||
view.updateVideoDimensions(localVideoDimensions, remoteVideoDimensions);
|
||||
|
||||
var view2 =
|
||||
TestUtils.renderIntoDocument(React.createElement(TestComp));
|
||||
|
||||
|
@ -174,6 +174,30 @@ describe("loop.shared.utils", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it("should return true if enumerateDevices doesn't exist in navigator.mediaDevices", function(done) {
|
||||
sharedUtils.setRootObjects(fakeWindowObject, {
|
||||
mediaDevices: {}
|
||||
});
|
||||
delete fakeWindowObject.MediaStreamTrack;
|
||||
|
||||
sharedUtils.hasAudioOrVideoDevices(function(result) {
|
||||
expect(result).eql(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return true if getSources doesn't exist in window.MediaStreamTrack", function(done) {
|
||||
sharedUtils.setRootObjects({
|
||||
MediaStreamTrack: {}
|
||||
}, fakeNavigatorObject);
|
||||
delete fakeNavigatorObject.mediaDevices;
|
||||
|
||||
sharedUtils.hasAudioOrVideoDevices(function(result) {
|
||||
expect(result).eql(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return false if no audio nor video devices exist according to navigator.mediaDevices", function(done) {
|
||||
delete fakeWindowObject.MediaStreamTrack;
|
||||
|
||||
|
@ -1113,7 +1113,7 @@ BrowserGlue.prototype = {
|
||||
}
|
||||
catch (ex) { /* Don't break the default prompt if telemetry is broken. */ }
|
||||
|
||||
Services.setBoolPref("browser.shell.isSetAsDefaultBrowser", isDefault);
|
||||
Services.prefs.setBoolPref("browser.shell.isSetAsDefaultBrowser", isDefault);
|
||||
|
||||
if (shouldCheck && !isDefault && !willRecoverSession) {
|
||||
Services.tm.mainThread.dispatch(function() {
|
||||
|
@ -17,6 +17,7 @@ support-files =
|
||||
doc_inspector_highlighter.html
|
||||
doc_inspector_highlighter_rect.html
|
||||
doc_inspector_highlighter_rect_iframe.html
|
||||
doc_inspector_highlighter_xbl.xul
|
||||
doc_inspector_infobar_01.html
|
||||
doc_inspector_infobar_02.html
|
||||
doc_inspector_menu.html
|
||||
@ -67,6 +68,7 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
|
||||
[browser_inspector_highlighter-rulers_02.js]
|
||||
[browser_inspector_highlighter-selector_01.js]
|
||||
[browser_inspector_highlighter-selector_02.js]
|
||||
[browser_inspector_highlighter-xbl.js]
|
||||
[browser_inspector_highlighter-zoom.js]
|
||||
[browser_inspector_iframe-navigation.js]
|
||||
[browser_inspector_infobar_01.js]
|
||||
|
@ -0,0 +1,41 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the picker works correctly with XBL anonymous nodes
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_inspector_highlighter_xbl.xul";
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
|
||||
|
||||
info("Starting element picker");
|
||||
yield toolbox.highlighterUtils.startPicker();
|
||||
|
||||
info("Selecting the scale");
|
||||
yield moveMouseOver("#scale");
|
||||
yield doKeyPick({key: "VK_RETURN", options: {}});
|
||||
is(inspector.selection.nodeFront.className, "scale-slider",
|
||||
"The .scale-slider inside the scale was selected");
|
||||
|
||||
function doKeyPick(msg) {
|
||||
info("Key pressed. Waiting for element to be picked");
|
||||
executeInContent("Test:SynthesizeKey", msg);
|
||||
return promise.all([
|
||||
toolbox.selection.once("new-node-front"),
|
||||
inspector.once("inspector-updated")
|
||||
]);
|
||||
}
|
||||
|
||||
function moveMouseOver(selector) {
|
||||
info("Waiting for element " + selector + " to be highlighted");
|
||||
executeInContent("Test:SynthesizeMouse", {
|
||||
options: {type: "mousemove"},
|
||||
center: true,
|
||||
selector: selector
|
||||
}, null, false);
|
||||
return inspector.toolbox.once("picker-node-hovered");
|
||||
}
|
||||
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
|
||||
<window title="Test that the picker works correctly with XBL anonymous nodes"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<scale id="scale"/>
|
||||
|
||||
</window>
|
@ -861,7 +861,7 @@ var interfaceNamesInGlobalScope =
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"PluginArray",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{name: "PointerEvent", disabled: true},
|
||||
{name: "PointerEvent", nightly: true},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"PopStateEvent",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
|
@ -38,8 +38,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=477754
|
||||
}, false);
|
||||
|
||||
function doTest() {
|
||||
is(Math.round(testAnchor.getBoundingClientRect().right) -
|
||||
Math.round(testPopup.getBoundingClientRect().right), 10,
|
||||
is(Math.round(testAnchor.getBoundingClientRect().right -
|
||||
testPopup.getBoundingClientRect().right), 10,
|
||||
"RTL popup's right offset should be equal to the x offset passed to openPopup");
|
||||
testPopup.hidePopup();
|
||||
SimpleTest.finish();
|
||||
|
@ -180,6 +180,8 @@ pref("xpinstall.whitelist.fileRequest", false);
|
||||
pref("xpinstall.whitelist.add", "addons.mozilla.org");
|
||||
pref("xpinstall.whitelist.add.180", "marketplace.firefox.com");
|
||||
|
||||
pref("xpinstall.signatures.required", false);
|
||||
|
||||
pref("extensions.enabledScopes", 1);
|
||||
pref("extensions.autoupdate.enabled", true);
|
||||
pref("extensions.autoupdate.interval", 86400);
|
||||
|
@ -723,10 +723,10 @@ public class BrowserApp extends GeckoApp
|
||||
mBrowserToolbar.setTitle(intent.getDataString());
|
||||
|
||||
showTabQueuePromptIfApplicable(intent);
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT);
|
||||
} else if (GuestSession.NOTIFICATION_INTENT.equals(action)) {
|
||||
GuestSession.handleIntent(this, intent);
|
||||
} else if (TabQueueHelper.LOAD_URLS_ACTION.equals(action)) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION, "tabqueue");
|
||||
}
|
||||
|
||||
if (HardwareUtils.isTablet()) {
|
||||
@ -937,6 +937,10 @@ public class BrowserApp extends GeckoApp
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
|
||||
int queuedTabCount = TabQueueHelper.getTabQueueLength(BrowserApp.this);
|
||||
|
||||
Telemetry.addToHistogram("FENNEC_TABQUEUE_QUEUESIZE", queuedTabCount);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "tabqueue-delayed");
|
||||
|
||||
TabQueueHelper.openQueuedUrls(BrowserApp.this, mProfile, TabQueueHelper.FILE_NAME, false);
|
||||
|
||||
// If there's more than one tab then also show the tabs panel.
|
||||
@ -1724,6 +1728,7 @@ public class BrowserApp extends GeckoApp
|
||||
Telemetry.addToHistogram("FENNEC_THUMBNAILS_COUNT", db.getCount(cr, "thumbnails"));
|
||||
Telemetry.addToHistogram("FENNEC_READING_LIST_COUNT", db.getReadingListAccessor().getCount(cr));
|
||||
Telemetry.addToHistogram("BROWSER_IS_USER_DEFAULT", (isDefaultBrowser(Intent.ACTION_VIEW) ? 1 : 0));
|
||||
Telemetry.addToHistogram("FENNEC_TABQUEUE_ENABLED", (TabQueueHelper.isTabQueueEnabled(BrowserApp.this) ? 1 : 0));
|
||||
if (Versions.feature16Plus) {
|
||||
Telemetry.addToHistogram("BROWSER_IS_ASSIST_DEFAULT", (isDefaultBrowser(Intent.ACTION_ASSIST) ? 1 : 0));
|
||||
}
|
||||
@ -3494,12 +3499,11 @@ public class BrowserApp extends GeckoApp
|
||||
// Hide firstrun-pane if the user is loading a URL from an external app.
|
||||
hideFirstrunPager();
|
||||
|
||||
// GeckoApp.ACTION_HOMESCREEN_SHORTCUT means we're opening a bookmark that
|
||||
// was added to Android's homescreen.
|
||||
final TelemetryContract.Method method =
|
||||
(isViewAction ? TelemetryContract.Method.INTENT : TelemetryContract.Method.HOMESCREEN);
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, method);
|
||||
if (isBookmarkAction) {
|
||||
// GeckoApp.ACTION_HOMESCREEN_SHORTCUT means we're opening a bookmark that
|
||||
// was added to Android's homescreen.
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.HOMESCREEN);
|
||||
}
|
||||
}
|
||||
|
||||
showTabQueuePromptIfApplicable(intent);
|
||||
@ -3518,6 +3522,7 @@ public class BrowserApp extends GeckoApp
|
||||
|
||||
// If the user has clicked the tab queue notification then load the tabs.
|
||||
if(AppConstants.NIGHTLY_BUILD && AppConstants.MOZ_ANDROID_TAB_QUEUE && mInitialized && isTabQueueAction) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION, "tabqueue");
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -157,6 +157,9 @@ public interface TelemetryContract {
|
||||
// No method is specified.
|
||||
NONE(null),
|
||||
|
||||
// Action triggered from a notification in the Android notification bar.
|
||||
NOTIFICATION("notification"),
|
||||
|
||||
// Action triggered from a pageaction in the URLBar.
|
||||
// Note: Only used in JavaScript for now, but here for completeness.
|
||||
PAGEACTION("pageaction"),
|
||||
|
@ -195,10 +195,10 @@
|
||||
<!ENTITY pref_donottrack_title "Do not track">
|
||||
<!ENTITY pref_donottrack_summary "&brandShortName; will tell sites that you do not want to be tracked">
|
||||
|
||||
<!ENTITY tab_queue_toast_message2 "Tab queued in &brandShortName;">
|
||||
<!ENTITY tab_queue_toast_message3 "Tab saved in &brandShortName;">
|
||||
<!ENTITY tab_queue_toast_action "Open now">
|
||||
<!ENTITY tab_queue_prompt_title "Opening multiple links?">
|
||||
<!ENTITY tab_queue_prompt_text2 "Open them without switching to &brandShortName; each time.">
|
||||
<!ENTITY tab_queue_prompt_text3 "Save them until the next time you open &brandShortName;">
|
||||
<!ENTITY tab_queue_prompt_tip_text "you can change this later in Settings">
|
||||
<!ENTITY tab_queue_prompt_positive_action_button "Enable">
|
||||
<!ENTITY tab_queue_prompt_negative_action_button "Not now">
|
||||
@ -402,7 +402,7 @@ size. -->
|
||||
<!ENTITY pref_scroll_title_bar_summary "Hide the &brandShortName; title bar when scrolling down a page">
|
||||
|
||||
<!ENTITY pref_tab_queue_title2 "Open multiple links">
|
||||
<!ENTITY pref_tab_queue_summary2 "Queue links for later instead of switching to &brandShortName; each time">
|
||||
<!ENTITY pref_tab_queue_summary3 "Save them until the next time you open &brandShortName;">
|
||||
|
||||
<!-- Localization note (page_removed): This string appears in a toast message when
|
||||
any page is removed frome about:home. This includes pages that are in history,
|
||||
|
@ -46,7 +46,7 @@
|
||||
android:textColor="@color/placeholder_grey"
|
||||
android:textSize="16sp"
|
||||
|
||||
tools:text="Open them without switching to Firefox each time." />
|
||||
tools:text="Save them until the next time you open Firefox." />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tip_text"
|
||||
|
@ -243,13 +243,13 @@
|
||||
<string name="pref_update_autodownload_enabled">&pref_update_autodownload_always;</string>
|
||||
|
||||
<string name="pref_tab_queue_title">&pref_tab_queue_title2;</string>
|
||||
<string name="pref_tab_queue_summary">&pref_tab_queue_summary2;</string>
|
||||
<string name="pref_tab_queue_summary">&pref_tab_queue_summary3;</string>
|
||||
<string name="tab_queue_prompt_title">&tab_queue_prompt_title;</string>
|
||||
<string name="tab_queue_prompt_text">&tab_queue_prompt_text2;</string>
|
||||
<string name="tab_queue_prompt_text">&tab_queue_prompt_text3;</string>
|
||||
<string name="tab_queue_prompt_tip_text">&tab_queue_prompt_tip_text;</string>
|
||||
<string name="tab_queue_prompt_positive_action_button">&tab_queue_prompt_positive_action_button;</string>
|
||||
<string name="tab_queue_prompt_negative_action_button">&tab_queue_prompt_negative_action_button;</string>
|
||||
<string name="tab_queue_toast_message">&tab_queue_toast_message2;</string>
|
||||
<string name="tab_queue_toast_message">&tab_queue_toast_message3;</string>
|
||||
<string name="tab_queue_toast_action">&tab_queue_toast_action;</string>
|
||||
<string name="tab_queue_notification_text_singular">&tab_queue_notification_text_singular2;</string>
|
||||
<string name="tab_queue_notification_text_plural">&tab_queue_notification_text_plural2;</string>
|
||||
|
@ -9,6 +9,8 @@ import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.mozglue.ContextUtils;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
|
||||
@ -55,7 +57,7 @@ public class TabQueueDispatcher extends Locales.LocaleAwareActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean shouldShowOpenInBackgroundToast = GeckoSharedPrefs.forApp(this).getBoolean(GeckoPreferences.PREFS_TAB_QUEUE, false);
|
||||
boolean shouldShowOpenInBackgroundToast = TabQueueHelper.isTabQueueEnabled(this);
|
||||
|
||||
if (shouldShowOpenInBackgroundToast) {
|
||||
showToast(safeIntent.getUnsafe());
|
||||
@ -76,6 +78,7 @@ public class TabQueueDispatcher extends Locales.LocaleAwareActivity {
|
||||
private void loadNormally(Intent intent) {
|
||||
intent.setClassName(getApplicationContext(), AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
|
||||
startActivity(intent);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "");
|
||||
finish();
|
||||
}
|
||||
|
||||
|
@ -54,12 +54,11 @@ public class TabQueueHelper {
|
||||
public static boolean shouldShowTabQueuePrompt(Context context) {
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
|
||||
boolean isTabQueueEnabled = prefs.getBoolean(GeckoPreferences.PREFS_TAB_QUEUE, false);
|
||||
int numberOfTimesTabQueuePromptSeen = prefs.getInt(PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN, 0);
|
||||
|
||||
// Exit early if the feature is already enabled or the user has seen the
|
||||
// prompt more than MAX_TIMES_TO_SHOW_PROMPT times.
|
||||
if (isTabQueueEnabled || numberOfTimesTabQueuePromptSeen >= MAX_TIMES_TO_SHOW_PROMPT) {
|
||||
if (isTabQueueEnabled(prefs) || numberOfTimesTabQueuePromptSeen >= MAX_TIMES_TO_SHOW_PROMPT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -184,10 +183,9 @@ public class TabQueueHelper {
|
||||
// TODO: Use profile shared prefs when bug 1147925 gets fixed.
|
||||
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
|
||||
boolean tabQueueEnabled = prefs.getBoolean(GeckoPreferences.PREFS_TAB_QUEUE, false);
|
||||
int tabsQueued = prefs.getInt(PREF_TAB_QUEUE_COUNT, 0);
|
||||
|
||||
return tabQueueEnabled && tabsQueued > 0;
|
||||
return isTabQueueEnabled(prefs) && tabsQueued > 0;
|
||||
}
|
||||
|
||||
public static int getTabQueueLength(final Context context) {
|
||||
@ -270,4 +268,12 @@ public class TabQueueHelper {
|
||||
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isTabQueueEnabled(Context context) {
|
||||
return isTabQueueEnabled(GeckoSharedPrefs.forApp(context));
|
||||
}
|
||||
|
||||
public static boolean isTabQueueEnabled(SharedPreferences prefs) {
|
||||
return prefs.getBoolean(GeckoPreferences.PREFS_TAB_QUEUE, false);
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,10 @@
|
||||
|
||||
package org.mozilla.gecko.tabqueue;
|
||||
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.animation.TransitionsTracker;
|
||||
|
||||
import android.os.Bundle;
|
||||
@ -39,15 +41,19 @@ public class TabQueuePrompt extends Locales.LocaleAwareActivity {
|
||||
private void showTabQueueEnablePrompt() {
|
||||
setContentView(R.layout.tab_queue_prompt);
|
||||
|
||||
final int numberOfTimesTabQueuePromptSeen = GeckoSharedPrefs.forApp(this).getInt(TabQueueHelper.PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN, 0);
|
||||
|
||||
findViewById(R.id.ok_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onConfirmButtonPressed();
|
||||
Telemetry.addToHistogram("FENNEC_TABQUEUE_PROMPT_ENABLE_YES", numberOfTimesTabQueuePromptSeen);
|
||||
}
|
||||
});
|
||||
findViewById(R.id.cancel_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Telemetry.addToHistogram("FENNEC_TABQUEUE_PROMPT_ENABLE_NO", numberOfTimesTabQueuePromptSeen);
|
||||
setResult(TabQueueHelper.TAB_QUEUE_NO);
|
||||
finish();
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.mozglue.ContextUtils;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
|
||||
@ -29,6 +31,7 @@ import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@ -199,6 +202,17 @@ public class TabQueueService extends Service {
|
||||
GeckoSharedPrefs.forApp(getApplicationContext()).edit().remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE)
|
||||
.remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME)
|
||||
.apply();
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "tabqueue-now");
|
||||
|
||||
executorService.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int queuedTabCount = TabQueueHelper.getTabQueueLength(TabQueueService.this);
|
||||
Telemetry.addToHistogram("FENNEC_TABQUEUE_QUEUESIZE", queuedTabCount);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void removeView() {
|
||||
|
@ -106,3 +106,12 @@ fi
|
||||
|
||||
# Use the low-memory GC tuning.
|
||||
export JS_GC_SMALL_CHUNK_SIZE=1
|
||||
|
||||
# Enable checking that add-ons are signed by the trusted root
|
||||
MOZ_ADDON_SIGNING=1
|
||||
if test "$MOZ_OFFICIAL_BRANDING"; then
|
||||
if test "$MOZ_UPDATE_CHANNEL" = "beta" -o \
|
||||
"$MOZ_UPDATE_CHANNEL" = "release"; then
|
||||
MOZ_REQUIRE_SIGNING=1
|
||||
fi
|
||||
fi
|
||||
|
@ -4415,15 +4415,24 @@ pref("dom.mozPermissionSettings.enabled", false);
|
||||
pref("dom.w3c_touch_events.enabled", 2);
|
||||
#endif
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
#if defined(XP_WIN) || defined(XP_LINUX) || defined(XP_MACOSX)
|
||||
// W3C draft pointer events
|
||||
pref("dom.w3c_pointer_events.enabled", true);
|
||||
// W3C touch-action css property (related to touch and pointer events)
|
||||
pref("layout.css.touch_action.enabled", true);
|
||||
#else
|
||||
pref("dom.w3c_pointer_events.enabled", false);
|
||||
pref("layout.css.touch_action.enabled", false);
|
||||
#endif
|
||||
#else
|
||||
pref("dom.w3c_pointer_events.enabled", false);
|
||||
pref("layout.css.touch_action.enabled", false);
|
||||
#endif
|
||||
|
||||
// W3C draft ImageCapture API
|
||||
pref("dom.imagecapture.enabled", false);
|
||||
|
||||
// W3C touch-action css property (related to touch and pointer events)
|
||||
pref("layout.css.touch_action.enabled", false);
|
||||
|
||||
// Enables some assertions in nsStyleContext that are too expensive
|
||||
// for general use, but might be useful to enable for specific tests.
|
||||
// This only has an effect in DEBUG-builds.
|
||||
|
@ -1,12 +1,3 @@
|
||||
[pointerevent_touch-action-illegal.html]
|
||||
type: testharness
|
||||
prefs: [dom.w3c_pointer_events.enabled:true]
|
||||
['pan-x none' is corrected properly]
|
||||
expected: FAIL
|
||||
|
||||
['pan-y none' is corrected properly]
|
||||
expected: FAIL
|
||||
|
||||
['auto none' is corrected properly]
|
||||
expected: FAIL
|
||||
|
||||
prefs: [layout.css.touch_action.enabled:true]
|
||||
|
@ -1,18 +1,3 @@
|
||||
[pointerevent_touch-action-verification.html]
|
||||
type: testharness
|
||||
prefs: [dom.w3c_pointer_events.enabled:true]
|
||||
['auto' is set properly]
|
||||
expected: FAIL
|
||||
|
||||
['pan-x' is corrected properly]
|
||||
expected: FAIL
|
||||
|
||||
['pan-y' is set properly]
|
||||
expected: FAIL
|
||||
|
||||
['none' is set properly]
|
||||
expected: FAIL
|
||||
|
||||
['manipulation' is set properly]
|
||||
expected: FAIL
|
||||
|
||||
prefs: [layout.css.touch_action.enabled:true]
|
||||
|
@ -315,7 +315,7 @@ class XPCShellTestThread(Thread):
|
||||
name.replace('\\', '/')]
|
||||
|
||||
def setupTempDir(self):
|
||||
tempDir = mkdtemp()
|
||||
tempDir = mkdtemp(prefix='xpc-other-')
|
||||
self.env["XPCSHELL_TEST_TEMP_DIR"] = tempDir
|
||||
if self.interactive:
|
||||
self.log.info("temp dir is %s" % tempDir)
|
||||
@ -325,7 +325,7 @@ class XPCShellTestThread(Thread):
|
||||
if not os.path.isdir(self.pluginsPath):
|
||||
return None
|
||||
|
||||
pluginsDir = mkdtemp()
|
||||
pluginsDir = mkdtemp(prefix='xpc-plugins-')
|
||||
# shutil.copytree requires dst to not exist. Deleting the tempdir
|
||||
# would make a race condition possible in a concurrent environment,
|
||||
# so we are using dir_utils.copy_tree which accepts an existing dst
|
||||
@ -351,7 +351,7 @@ class XPCShellTestThread(Thread):
|
||||
pass
|
||||
os.makedirs(profileDir)
|
||||
else:
|
||||
profileDir = mkdtemp()
|
||||
profileDir = mkdtemp(prefix='xpc-profile-')
|
||||
self.env["XPCSHELL_TEST_PROFILE_DIR"] = profileDir
|
||||
if self.interactive or self.singleFile:
|
||||
self.log.info("profile dir is %s" % profileDir)
|
||||
|
@ -1,4 +1,3 @@
|
||||
/* vim: set ts=2 sts=2 sw=2 et tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
@ -452,27 +451,28 @@ var LoginManagerContent = {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _getPasswordFields
|
||||
*
|
||||
* Returns an array of password field elements for the specified form.
|
||||
* If no pw fields are found, or if more than 3 are found, then null
|
||||
* is returned.
|
||||
*
|
||||
* skipEmptyFields can be set to ignore password fields with no value.
|
||||
/**
|
||||
* @param {FormLike} form - the FormLike to look for password fields in.
|
||||
* @param {bool} [skipEmptyFields=false] - Whether to ignore password fields with no value.
|
||||
* Used at capture time since saving empty values isn't
|
||||
* useful.
|
||||
* @return {Array|null} Array of password field elements for the specified form.
|
||||
* If no pw fields are found, or if more than 3 are found, then null
|
||||
* is returned.
|
||||
*/
|
||||
_getPasswordFields : function (form, skipEmptyFields) {
|
||||
_getPasswordFields(form, skipEmptyFields = false) {
|
||||
// Locate the password fields in the form.
|
||||
var pwFields = [];
|
||||
for (var i = 0; i < form.elements.length; i++) {
|
||||
var element = form.elements[i];
|
||||
let pwFields = [];
|
||||
for (let i = 0; i < form.elements.length; i++) {
|
||||
let element = form.elements[i];
|
||||
if (!(element instanceof Ci.nsIDOMHTMLInputElement) ||
|
||||
element.type != "password")
|
||||
element.type != "password") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (skipEmptyFields && !element.value)
|
||||
if (skipEmptyFields && !element.value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pwFields[pwFields.length] = {
|
||||
index : i,
|
||||
@ -485,15 +485,13 @@ var LoginManagerContent = {
|
||||
log("(form ignored -- no password fields.)");
|
||||
return null;
|
||||
} else if (pwFields.length > 3) {
|
||||
log("(form ignored -- too many password fields. [ got ",
|
||||
pwFields.length, "])");
|
||||
log("(form ignored -- too many password fields. [ got ", pwFields.length, "])");
|
||||
return null;
|
||||
}
|
||||
|
||||
return pwFields;
|
||||
},
|
||||
|
||||
|
||||
_isUsernameFieldType: function(element) {
|
||||
if (!(element instanceof Ci.nsIDOMHTMLInputElement))
|
||||
return false;
|
||||
@ -1091,3 +1089,76 @@ UserAutoCompleteResult.prototype = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A factory to generate FormLike objects that represent a set of login fields
|
||||
* which aren't necessarily marked up with a <form> element.
|
||||
*/
|
||||
let FormLikeFactory = {
|
||||
_propsFromForm: [
|
||||
"action",
|
||||
"autocomplete",
|
||||
],
|
||||
|
||||
/**
|
||||
* Create a FormLike object from a <form>.
|
||||
*
|
||||
* @param {HTMLFormElement} aForm
|
||||
* @return {FormLike}
|
||||
* @throws Error if aForm isn't an HTMLFormElement
|
||||
*/
|
||||
createFromForm(aForm) {
|
||||
if (!(aForm instanceof Ci.nsIDOMHTMLFormElement)) {
|
||||
throw new Error("createFromForm: aForm must be a nsIDOMHTMLFormElement");
|
||||
}
|
||||
|
||||
let formLike = {
|
||||
elements: [...aForm.elements],
|
||||
ownerDocument: aForm.ownerDocument,
|
||||
rootElement: aForm,
|
||||
};
|
||||
|
||||
for (let prop of this._propsFromForm) {
|
||||
formLike[prop] = aForm[prop];
|
||||
}
|
||||
|
||||
return formLike;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a FormLike object from an <input type=password>.
|
||||
*
|
||||
* If the <input> is in a <form>, construct the FormLike from the form.
|
||||
* Otherwise, create a FormLike with a rootElement (wrapper) according to
|
||||
* heuristics. Currently all <input> not in a <form> are one FormLike but this
|
||||
* shouldn't be relied upon as the heuristics may change to detect multiple
|
||||
* "forms" (e.g. registration and login) on one page with a <form>.
|
||||
*
|
||||
* @param {HTMLInputElement} aPasswordField - a password field in a document
|
||||
* @return {FormLike}
|
||||
* @throws Error if aPasswordField isn't a password input in a document
|
||||
*/
|
||||
createFromPasswordField(aPasswordField) {
|
||||
if (!(aPasswordField instanceof Ci.nsIDOMHTMLInputElement) ||
|
||||
aPasswordField.type != "password" ||
|
||||
!aPasswordField.ownerDocument) {
|
||||
throw new Error("createFromPasswordField requires a password field in a document");
|
||||
}
|
||||
|
||||
if (aPasswordField.form) {
|
||||
return this.createFromForm(aPasswordField.form);
|
||||
}
|
||||
|
||||
let doc = aPasswordField.ownerDocument;
|
||||
log("Created non-form FormLike for rootElement:", doc.documentElement);
|
||||
return {
|
||||
action: "",
|
||||
autocomplete: "on",
|
||||
// Exclude elements inside the rootElement that are already in a <form> as
|
||||
// they will be handled by their own FormLike.
|
||||
elements: [for (el of doc.querySelectorAll("input")) if (!el.form) el],
|
||||
ownerDocument: doc,
|
||||
rootElement: doc.documentElement,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@ -124,10 +124,9 @@ const RecipeHelpers = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a document for the given URL containing the given HTML containing a
|
||||
* form and return the <form>.
|
||||
* Create a document for the given URL containing the given HTML with the ownerDocument of all <form>s having a mocked location.
|
||||
*/
|
||||
createTestForm(aDocumentURL, aHTML = "<form>") {
|
||||
createTestDocument(aDocumentURL, aHTML = "<form>") {
|
||||
let parser = Cc["@mozilla.org/xmlextras/domparser;1"].
|
||||
createInstance(Ci.nsIDOMParser);
|
||||
parser.init();
|
||||
@ -146,14 +145,13 @@ const RecipeHelpers = {
|
||||
},
|
||||
});
|
||||
|
||||
let form = parsedDoc.forms[0];
|
||||
|
||||
// Assign form.ownerDocument to the proxy so document.location works.
|
||||
Object.defineProperty(form, "ownerDocument", {
|
||||
value: document,
|
||||
});
|
||||
|
||||
return form;
|
||||
for (let form of parsedDoc.forms) {
|
||||
// Assign form.ownerDocument to the proxy so document.location works.
|
||||
Object.defineProperty(form, "ownerDocument", {
|
||||
value: document,
|
||||
});
|
||||
}
|
||||
return parsedDoc;
|
||||
}
|
||||
};
|
||||
|
||||
@ -176,3 +174,23 @@ add_task(function test_common_initialize()
|
||||
// Clean up after every test.
|
||||
do_register_cleanup(() => LoginTestUtils.clearData());
|
||||
});
|
||||
|
||||
/**
|
||||
* Compare two FormLike to see if they represent the same information. Elements
|
||||
* are compared using their @id attribute.
|
||||
*/
|
||||
function formLikeEqual(a, b) {
|
||||
Assert.strictEqual(Object.keys(a).length, Object.keys(b).length,
|
||||
"Check the formLikes have the same number of properties");
|
||||
|
||||
for (let propName of Object.keys(a)) {
|
||||
if (propName == "elements") {
|
||||
Assert.strictEqual(a.elements.length, b.elements.length, "Check element count");
|
||||
for (let i = 0; i < a.elements.length; i++) {
|
||||
Assert.strictEqual(a.elements[i].id, b.elements[i].id, "Check element " + i + " id");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Assert.strictEqual(a[propName], b[propName], "Compare formLike " + propName + " property");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Test for LoginManagerContent._getPasswordFields using FormLikeFactory.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const LMCBackstagePass = Cu.import("resource://gre/modules/LoginManagerContent.jsm");
|
||||
const { LoginManagerContent, FormLikeFactory } = LMCBackstagePass;
|
||||
const TESTCASES = [
|
||||
{
|
||||
description: "Empty document",
|
||||
document: ``,
|
||||
returnedFieldIDsByFormLike: [],
|
||||
skipEmptyFields: undefined,
|
||||
},
|
||||
{
|
||||
description: "Non-password input with no <form> present",
|
||||
document: `<input>`,
|
||||
returnedFieldIDsByFormLike: [],
|
||||
skipEmptyFields: undefined,
|
||||
},
|
||||
{
|
||||
description: "1 password field outside of a <form>",
|
||||
document: `<input id="pw1" type=password>`,
|
||||
returnedFieldIDsByFormLike: [["pw1"]],
|
||||
skipEmptyFields: undefined,
|
||||
},
|
||||
{
|
||||
description: "4 empty password fields outside of a <form>",
|
||||
document: `<input id="pw1" type=password>
|
||||
<input id="pw2" type=password>
|
||||
<input id="pw3" type=password>
|
||||
<input id="pw4" type=password>`,
|
||||
returnedFieldIDsByFormLike: [[]],
|
||||
skipEmptyFields: undefined,
|
||||
},
|
||||
{
|
||||
description: "4 password fields outside of a <form> (1 empty, 3 full) with skipEmpty",
|
||||
document: `<input id="pw1" type=password>
|
||||
<input id="pw2" type=password value="pass2">
|
||||
<input id="pw3" type=password value="pass3">
|
||||
<input id="pw4" type=password value="pass4">`,
|
||||
returnedFieldIDsByFormLike: [["pw2", "pw3", "pw4"]],
|
||||
skipEmptyFields: true,
|
||||
},
|
||||
{
|
||||
description: "Form with 1 password field",
|
||||
document: `<form><input id="pw1" type=password></form>`,
|
||||
returnedFieldIDsByFormLike: [["pw1"]],
|
||||
skipEmptyFields: undefined,
|
||||
},
|
||||
{
|
||||
description: "Form with 2 password fields",
|
||||
document: `<form><input id="pw1" type=password><input id='pw2' type=password></form>`,
|
||||
returnedFieldIDsByFormLike: [["pw1", "pw2"]],
|
||||
skipEmptyFields: undefined,
|
||||
},
|
||||
{
|
||||
description: "1 password field in a form, 1 outside",
|
||||
document: `<form><input id="pw1" type=password></form><input id="pw2" type=password>`,
|
||||
returnedFieldIDsByFormLike: [["pw1"], ["pw2"]],
|
||||
skipEmptyFields: undefined,
|
||||
},
|
||||
{
|
||||
description: "2 password fields outside of a <form> with 1 linked via @form",
|
||||
document: `<input id="pw1" type=password><input id="pw2" type=password form='form1'>
|
||||
<form id="form1"></form>`,
|
||||
returnedFieldIDsByFormLike: [["pw1"], ["pw2"]],
|
||||
skipEmptyFields: undefined,
|
||||
},
|
||||
{
|
||||
description: "2 password fields outside of a <form> with 1 linked via @form + skipEmpty",
|
||||
document: `<input id="pw1" type=password><input id="pw2" type=password form="form1">
|
||||
<form id="form1"></form>`,
|
||||
returnedFieldIDsByFormLike: [[],[]],
|
||||
skipEmptyFields: true,
|
||||
},
|
||||
{
|
||||
description: "2 password fields outside of a <form> with 1 linked via @form + skipEmpty with 1 empty",
|
||||
document: `<input id="pw1" type=password value="pass1"><input id="pw2" type=password form="form1">
|
||||
<form id="form1"></form>`,
|
||||
returnedFieldIDsByFormLike: [["pw1"],[]],
|
||||
skipEmptyFields: true,
|
||||
},
|
||||
];
|
||||
|
||||
for (let tc of TESTCASES) {
|
||||
do_print("Sanity checking the testcase: " + tc.description);
|
||||
|
||||
(function() {
|
||||
let testcase = tc;
|
||||
add_task(function*() {
|
||||
do_print("Starting testcase: " + testcase.description);
|
||||
let document = RecipeHelpers.createTestDocument("http://localhost:8080/test/",
|
||||
testcase.document);
|
||||
|
||||
let mapRootElementToFormLike = new Map();
|
||||
for (let input of document.querySelectorAll("input")) {
|
||||
if (input.type != "password") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let formLike = FormLikeFactory.createFromPasswordField(input);
|
||||
let existingFormLike = mapRootElementToFormLike.get(formLike.rootElement);
|
||||
if (!existingFormLike) {
|
||||
mapRootElementToFormLike.set(formLike.rootElement, formLike);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the formLike is already present, ensure that the properties are the same.
|
||||
do_print("Checking if the new FormLike for the same root has the same properties");
|
||||
formLikeEqual(formLike, existingFormLike);
|
||||
}
|
||||
|
||||
Assert.strictEqual(mapRootElementToFormLike.size, testcase.returnedFieldIDsByFormLike.length,
|
||||
"Check the correct number of different formLikes were returned");
|
||||
|
||||
let formLikeIndex = -1;
|
||||
for (let formLikeFromInput of mapRootElementToFormLike.values()) {
|
||||
formLikeIndex++;
|
||||
let pwFields = LoginManagerContent._getPasswordFields(formLikeFromInput,
|
||||
testcase.skipEmptyFields);
|
||||
|
||||
if (formLikeFromInput.rootElement instanceof Ci.nsIDOMHTMLFormElement) {
|
||||
let formLikeFromForm = FormLikeFactory.createFromForm(formLikeFromInput.rootElement);
|
||||
do_print("Checking that the FormLike created for the <form> matches" +
|
||||
" the one from a password field");
|
||||
formLikeEqual(formLikeFromInput, formLikeFromForm);
|
||||
}
|
||||
|
||||
|
||||
if (testcase.returnedFieldIDsByFormLike[formLikeIndex].length === 0) {
|
||||
Assert.strictEqual(pwFields, null,
|
||||
"If no password fields were found null should be returned");
|
||||
} else {
|
||||
Assert.strictEqual(pwFields.length,
|
||||
testcase.returnedFieldIDsByFormLike[formLikeIndex].length,
|
||||
"Check the # of password fields for formLike #" + formLikeIndex);
|
||||
}
|
||||
|
||||
for (let i = 0; i < testcase.returnedFieldIDsByFormLike[formLikeIndex].length; i++) {
|
||||
let expectedID = testcase.returnedFieldIDsByFormLike[formLikeIndex][i];
|
||||
Assert.strictEqual(pwFields[i].element.id, expectedID,
|
||||
"Check password field " + i + " ID");
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
@ -7,11 +7,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
add_task(function* test_getFieldOverrides() {
|
||||
let recipes = new Set([
|
||||
{ // path doesn't match but otherwise good
|
||||
@ -32,7 +29,8 @@ add_task(function* test_getFieldOverrides() {
|
||||
},
|
||||
]);
|
||||
|
||||
let form = RecipeHelpers.createTestForm("http://localhost:8080/first/second/");
|
||||
let form = RecipeHelpers.createTestDocument("http://localhost:8080/first/second/", "<form>").
|
||||
forms[0];
|
||||
let override = LoginRecipesContent.getFieldOverrides(recipes, form);
|
||||
Assert.strictEqual(override.description, "best match",
|
||||
"Check the best field override recipe was returned");
|
||||
|
@ -16,6 +16,7 @@ skip-if = os != "android"
|
||||
|
||||
# The following tests apply to any storage back-end.
|
||||
[test_disabled_hosts.js]
|
||||
[test_getPasswordFields.js]
|
||||
[test_legacy_empty_formSubmitURL.js]
|
||||
[test_legacy_validation.js]
|
||||
[test_logins_change.js]
|
||||
|
@ -8070,5 +8070,29 @@
|
||||
"expires_in_version": "never",
|
||||
"kind": "count",
|
||||
"description": "Record the removal of defective permissions.sqlite"
|
||||
},
|
||||
"FENNEC_TABQUEUE_QUEUESIZE" : {
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": "50",
|
||||
"n_buckets": 10,
|
||||
"description": "The number of tabs queued when opened."
|
||||
},
|
||||
"FENNEC_TABQUEUE_PROMPT_ENABLE_YES" : {
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 3,
|
||||
"description": "The number of times the tab queue prompt was seen before the user selected YES."
|
||||
},
|
||||
"FENNEC_TABQUEUE_PROMPT_ENABLE_NO" : {
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 3,
|
||||
"description": "The number of times the tab queue prompt was seen before the user selected NO."
|
||||
},
|
||||
"FENNEC_TABQUEUE_ENABLED": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
"description": "Has the tab queue functionality been enabled."
|
||||
}
|
||||
}
|
||||
|
@ -349,12 +349,10 @@ let HighlighterActor = exports.HighlighterActor = protocol.ActorClass({
|
||||
}),
|
||||
|
||||
_findAndAttachElement: function(event) {
|
||||
let doc = event.target.ownerDocument;
|
||||
|
||||
let x = event.clientX;
|
||||
let y = event.clientY;
|
||||
|
||||
let node = doc.elementFromPoint(x, y);
|
||||
// originalTarget allows access to the "real" element before any retargeting
|
||||
// is applied, such as in the case of XBL anonymous elements. See also
|
||||
// https://developer.mozilla.org/docs/XBL/XBL_1.0_Reference/Anonymous_Content#Event_Flow_and_Targeting
|
||||
let node = event.originalTarget || event.target;
|
||||
return this._walker.attachElement(node);
|
||||
},
|
||||
|
||||
|
@ -754,4 +754,120 @@ ChildDebuggerTransport.prototype = {
|
||||
|
||||
exports.ChildDebuggerTransport = ChildDebuggerTransport;
|
||||
|
||||
// WorkerDebuggerTransport is defined differently depending on whether we are
|
||||
// on the main thread or a worker thread. In the former case, we are required
|
||||
// by the devtools loader, and isWorker will be false. Otherwise, we are
|
||||
// required by the worker loader, and isWorker will be true.
|
||||
//
|
||||
// Each worker debugger supports only a single connection to the main thread.
|
||||
// However, its theoretically possible for multiple servers to connect to the
|
||||
// same worker. Consequently, each transport has a connection id, to allow
|
||||
// messages from multiple connections to be multiplexed on a single channel.
|
||||
|
||||
if (!this.isWorker) {
|
||||
(function () { // Main thread
|
||||
/**
|
||||
* A transport that uses a WorkerDebugger to send packets from the main
|
||||
* thread to a worker thread.
|
||||
*/
|
||||
function WorkerDebuggerTransport(dbg, id) {
|
||||
this._dbg = dbg;
|
||||
this._id = id;
|
||||
this.onMessage = this._onMessage.bind(this);
|
||||
}
|
||||
|
||||
WorkerDebuggerTransport.prototype = {
|
||||
constructor: WorkerDebuggerTransport,
|
||||
|
||||
ready: function () {
|
||||
this._dbg.addListener(this);
|
||||
},
|
||||
|
||||
close: function () {
|
||||
this._dbg.removeListener(this);
|
||||
if (this.hooks) {
|
||||
this.hooks.onClosed();
|
||||
}
|
||||
},
|
||||
|
||||
send: function (packet) {
|
||||
this._dbg.postMessage(JSON.stringify({
|
||||
type: "message",
|
||||
id: this._id,
|
||||
message: packet
|
||||
}));
|
||||
},
|
||||
|
||||
startBulkSend: function () {
|
||||
throw new Error("Can't send bulk data from worker threads!");
|
||||
},
|
||||
|
||||
_onMessage: function (message) {
|
||||
let packet = JSON.parse(message);
|
||||
if (packet.type !== "message" || packet.id !== this._id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hooks) {
|
||||
this.hooks.onPacket(packet.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
|
||||
}).call(this);
|
||||
} else {
|
||||
(function () { // Worker thread
|
||||
/*
|
||||
* A transport that uses a WorkerDebuggerGlobalScope to send packets from a
|
||||
* worker thread to the main thread.
|
||||
*/
|
||||
function WorkerDebuggerTransport(scope, id) {
|
||||
this._scope = scope;
|
||||
this._id = id;
|
||||
this._onMessage = this._onMessage.bind(this);
|
||||
}
|
||||
|
||||
WorkerDebuggerTransport.prototype = {
|
||||
constructor: WorkerDebuggerTransport,
|
||||
|
||||
ready: function () {
|
||||
this._scope.addEventListener("message", this._onMessage);
|
||||
},
|
||||
|
||||
close: function () {
|
||||
this._scope.removeEventListener("message", this._onMessage);
|
||||
if (this.hooks) {
|
||||
this.hooks.onClosed();
|
||||
}
|
||||
},
|
||||
|
||||
send: function (packet) {
|
||||
this._scope.postMessage(JSON.stringify({
|
||||
type: "message",
|
||||
id: this._id,
|
||||
message: packet
|
||||
}));
|
||||
},
|
||||
|
||||
startBulkSend: function () {
|
||||
throw new Error("Can't send bulk data from worker threads!");
|
||||
},
|
||||
|
||||
_onMessage: function (event) {
|
||||
let packet = JSON.parse(event.data);
|
||||
if (packet.type !== "message" || packet.id !== this._id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hooks) {
|
||||
this.hooks.onPacket(packet.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
|
||||
}).call(this);
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -307,8 +307,10 @@ mmin(size_t a, size_t b)
|
||||
static NS_tchar*
|
||||
mstrtok(const NS_tchar *delims, NS_tchar **str)
|
||||
{
|
||||
if (!*str || !**str)
|
||||
if (!*str || !**str) {
|
||||
*str = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// skip leading "whitespace"
|
||||
NS_tchar *ret = *str;
|
||||
|
@ -176,10 +176,10 @@ separator.groove[orient="vertical"] {
|
||||
description,
|
||||
label {
|
||||
cursor: default;
|
||||
/* FIXME: On Windows and Linux, we're using -moz-margin-end: 5px, but for
|
||||
unknown reasons this breaks test_bug477754.xul on OS X.
|
||||
See bug 1169606. */
|
||||
margin: 1px 6px 2px;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 2px;
|
||||
-moz-margin-start: 6px;
|
||||
-moz-margin-end: 5px;
|
||||
}
|
||||
|
||||
description {
|
||||
|
Loading…
x
Reference in New Issue
Block a user