merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2014-10-31 14:19:30 +01:00
commit 5925b43698
34 changed files with 779 additions and 50 deletions

View File

@ -729,3 +729,16 @@ addEventListener("pageshow", function(event) {
});
}
});
addMessageListener("ContextMenu:SaveVideoFrameAsImage", (message) => {
let video = message.objects.target;
let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
let ctxDraw = canvas.getContext("2d");
ctxDraw.drawImage(video, 0, 0);
sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage:Result", {
dataURL: canvas.toDataURL("image/jpeg", ""),
});
});

View File

@ -1037,6 +1037,7 @@ nsContextMenu.prototype = {
},
saveVideoFrameAsImage: function () {
let mm = this.browser.messageManager;
let name = "";
if (this.mediaURL) {
try {
@ -1048,13 +1049,18 @@ nsContextMenu.prototype = {
}
if (!name)
name = "snapshot.jpg";
var video = this.target;
var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
var ctxDraw = canvas.getContext("2d");
ctxDraw.drawImage(video, 0, 0);
saveImageURL(canvas.toDataURL("image/jpeg", ""), name, "SaveImageTitle", true, false, document.documentURIObject, this.target.ownerDocument);
mm.sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage", {}, {
target: this.target,
});
let onMessage = (message) => {
mm.removeMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
let dataURL = message.data.dataURL;
saveImageURL(dataURL, name, "SaveImageTitle", true, false,
document.documentURIObject, this.target.ownerDocument);
};
mm.addMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
},
fullScreenVideo: function () {

View File

@ -16,9 +16,6 @@ support-files =
browser_star_hsts.sjs
browser_tab_dragdrop2_frame1.xul
browser_web_channel.html
bug564387.html
bug564387_video1.ogv
bug564387_video1.ogv^headers^
bug592338.html
bug792517-2.html
bug792517.html
@ -82,6 +79,9 @@ support-files =
test_wyciwyg_copying.html
title_test.svg
video.ogg
web_video.html
web_video1.ogv
web_video1.ogv^headers^
zoom_test.html
test_no_mcb_on_http_site_img.html
test_no_mcb_on_http_site_img.css
@ -411,6 +411,7 @@ skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates c
skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
[browser_save_video.js]
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (event.target)
[browser_save_video_frame.js]
[browser_scope.js]
[browser_searchSuggestionUI.js]
skip-if = e10s

View File

@ -24,6 +24,7 @@ add_task(function*() {
let actionURL = makeActionURI("switchtab", {url: "about:about"}).spec;
yield check_a11y_label("% about", "about:about " + actionURL + " Tab");
gURLBar.popup.hidePopup();
yield promisePopupHidden(gURLBar.popup);
gBrowser.removeTab(tab);
});

View File

@ -12,7 +12,7 @@ function test() {
waitForExplicitFinish();
var fileName;
gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/bug564387.html");
gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html");
gBrowser.addEventListener("pageshow", function pageShown(event) {
if (event.target.location == "about:blank")
@ -66,7 +66,7 @@ function test() {
function onTransferComplete(downloadSuccess) {
ok(downloadSuccess, "Video file should have been downloaded successfully");
is(fileName, "Bug564387-expectedName.ogv",
is(fileName, "web-video1-expectedName.ogv",
"Video file name is correctly retrieved from Content-Disposition http header");
finish();

View File

@ -0,0 +1,126 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const VIDEO_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html";
/**
* mockTransfer.js provides a utility that lets us mock out
* the "Save File" dialog.
*/
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
this);
/**
* Creates and returns an nsIFile for a new temporary save
* directory.
*
* @return nsIFile
*/
function createTemporarySaveDirectory() {
let saveDir = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties)
.get("TmpD", Ci.nsIFile);
saveDir.append("testsavedir");
if (!saveDir.exists())
saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
return saveDir;
}
/**
* MockTransfer exposes a "mockTransferCallback" global which
* allows us to define a callback to be called once the mock file
* selector has selected where to save the file.
*/
function waitForTransferComplete() {
return new Promise((resolve) => {
mockTransferCallback = () => {
ok(true, "Transfer completed");
resolve();
}
});
}
/**
* Given some browser, loads a framescript that right-clicks
* on the video1 element to spawn a contextmenu.
*/
function rightClickVideo(browser) {
let frame_script = () => {
const Ci = Components.interfaces;
let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let document = content.document;
let video = document.getElementById("video1");
let rect = video.getBoundingClientRect();
/* Synthesize a click in the center of the video. */
let left = rect.left + (rect.width / 2);
let top = rect.top + (rect.height / 2);
utils.sendMouseEvent("contextmenu", left, top,
2, /* aButton */
1, /* aClickCount */
0 /* aModifiers */);
};
let mm = browser.messageManager;
mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
}
/**
* Loads a page with a <video> element, right-clicks it and chooses
* to save a frame screenshot to the disk. Completes once we've
* verified that the frame has been saved to disk.
*/
add_task(function*() {
let MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window);
// Create the folder the video will be saved into.
let destDir = createTemporarySaveDirectory();
let destFile = destDir.clone();
MockFilePicker.displayDirectory = destDir;
MockFilePicker.showCallback = function(fp) {
destFile.append(fp.defaultString);
MockFilePicker.returnFiles = [destFile];
MockFilePicker.filterIndex = 1; // kSaveAsType_URL
};
mockTransferRegisterer.register();
// Make sure that we clean these things up when we're done.
registerCleanupFunction(function () {
mockTransferRegisterer.unregister();
MockFilePicker.cleanup();
destDir.remove(true);
});
let tab = gBrowser.addTab();
gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
info("Loading video tab");
yield promiseTabLoadEvent(tab, VIDEO_URL);
info("Video tab loaded.");
let video = browser.contentDocument.getElementById("video1");
let context = document.getElementById("contentAreaContextMenu");
let popupPromise = promisePopupShown(context);
info("Synthesizing right-click on video element");
rightClickVideo(browser);
info("Waiting for popup to fire popupshown.");
yield popupPromise;
info("Popup fired popupshown");
let saveSnapshotCommand = document.getElementById("context-video-saveimage");
let promiseTransfer = waitForTransferComplete()
info("Firing save snapshot command");
saveSnapshotCommand.doCommand();
context.hidePopup();
info("Waiting for transfer completion");
yield promiseTransfer;
info("Transfer complete");
gBrowser.removeTab(tab);
});

View File

@ -1,11 +0,0 @@
<html>
<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=564387 -->
<head>
<title> Bug 564387 test</title>
</head>
<body>
Testing for Mozilla Bug: 564387
<br>
<video src="bug564387_video1.ogv" id="video1"> </video>
</body>
</html>

View File

@ -1,3 +0,0 @@
Content-Disposition: filename="Bug564387-expectedName.ogv"
Content-Type: video/ogg

View File

@ -744,7 +744,7 @@ function is_element_hidden(element, msg) {
function promisePopupEvent(popup, eventSuffix) {
let endState = {shown: "open", hidden: "closed"}[eventSuffix];
if (popup.state = endState)
if (popup.state == endState)
return Promise.resolve();
let eventType = "popup" + eventSuffix;

View File

@ -0,0 +1,10 @@
<html>
<head>
<title>Document with Web Video</title>
</head>
<body>
This document has some web video in it.
<br>
<video src="web_video1.ogv" id="video1"> </video>
</body>
</html>

View File

@ -0,0 +1,3 @@
Content-Disposition: filename="web-video1-expectedName.ogv"
Content-Type: video/ogg

View File

@ -126,9 +126,9 @@ let gSyncPane = {
});
setEventListener("syncViewQuota", "command", gSyncPane.openQuotaDialog);
setEventListener("syncChangePassword", "command",
gSyncUtils.changePassword);
() => gSyncUtils.changePassword());
setEventListener("syncResetPassphrase", "command",
gSyncUtils.resetPassphrase);
() => gSyncUtils.resetPassphrase());
setEventListener("syncReset", "command", gSyncPane.resetSync);
setEventListener("syncAddDeviceLabel", "click", function () {
gSyncPane.openAddDevice();

View File

@ -65,6 +65,8 @@ support-files =
[browser_broadcast.js]
[browser_capabilities.js]
[browser_cleaner.js]
[browser_crashedTabs.js]
skip-if = !e10s || os == "linux" # Waiting on OMTC enabled by default on Linux (Bug 994541)
[browser_dying_cache.js]
[browser_dynamic_frames.js]
[browser_form_restore_events.js]

View File

@ -0,0 +1,328 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const PAGE_1 = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
const PAGE_2 = "data:text/html,<html><body>Another%20regular,%20everyday,%20normal%20page.";
/**
* Returns a Promise that resolves once a remote <xul:browser> has experienced
* a crash. Also does the job of cleaning up the minidump of the crash.
*
* @param browser
* The <xul:browser> that will crash
* @return Promise
*/
function crashBrowser(browser) {
// This frame script is injected into the remote browser, and used to
// intentionally crash the tab. We crash by using js-ctypes and dereferencing
// a bad pointer. The crash should happen immediately upon loading this
// frame script.
let frame_script = () => {
const Cu = Components.utils;
Cu.import("resource://gre/modules/ctypes.jsm");
let dies = function() {
let zero = new ctypes.intptr_t(8);
let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
badptr.contents
};
dump("Et tu, Brute?");
dies();
}
let crashCleanupPromise = new Promise((resolve, reject) => {
let observer = (subject, topic, data) => {
is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
ok(subject instanceof Ci.nsIPropertyBag2,
'Subject implements nsIPropertyBag2.');
// we might see this called as the process terminates due to previous tests.
// We are only looking for "abnormal" exits...
if (!subject.hasKey("abnormal")) {
info("This is a normal termination and isn't the one we are looking for...");
return;
}
let dumpID;
if ('nsICrashReporter' in Ci) {
dumpID = subject.getPropertyAsAString('dumpID');
ok(dumpID, "dumpID is present and not an empty string");
}
if (dumpID) {
let minidumpDirectory = getMinidumpDirectory();
removeFile(minidumpDirectory, dumpID + '.dmp');
removeFile(minidumpDirectory, dumpID + '.extra');
}
Services.obs.removeObserver(observer, 'ipc:content-shutdown');
resolve();
};
Services.obs.addObserver(observer, 'ipc:content-shutdown');
});
let aboutTabCrashedLoadPromise = new Promise((resolve, reject) => {
browser.addEventListener("AboutTabCrashedLoad", function onCrash() {
browser.removeEventListener("AboutTabCrashedLoad", onCrash, false);
resolve();
}, false, true);
});
// This frame script will crash the remote browser as soon as it is
// evaluated.
let mm = browser.messageManager;
mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
return Promise.all([crashCleanupPromise, aboutTabCrashedLoadPromise]);
}
/**
* Returns the directory where crash dumps are stored.
*
* @return nsIFile
*/
function getMinidumpDirectory() {
let dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
dir.append("minidumps");
return dir;
}
/**
* Removes a file from a directory. This is a no-op if the file does not
* exist.
*
* @param directory
* The nsIFile representing the directory to remove from.
* @param filename
* A string for the file to remove from the directory.
*/
function removeFile(directory, filename) {
let file = directory.clone();
file.append(filename);
if (file.exists()) {
file.remove(false);
}
}
/**
* Checks the documentURI of the root document of a remote browser
* to see if it equals URI. Returns a Promise that resolves if
* there is a match, and rejects with an error message if they
* do not match.
*
* @param browser
* The remote <xul:browser> to check the root document URI in.
* @param URI
* A string to match the root document URI against.
* @return Promise
*/
function promiseContentDocumentURIEquals(browser, URI) {
return new Promise((resolve, reject) => {
let frame_script = () => {
sendAsyncMessage("test:documenturi", {
uri: content.document.documentURI,
});
};
let mm = browser.messageManager;
mm.addMessageListener("test:documenturi", function onMessage(message) {
mm.removeMessageListener("test:documenturi", onMessage);
let contentURI = message.data.uri;
if (contentURI == URI) {
resolve();
} else {
reject(`Content has URI ${contentURI} which does not match ${URI}`);
}
});
mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
});
}
/**
* Checks the window.history.length of the root window of a remote
* browser to see if it equals length. Returns a Promise that resolves
* if there is a match, and rejects with an error message if they
* do not match.
*
* @param browser
* The remote <xul:browser> to check the root window.history.length
* @param length
* The expected history length
* @return Promise
*/
function promiseHistoryLength(browser, length) {
return new Promise((resolve, reject) => {
let frame_script = () => {
sendAsyncMessage("test:historylength", {
length: content.history.length,
});
};
let mm = browser.messageManager;
mm.addMessageListener("test:historylength", function onMessage(message) {
mm.removeMessageListener("test:historylength", onMessage);
let contentLength = message.data.length;
if (contentLength == length) {
resolve();
} else {
reject(`Content has window.history.length ${contentLength} which does ` +
`not equal expected ${length}`);
}
});
mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
});
}
/**
* Checks that if a tab crashes, that information about the tab crashed
* page does not get added to the tab history.
*/
add_task(function test_crash_page_not_in_history() {
let newTab = gBrowser.addTab();
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
yield promiseBrowserLoaded(browser);
browser.loadURI(PAGE_1);
yield promiseBrowserLoaded(browser);
TabState.flush(browser);
// Crash the tab
yield crashBrowser(browser);
// Flush out any notifications from the crashed browser.
TabState.flush(browser);
// Check the tab state and make sure the tab crashed page isn't
// mentioned.
let {entries} = JSON.parse(ss.getTabState(newTab));
is(entries.length, 1, "Should have a single history entry");
is(entries[0].url, PAGE_1,
"Single entry should be the page we visited before crashing");
gBrowser.removeTab(newTab);
});
/**
* Checks that if a tab crashes, that when we browse away from that page
* to a non-blacklisted site (so the browser becomes remote again), that
* we record history for that new visit.
*/
add_task(function test_revived_history_from_remote() {
let newTab = gBrowser.addTab();
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
yield promiseBrowserLoaded(browser);
browser.loadURI(PAGE_1);
yield promiseBrowserLoaded(browser);
TabState.flush(browser);
// Crash the tab
yield crashBrowser(browser);
// Flush out any notifications from the crashed browser.
TabState.flush(browser);
// Browse to a new site that will cause the browser to
// become remote again.
browser.loadURI(PAGE_2);
yield promiseBrowserLoaded(browser);
ok(browser.isRemoteBrowser, "Should be a remote browser");
TabState.flush(browser);
// Check the tab state and make sure the tab crashed page isn't
// mentioned.
let {entries} = JSON.parse(ss.getTabState(newTab));
is(entries.length, 2, "Should have two history entries");
is(entries[0].url, PAGE_1,
"First entry should be the page we visited before crashing");
is(entries[1].url, PAGE_2,
"Second entry should be the page we visited after crashing");
gBrowser.removeTab(newTab);
});
/**
* Checks that if a tab crashes, that when we browse away from that page
* to a blacklisted site (so the browser stays non-remote), that
* we record history for that new visit.
*/
add_task(function test_revived_history_from_non_remote() {
let newTab = gBrowser.addTab();
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
yield promiseBrowserLoaded(browser);
browser.loadURI(PAGE_1);
yield promiseBrowserLoaded(browser);
TabState.flush(browser);
// Crash the tab
yield crashBrowser(browser);
// Flush out any notifications from the crashed browser.
TabState.flush(browser);
// Browse to a new site that will not cause the browser to
// become remote again.
browser.loadURI("about:mozilla");
yield promiseBrowserLoaded(browser);
ok(!browser.isRemoteBrowser, "Should not be a remote browser");
TabState.flush(browser);
// Check the tab state and make sure the tab crashed page isn't
// mentioned.
let {entries} = JSON.parse(ss.getTabState(newTab));
is(entries.length, 2, "Should have two history entries");
is(entries[0].url, PAGE_1,
"First entry should be the page we visited before crashing");
is(entries[1].url, "about:mozilla",
"Second entry should be the page we visited after crashing");
gBrowser.removeTab(newTab);
});
/**
* Checks that we can revive a crashed tab back to the page that
* it was on when it crashed.
*/
add_task(function test_revive_tab_from_session_store() {
let newTab = gBrowser.addTab();
gBrowser.selectedTab = newTab;
let browser = newTab.linkedBrowser;
ok(browser.isRemoteBrowser, "Should be a remote browser");
yield promiseBrowserLoaded(browser);
browser.loadURI(PAGE_1);
yield promiseBrowserLoaded(browser);
browser.loadURI(PAGE_2);
yield promiseBrowserLoaded(browser);
TabState.flush(browser);
// Crash the tab
yield crashBrowser(browser);
// Flush out any notifications from the crashed browser.
TabState.flush(browser);
// Use SessionStore to revive the tab
SessionStore.reviveCrashedTab(newTab);
yield promiseBrowserLoaded(browser);
// We can't just check browser.currentURI.spec, because from
// the outside, a crashed tab has the same URI as the page
// it crashed on (much like an about:neterror page). Instead,
// we have to use the documentURI on the content.
yield promiseContentDocumentURIEquals(browser, PAGE_2);
// We should also have two entries in the browser history.
yield promiseHistoryLength(browser, 2);
gBrowser.removeTab(newTab);
});

View File

@ -320,6 +320,29 @@ function whenBrowserUnloaded(aBrowser, aContainer, aCallback = next) {
executeSoon(aCallback);
}, true);
}
/**
* Loads a page in a browser, and returns a Promise that
* resolves once a "load" event has been fired within that
* browser.
*
* @param browser
* The browser to load the page in.
* @param uri
* The URI to load.
*
* @return Promise
*/
function loadPage(browser, uri) {
return new Promise((resolve, reject) => {
browser.addEventListener("load", function onLoad(event) {
browser.removeEventListener("load", onLoad, true);
resolve();
}, true);
browser.loadURI(uri);
});
}
function promiseBrowserUnloaded(aBrowser, aContainer) {
return new Promise(resolve => {
whenBrowserUnloaded(aBrowser, aContainer, resolve);

View File

@ -8925,6 +8925,9 @@ AC_SUBST(MOZ_FOLD_LIBS)
AC_SUBST(MOZ_ENABLE_SZIP)
AC_SUBST(MOZ_SZIP_FLAGS)
dnl Host JavaScript runtime, if any, to use during cross compiles.
AC_SUBST(JS_BINARY)
if test "$MOZ_DEBUG"; then
MOZ_EM_DEBUG=1
fi

View File

@ -16,6 +16,7 @@ import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@ -24,6 +25,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnClickListener, GeckoEventListener {
private static final String LOGTAG = "FindInPageBar";
private static final String REQUEST_ID = "FindInPageBar";
private final Context mContext;
@ -181,13 +183,23 @@ public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnC
/**
* Request find operation, and update matchCount results (current count and total).
*/
private void sendRequestToFinderHelper(String request, String searchString) {
private void sendRequestToFinderHelper(final String request, final String searchString) {
GeckoAppShell.sendRequestToGecko(new GeckoRequest(request, searchString) {
@Override
public void onResponse(NativeJSObject nativeJSObject) {
final int total = nativeJSObject.optInt("total", 0);
final int current = nativeJSObject.optInt("current", 0);
updateResult(total, current);
}
public void onError() {
// Gecko didn't respond due to state change, javascript error, etc.
updateResult(0, 0);
Log.d(LOGTAG, "No response from Gecko on request to match string: [" +
searchString + "]");
}
private void updateResult(int total, int current) {
final Boolean statusVisibility = (total > 0);
final String statusText = current + "/" + total;

View File

@ -7,6 +7,7 @@ package org.mozilla.gecko.fxa.activities;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.Engaged;
import org.mozilla.gecko.fxa.login.State;
@ -18,6 +19,7 @@ import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
import android.accounts.Account;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
@ -33,6 +35,7 @@ public class FxAccountConfirmAccountActivity extends FxAccountAbstractActivity i
// Set in onCreate.
protected TextView verificationLinkTextView;
protected View resendLink;
protected View changeEmail;
// Set in onResume.
protected AndroidFxAccount fxAccount;
@ -56,6 +59,8 @@ public class FxAccountConfirmAccountActivity extends FxAccountAbstractActivity i
verificationLinkTextView = (TextView) ensureFindViewById(null, R.id.verification_link_text, "verification link text");
resendLink = ensureFindViewById(null, R.id.resend_confirmation_email_link, "resend confirmation email link");
resendLink.setOnClickListener(this);
changeEmail = ensureFindViewById(null, R.id.change_confirmation_email_link, "change confirmation email address");
changeEmail.setOnClickListener(this);
View backToBrowsingButton = ensureFindViewById(null, R.id.button, "back to browsing button");
backToBrowsingButton.setOnClickListener(new OnClickListener() {
@ -159,6 +164,12 @@ public class FxAccountConfirmAccountActivity extends FxAccountAbstractActivity i
@Override
public void onClick(View v) {
FxAccountCodeResender.resendCode(this, fxAccount);
if (v.equals(resendLink)) {
FxAccountCodeResender.resendCode(this, fxAccount);
} else if (v.equals(changeEmail)) {
final Account account = fxAccount.getAndroidAccount();
Intent intent = new Intent(this, FxAccountGetStartedActivity.class);
FxAccountStatusActivity.maybeDeleteAndroidAccount(this, account, intent);
}
}
}

View File

@ -10,6 +10,7 @@ import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.LocaleAware.LocaleAwareActivity;
import org.mozilla.gecko.LocaleAware.LocaleAwareFragmentActivity;
import android.accounts.Account;
@ -113,7 +114,7 @@ public class FxAccountStatusActivity extends LocaleAwareFragmentActivity {
* Helper function to maybe remove the given Android account.
*/
@SuppressLint("InlinedApi")
public void maybeDeleteAndroidAccount(final Account account) {
public static void maybeDeleteAndroidAccount(final Activity activity, final Account account, final Intent intent) {
if (account == null) {
Logger.warn(LOG_TAG, "Trying to delete null account; ignoring request.");
return;
@ -123,11 +124,13 @@ public class FxAccountStatusActivity extends LocaleAwareFragmentActivity {
@Override
public void run(AccountManagerFuture<Boolean> future) {
Logger.info(LOG_TAG, "Account " + Utils.obfuscateEmail(account.name) + " removed.");
final Activity activity = FxAccountStatusActivity.this;
final String text = activity.getResources().getString(R.string.fxaccount_remove_account_toast, account.name);
Toast.makeText(activity, text, Toast.LENGTH_LONG).show();
finish();
if (intent != null) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(intent);
}
activity.finish();
}
};
@ -138,20 +141,20 @@ public class FxAccountStatusActivity extends LocaleAwareFragmentActivity {
final int icon;
if (AppConstants.Versions.feature11Plus) {
final TypedValue typedValue = new TypedValue();
getTheme().resolveAttribute(android.R.attr.alertDialogIcon, typedValue, true);
activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, typedValue, true);
icon = typedValue.resourceId;
} else {
icon = android.R.drawable.ic_dialog_alert;
}
final AlertDialog dialog = new AlertDialog.Builder(this)
final AlertDialog dialog = new AlertDialog.Builder(activity)
.setTitle(R.string.fxaccount_remove_account_dialog_title)
.setIcon(icon)
.setMessage(R.string.fxaccount_remove_account_dialog_message)
.setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
AccountManager.get(FxAccountStatusActivity.this).removeAccount(account, callback, null);
AccountManager.get(activity).removeAccount(account, callback, null);
}
})
.setNegativeButton(android.R.string.cancel, new Dialog.OnClickListener() {
@ -174,7 +177,7 @@ public class FxAccountStatusActivity extends LocaleAwareFragmentActivity {
}
if (itemId == R.id.remove_account) {
maybeDeleteAndroidAccount(FirefoxAccounts.getFirefoxAccount(this));
maybeDeleteAndroidAccount(this, FirefoxAccounts.getFirefoxAccount(this), null);
return true;
}

View File

@ -229,7 +229,6 @@ public class HistoryPanel extends HomeFragment {
final SpannableStringBuilder hintBuilder = formatHintText(hintText);
if (hintBuilder != null) {
emptyHint.setText(hintBuilder);
emptyHint.setText(hintBuilder);
emptyHint.setMovementMethod(LinkMovementMethod.getInstance());
emptyHint.setVisibility(View.VISIBLE);

View File

@ -400,9 +400,9 @@ size. -->
<!ENTITY home_last_tabs_empty "Your recent tabs show up here.">
<!ENTITY home_open_all "Open all">
<!ENTITY home_most_recent_empty "Websites you visited most recently show up here.">
<!-- Localization note (home_most_recent_emptyhint): "Psst" is a sound that might be used to attract someone's attention unobtrusively, and intended to hint at "Private browsing" to the user.
<!-- Localization note (home_most_recent_emptyhint2): "Psst" is a sound that might be used to attract someone's attention unobtrusively, and intended to hint at Private Browsing to the user.
The placeholders &formatS1; and &formatS2; are used to mark the location of text underlining. -->
<!ENTITY home_most_recent_emptyhint "Psst: &formatS1;Private browsing&formatS2; mode won\'t save your history.">
<!ENTITY home_most_recent_emptyhint2 "Psst: using a &formatS1;New Private Tab&formatS2; won\'t save your history.">
<!ENTITY home_reading_list_empty "Articles you save for later show up here.">
<!-- Localization note (home_reading_list_hint): The "TIP" string is synonymous to "hint", "clue", etc. This string is displayed
as an advisory message on how to add content to the reading list when the reading list empty.

View File

@ -167,6 +167,7 @@
<!-- Localization note: &formatS; is the Firefox Account's email address. -->
<!ENTITY fxaccount_confirm_account_verification_link 'A verification link has been sent to &formatS;'>
<!ENTITY fxaccount_confirm_account_resend_email 'Resend email'>
<!ENTITY fxaccount_confirm_account_change_email 'Forget this email address?'>
<!ENTITY fxaccount_confirm_account_verification_link_sent2 'Verification email sent'>
<!ENTITY fxaccount_confirm_account_verification_link_not_sent2 'Couldn\&apos;t send verification email'>

View File

@ -45,6 +45,11 @@
style="@style/FxAccountLinkItem"
android:text="@string/fxaccount_confirm_account_resend_email" />
<TextView
android:id="@+id/change_confirmation_email_link"
style="@style/FxAccountLinkItem"
android:text="@string/fxaccount_confirm_account_change_email" />
<LinearLayout style="@style/FxAccountSpacer" />
<ImageView

View File

@ -355,7 +355,7 @@
<string name="home_last_tabs_empty">&home_last_tabs_empty;</string>
<string name="home_open_all">&home_open_all;</string>
<string name="home_most_recent_empty">&home_most_recent_empty;</string>
<string name="home_most_recent_emptyhint">&home_most_recent_emptyhint;</string>
<string name="home_most_recent_emptyhint">&home_most_recent_emptyhint2;</string>
<string name="home_reading_list_empty">&home_reading_list_empty;</string>
<string name="home_reading_list_hint">&home_reading_list_hint2;</string>
<string name="home_reading_list_hint_accessible">&home_reading_list_hint_accessible;</string>

View File

@ -0,0 +1,20 @@
#! /bin/sh
# 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/.
# Wrapper for running SpiderMonkey js shell in automation with correct
# LD_LIBRARY_PATH.
# We don't have a reference to topsrcdir at this point, but we are invoked as
# "$topsrcdir/mobile/android/config/js_wrapper.sh" so we can extract topsrcdir
# from $0.
topsrcdir=`cd \`dirname $0\`/../../..; pwd`
JS_BINARY="$topsrcdir/jsshell/js"
LD_LIBRARY_PATH="$topsrcdir/jsshell${LD_LIBRARY_PATH+:$LD_LIBRARY_PATH}"
export LD_LIBRARY_PATH
# Pass through all arguments and exit with status from js shell.
exec "$JS_BINARY" "$@"

View File

@ -53,3 +53,5 @@ ac_add_options --enable-stdcxx-compat
mk_add_options "export ANT_HOME=$topsrcdir/apache-ant"
mk_add_options "export PATH=$topsrcdir/apache-ant/bin:$PATH"
JS_BINARY="$topsrcdir/mobile/android/config/js_wrapper.sh"

View File

@ -28,5 +28,12 @@
"digest": "e28b7a12fbbef02ad742958df8dd356ea2adb8ef79e95cd8eb8dbc953eb4cc11888969dac7d636187fd3ace9c63d9a6bc3d7795021c1d811a843e413fe5e52c9",
"algorithm": "sha512",
"filename": "apache-ant-bin.tar.bz2"
},
{
"size": 4906080,
"digest": "d735544e039da89382c53b2302b7408d4610247b4f8b5cdc5a4d5a8ec5470947b19e8ea7f7a37e78222e661347e394e0030d81f41534138b527b14e9c4e55634",
"algorithm": "sha512",
"filename": "jsshell.tar.xz",
"unpack": "True"
}
]

View File

@ -28,5 +28,12 @@
"digest": "e28b7a12fbbef02ad742958df8dd356ea2adb8ef79e95cd8eb8dbc953eb4cc11888969dac7d636187fd3ace9c63d9a6bc3d7795021c1d811a843e413fe5e52c9",
"algorithm": "sha512",
"filename": "apache-ant-bin.tar.bz2"
},
{
"size": 4906080,
"digest": "d735544e039da89382c53b2302b7408d4610247b4f8b5cdc5a4d5a8ec5470947b19e8ea7f7a37e78222e661347e394e0030d81f41534138b527b14e9c4e55634",
"algorithm": "sha512",
"filename": "jsshell.tar.xz",
"unpack": "True"
}
]

View File

@ -28,5 +28,12 @@
"digest": "e28b7a12fbbef02ad742958df8dd356ea2adb8ef79e95cd8eb8dbc953eb4cc11888969dac7d636187fd3ace9c63d9a6bc3d7795021c1d811a843e413fe5e52c9",
"algorithm": "sha512",
"filename": "apache-ant-bin.tar.bz2"
},
{
"size": 4906080,
"digest": "d735544e039da89382c53b2302b7408d4610247b4f8b5cdc5a4d5a8ec5470947b19e8ea7f7a37e78222e661347e394e0030d81f41534138b527b14e9c4e55634",
"algorithm": "sha512",
"filename": "jsshell.tar.xz",
"unpack": "True"
}
]

View File

@ -35,6 +35,13 @@ MOZ_PACKAGER_MINIFY=1
include $(topsrcdir)/toolkit/mozapps/installer/packager.mk
# Note that JS_BINARY can be defined in packager.mk, so this test must come
# after including that file. MOZ_PACKAGER_MINIFY_JS is used in packager.mk, but
# since recipe evaluation is deferred, we can set it here after the inclusion.
ifneq (,$(JS_BINARY))
MOZ_PACKAGER_MINIFY_JS=1
endif
ifeq (bundle, $(MOZ_FS_LAYOUT))
BINPATH = $(_BINPATH)
DEFINES += -DAPPNAME=$(_APPNAME)

View File

@ -154,6 +154,7 @@
<string name="fxaccount_confirm_account_header">&fxaccount_confirm_account_header;</string>
<string name="fxaccount_confirm_account_verification_link">&fxaccount_confirm_account_verification_link;</string>
<string name="fxaccount_confirm_account_resend_email">&fxaccount_confirm_account_resend_email;</string>
<string name="fxaccount_confirm_account_change_email">&fxaccount_confirm_account_change_email;</string>
<string name="fxaccount_confirm_account_verification_link_sent">&fxaccount_confirm_account_verification_link_sent2;</string>
<string name="fxaccount_confirm_account_verification_link_not_sent">&fxaccount_confirm_account_verification_link_not_sent2;</string>

View File

@ -37,7 +37,7 @@ else:
__all__ = ['jsmin', 'JavascriptMinify']
__version__ = '2.0.3'
__version__ = '2.0.11'
def jsmin(js):
@ -72,7 +72,20 @@ class JavascriptMinify(object):
def minify(self, instream=None, outstream=None):
if instream and outstream:
self.ins, self.outs = instream, outstream
write = self.outs.write
self.is_return = False
self.return_buf = ''
def write(char):
# all of this is to support literal regular expressions.
# sigh
if char in 'return':
self.return_buf += char
self.is_return = self.return_buf == 'return'
self.outs.write(char)
if self.is_return:
self.return_buf = ''
read = self.ins.read
space_strings = "abcdefghijklmnopqrstuvwxyz"\
@ -82,6 +95,7 @@ class JavascriptMinify(object):
newlineend_strings = enders + space_strings
do_newline = False
do_space = False
escape_slash_count = 0
doing_single_comment = False
previous_before_comment = ''
doing_multi_comment = False
@ -90,13 +104,18 @@ class JavascriptMinify(object):
quote_buf = []
previous = read(1)
if previous == '\\':
escape_slash_count += 1
next1 = read(1)
if previous == '/':
if next1 == '/':
doing_single_comment = True
elif next1 == '*':
doing_multi_comment = True
previous = next1
next1 = read(1)
else:
in_re = True # literal regex at start of script
write(previous)
elif not previous:
return
@ -116,11 +135,15 @@ class JavascriptMinify(object):
last = next1.strip()
if not (doing_single_comment or doing_multi_comment)\
and last not in ('', '/'):
if in_quote:
write(''.join(quote_buf))
write(last)
break
if doing_multi_comment:
if next1 == '*' and next2 == '/':
doing_multi_comment = False
if previous_before_comment and previous_before_comment in space_strings:
do_space = True
next2 = read(1)
elif doing_single_comment:
if next1 in '\r\n':
@ -164,9 +187,17 @@ class JavascriptMinify(object):
or previous_non_space > '~') \
and (next2 in space_strings or next2 > '~'):
do_space = True
elif previous_non_space in '-+' and next2 == previous_non_space:
# protect against + ++ or - -- sequences
do_space = True
elif self.is_return and next2 == '/':
# returning a regex...
write(' ')
elif next1 == '/':
if do_space:
write(' ')
if in_re:
if previous != '\\':
if previous != '\\' or (not escape_slash_count % 2) or next2 in 'gimy':
in_re = False
write('/')
elif next2 == '/':
@ -174,8 +205,12 @@ class JavascriptMinify(object):
previous_before_comment = previous_non_space
elif next2 == '*':
doing_multi_comment = True
previous_before_comment = previous_non_space
previous = next1
next1 = next2
next2 = read(1)
else:
in_re = previous_non_space in '(,=:[?!&|'
in_re = previous_non_space in '(,=:[?!&|;' or self.is_return # literal regular expression
write('/')
else:
if do_space:
@ -184,12 +219,19 @@ class JavascriptMinify(object):
if do_newline:
write('\n')
do_newline = False
write(next1)
if not in_re and next1 in "'\"":
in_quote = next1
quote_buf = []
previous = next1
next1 = next2
if previous >= '!':
previous_non_space = previous
if previous == '\\':
escape_slash_count += 1
else:
escape_slash_count = 0

View File

@ -95,9 +95,36 @@ another thing;"""
expected = r"""function foo(){alert('crud');}"""
self.assertMinified(js, expected)
def testBlockCommentStartingWithSlash(self):
self.assertMinified('A; /*/ comment */ B', 'A;B')
def testBlockCommentEndingWithSlash(self):
self.assertMinified('A; /* comment /*/ B', 'A;B')
def testLeadingBlockCommentStartingWithSlash(self):
self.assertMinified('/*/ comment */ A', 'A')
def testLeadingBlockCommentEndingWithSlash(self):
self.assertMinified('/* comment /*/ A', 'A')
def testEmptyBlockComment(self):
self.assertMinified('/**/ A', 'A')
def testBlockCommentMultipleOpen(self):
self.assertMinified('/* A /* B */ C', 'C')
def testJustAComment(self):
self.assertMinified(' // a comment', '')
def test_issue_10(self):
js = '''
files = [{name: value.replace(/^.*\\\\/, '')}];
// comment
A
'''
expected = '''files=[{name:value.replace(/^.*\\\\/,'')}]; A'''
self.assertMinified(js, expected)
def testRe(self):
js = r'''
var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
@ -153,6 +180,20 @@ Element.cleanWhitespace(element);"""
expected = r"""inspect:function(useDoubleQuotes){var escapedString=this.gsub(/[\x00-\x1f\\]/,function(match){var character=String.specialChar[match[0]];return character?character:'\\u00'+match[0].charCodeAt().toPaddedString(2,16);});if(useDoubleQuotes)return'"'+escapedString.replace(/"/g,'\\"')+'"';return"'"+escapedString.replace(/'/g,'\\\'')+"'";},toJSON:function(){return this.inspect(true);},unfilterJSON:function(filter){return this.sub(filter||Prototype.JSONFilter,'#{1}');},"""
self.assertMinified(js, expected)
def testLiteralRe(self):
js = r"""
myString.replace(/\\/g, '/');
console.log("hi");
"""
expected = r"""myString.replace(/\\/g,'/');console.log("hi");"""
self.assertMinified(js, expected)
js = r''' return /^data:image\//i.test(url) ||
/^(https?|ftp|file|about|chrome|resource):/.test(url);
'''
expected = r'''return /^data:image\//i.test(url)||/^(https?|ftp|file|about|chrome|resource):/.test(url);'''
self.assertMinified(js, expected)
def testNoBracesWithComment(self):
js = r"""
onSuccess: function(transport) {
@ -247,6 +288,68 @@ var foo = "hey";
self.assertMinified('x.replace(/\//, "_")// slash to underscore',
'x.replace(/\//,"_")')
def testSlashesNearComments(self):
original = '''
{ a: n / 2, }
// comment
'''
expected = '''{a:n/2,}'''
self.assertMinified(original, expected)
def testReturn(self):
original = '''
return foo;//comment
return bar;'''
expected = 'return foo; return bar;'
self.assertMinified(original, expected)
def test_space_plus(self):
original = '"s" + ++e + "s"'
expected = '"s"+ ++e+"s"'
self.assertMinified(original, expected)
def test_no_final_newline(self):
original = '"s"'
expected = '"s"'
self.assertMinified(original, expected)
def test_space_with_regex_repeats(self):
original = '/(NaN| {2}|^$)/.test(a)&&(a="M 0 0");'
self.assertMinified(original, original) # there should be nothing jsmin can do here
def test_space_with_regex_repeats_not_at_start(self):
original = 'aaa;/(NaN| {2}|^$)/.test(a)&&(a="M 0 0");'
self.assertMinified(original, original) # there should be nothing jsmin can do here
def test_space_in_regex(self):
original = '/a (a)/.test("a")'
self.assertMinified(original, original)
def test_angular_1(self):
original = '''var /** holds major version number for IE or NaN for real browsers */
msie,
jqLite, // delay binding since jQuery could be loaded after us.'''
minified = jsmin.jsmin(original)
self.assertTrue('var msie' in minified)
def test_angular_2(self):
original = 'var/* comment */msie;'
expected = 'var msie;'
self.assertMinified(original, expected)
def test_angular_3(self):
original = 'var /* comment */msie;'
expected = 'var msie;'
self.assertMinified(original, expected)
def test_angular_4(self):
original = 'var /* comment */ msie;'
expected = 'var msie;'
self.assertMinified(original, expected)
def test_angular_4(self):
original = 'a/b'
self.assertMinified(original, original)
if __name__ == '__main__':
unittest.main()