merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-06-02 12:57:52 +02:00
commit 243b8c2559
38 changed files with 645 additions and 111 deletions

View File

@ -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});
}
},
/**

View File

@ -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;
},

View File

@ -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";

View File

@ -0,0 +1,2 @@
User-agent: *
Disallow: /

View File

@ -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() {

View File

@ -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));

View File

@ -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;

View File

@ -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() {

View File

@ -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]

View File

@ -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");
}
});

View File

@ -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>

View File

@ -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!

View File

@ -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();

View File

@ -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);

View File

@ -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() {

View File

@ -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"),

View File

@ -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,

View File

@ -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"

View File

@ -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>

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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() {

View File

@ -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

View File

@ -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.

View File

@ -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]

View File

@ -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]

View File

@ -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)

View File

@ -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,
};
},
};

View File

@ -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");
}
}

View File

@ -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");
}
}
});
})();
}

View File

@ -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");

View File

@ -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]

View File

@ -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."
}
}

View File

@ -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);
},

View File

@ -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);
}
});

View File

@ -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;

View File

@ -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 {