зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1078432 - Use Android print service to enable cloud printing r=sebastian
This commit is contained in:
Родитель
b480648efc
Коммит
3f6cf75fef
|
@ -10,6 +10,7 @@ import org.mozilla.gecko.AppConstants.Versions;
|
|||
import org.mozilla.gecko.DynamicToolbar.PinReason;
|
||||
import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
||||
import org.mozilla.gecko.PrintHelper;
|
||||
import org.mozilla.gecko.Tabs.TabEvents;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.TransitionsTracker;
|
||||
|
@ -3167,6 +3168,7 @@ public class BrowserApp extends GeckoApp
|
|||
final MenuItem share = aMenu.findItem(R.id.share);
|
||||
final MenuItem quickShare = aMenu.findItem(R.id.quickshare);
|
||||
final MenuItem saveAsPDF = aMenu.findItem(R.id.save_as_pdf);
|
||||
final MenuItem print = aMenu.findItem(R.id.print);
|
||||
final MenuItem charEncoding = aMenu.findItem(R.id.char_encoding);
|
||||
final MenuItem findInPage = aMenu.findItem(R.id.find_in_page);
|
||||
final MenuItem desktopMode = aMenu.findItem(R.id.desktop_mode);
|
||||
|
@ -3192,6 +3194,7 @@ public class BrowserApp extends GeckoApp
|
|||
share.setEnabled(false);
|
||||
quickShare.setEnabled(false);
|
||||
saveAsPDF.setEnabled(false);
|
||||
print.setEnabled(false);
|
||||
findInPage.setEnabled(false);
|
||||
|
||||
// NOTE: Use MenuUtils.safeSetEnabled because some actions might
|
||||
|
@ -3344,10 +3347,13 @@ public class BrowserApp extends GeckoApp
|
|||
final boolean privateTabVisible = RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_PRIVATE_BROWSING);
|
||||
MenuUtils.safeSetVisible(aMenu, R.id.new_private_tab, privateTabVisible);
|
||||
|
||||
// Disable save as PDF for about:home and xul pages.
|
||||
saveAsPDF.setEnabled(!(isAboutHome(tab) ||
|
||||
// Disable PDF generation (save and print) for about:home and xul pages.
|
||||
boolean allowPDF = (!(isAboutHome(tab) ||
|
||||
tab.getContentType().equals("application/vnd.mozilla.xul+xml") ||
|
||||
tab.getContentType().startsWith("video/")));
|
||||
saveAsPDF.setEnabled(allowPDF);
|
||||
print.setEnabled(allowPDF);
|
||||
print.setVisible(Versions.feature19Plus && AppConstants.NIGHTLY_BUILD);
|
||||
|
||||
// Disable find in page for about:home, since it won't work on Java content.
|
||||
findInPage.setEnabled(!isAboutHome(tab));
|
||||
|
@ -3471,6 +3477,11 @@ public class BrowserApp extends GeckoApp
|
|||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.print) {
|
||||
PrintHelper.printPDF(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.settings) {
|
||||
intent = new Intent(this, GeckoPreferences.class);
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
package org.mozilla.gecko;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
|
@ -47,6 +46,7 @@ import org.mozilla.gecko.util.EventCallback;
|
|||
import org.mozilla.gecko.util.GeckoRequest;
|
||||
import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.IOUtils;
|
||||
import org.mozilla.gecko.util.NativeEventListener;
|
||||
import org.mozilla.gecko.util.NativeJSContainer;
|
||||
import org.mozilla.gecko.util.NativeJSObject;
|
||||
|
@ -1004,13 +1004,6 @@ public class GeckoAppShell
|
|||
return type + "/" + subType;
|
||||
}
|
||||
|
||||
static void safeStreamClose(Closeable stream) {
|
||||
try {
|
||||
if (stream != null)
|
||||
stream.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
|
||||
static boolean isUriSafeForScheme(Uri aUri) {
|
||||
// Bug 794034 - We don't want to pass MWI or USSD codes to the
|
||||
// dialer, and ensure the Uri class doesn't parse a URI
|
||||
|
@ -2576,13 +2569,13 @@ public class GeckoAppShell
|
|||
// Only alter the intent when we're sure everything has worked
|
||||
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile));
|
||||
} finally {
|
||||
safeStreamClose(is);
|
||||
IOUtils.safeStreamClose(is);
|
||||
}
|
||||
}
|
||||
} catch(IOException ex) {
|
||||
// If something went wrong, we'll just leave the intent un-changed
|
||||
} finally {
|
||||
safeStreamClose(os);
|
||||
IOUtils.safeStreamClose(os);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.util.GeckoRequest;
|
||||
import org.mozilla.gecko.util.IOUtils;
|
||||
import org.mozilla.gecko.util.NativeJSObject;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.print.PrintAttributes;
|
||||
import android.print.PrintDocumentAdapter;
|
||||
import android.print.PrintDocumentAdapter.LayoutResultCallback;
|
||||
import android.print.PrintDocumentAdapter.WriteResultCallback;
|
||||
import android.print.PrintDocumentInfo;
|
||||
import android.print.PrintManager;
|
||||
import android.print.PageRange;
|
||||
import android.util.Log;
|
||||
|
||||
public class PrintHelper {
|
||||
private static final String LOGTAG = "GeckoPrintUtils";
|
||||
|
||||
public static void printPDF(final Context context) {
|
||||
GeckoAppShell.sendRequestToGecko(new GeckoRequest("Print:PDF", new JSONObject()) {
|
||||
@Override
|
||||
public void onResponse(NativeJSObject nativeJSObject) {
|
||||
final String filePath = nativeJSObject.getString("file");
|
||||
final String title = nativeJSObject.getString("title");
|
||||
finish(context, filePath, title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(NativeJSObject error) {
|
||||
// Gecko didn't respond due to state change, javascript error, etc.
|
||||
Log.d(LOGTAG, "No response from Gecko on request to generate a PDF");
|
||||
}
|
||||
|
||||
private void finish(final Context context, final String filePath, final String title) {
|
||||
PrintManager printManager = (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
|
||||
String jobName = title;
|
||||
|
||||
// The adapter methods are all called on the UI thread by the PrintManager. Put the heavyweight code
|
||||
// in onWrite on the background thread.
|
||||
PrintDocumentAdapter pda = new PrintDocumentAdapter() {
|
||||
@Override
|
||||
public void onWrite(final PageRange[] pages, final ParcelFileDescriptor destination, final CancellationSignal cancellationSignal, final WriteResultCallback callback) {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
InputStream input = null;
|
||||
OutputStream output = null;
|
||||
|
||||
try {
|
||||
File pdfFile = new File(filePath);
|
||||
input = new FileInputStream(pdfFile);
|
||||
output = new FileOutputStream(destination.getFileDescriptor());
|
||||
|
||||
byte[] buf = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = input.read(buf)) > 0) {
|
||||
output.write(buf, 0, bytesRead);
|
||||
}
|
||||
|
||||
callback.onWriteFinished(new PageRange[] { PageRange.ALL_PAGES });
|
||||
|
||||
// File is not really deleted until the input stream closes it
|
||||
pdfFile.delete();
|
||||
} catch (FileNotFoundException ee) {
|
||||
Log.d(LOGTAG, "Unable to find the temporary PDF file.");
|
||||
} catch (IOException ioe) {
|
||||
Log.e(LOGTAG, "IOException while transferring temporary PDF file: ", ioe);
|
||||
} finally {
|
||||
IOUtils.safeStreamClose(input);
|
||||
IOUtils.safeStreamClose(output);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras){
|
||||
if (cancellationSignal.isCanceled()) {
|
||||
callback.onLayoutCancelled();
|
||||
return;
|
||||
}
|
||||
|
||||
PrintDocumentInfo pdi = new PrintDocumentInfo.Builder(filePath).setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).build();
|
||||
callback.onLayoutFinished(pdi, true);
|
||||
}
|
||||
};
|
||||
|
||||
printManager.print(jobName, pda, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -347,6 +347,7 @@ size. -->
|
|||
<!ENTITY share_title "Share via">
|
||||
<!ENTITY share_image_failed "Unable to share this image">
|
||||
<!ENTITY save_as_pdf "Save as PDF">
|
||||
<!ENTITY print "Print">
|
||||
<!ENTITY find_in_page "Find in Page">
|
||||
<!ENTITY desktop_mode "Request Desktop Site">
|
||||
<!ENTITY page "Page">
|
||||
|
|
|
@ -419,6 +419,7 @@ gbjar.sources += [
|
|||
'preferences/SearchPreferenceCategory.java',
|
||||
'preferences/SyncPreference.java',
|
||||
'PrefsHelper.java',
|
||||
'PrintHelper.java',
|
||||
'PrivateTab.java',
|
||||
'prompts/ColorPickerInput.java',
|
||||
'prompts/IconGridInput.java',
|
||||
|
|
|
@ -72,6 +72,9 @@
|
|||
<item android:id="@+id/save_as_pdf"
|
||||
android:title="@string/save_as_pdf"/>
|
||||
|
||||
<item android:id="@+id/print"
|
||||
android:title="@string/print"/>
|
||||
|
||||
<item android:id="@+id/add_search_engine"
|
||||
android:title="@string/contextmenu_add_search_engine"/>
|
||||
|
||||
|
|
|
@ -72,6 +72,9 @@
|
|||
<item android:id="@+id/save_as_pdf"
|
||||
android:title="@string/save_as_pdf"/>
|
||||
|
||||
<item android:id="@+id/print"
|
||||
android:title="@string/print"/>
|
||||
|
||||
<item android:id="@+id/add_search_engine"
|
||||
android:title="@string/contextmenu_add_search_engine"/>
|
||||
|
||||
|
|
|
@ -73,6 +73,9 @@
|
|||
<item android:id="@+id/save_as_pdf"
|
||||
android:title="@string/save_as_pdf"/>
|
||||
|
||||
<item android:id="@+id/print"
|
||||
android:title="@string/print"/>
|
||||
|
||||
<item android:id="@+id/add_search_engine"
|
||||
android:title="@string/contextmenu_add_search_engine"/>
|
||||
|
||||
|
|
|
@ -39,6 +39,10 @@
|
|||
<item android:id="@+id/save_as_pdf"
|
||||
android:title="@string/save_as_pdf" />
|
||||
|
||||
<item android:id="@+id/print"
|
||||
android:visible="false"
|
||||
android:title="@string/print" />
|
||||
|
||||
<item android:id="@+id/find_in_page"
|
||||
android:title="@string/find_in_page" />
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@
|
|||
<string name="share_title">&share_title;</string>
|
||||
<string name="share_image_failed">&share_image_failed;</string>
|
||||
<string name="save_as_pdf">&save_as_pdf;</string>
|
||||
<string name="print">&print;</string>
|
||||
<string name="find_in_page">&find_in_page;</string>
|
||||
<string name="find_matchcase">&find_matchcase;</string>
|
||||
<string name="desktop_mode">&desktop_mode;</string>
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.mozilla.gecko.util;
|
|||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
|
@ -108,4 +109,11 @@ public class IOUtils {
|
|||
|
||||
return newBytes;
|
||||
}
|
||||
|
||||
public static void safeStreamClose(Closeable stream) {
|
||||
try {
|
||||
if (stream != null)
|
||||
stream.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
var PrintHelper = {
|
||||
init: function() {
|
||||
Services.obs.addObserver(this, "Print:PDF", false);
|
||||
},
|
||||
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
let browser = BrowserApp.selectedBrowser;
|
||||
|
||||
switch (aTopic) {
|
||||
case "Print:PDF":
|
||||
Messaging.handleRequest(aTopic, aData, (data) => {
|
||||
return this.generatePDF(browser);
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
generatePDF: function(aBrowser) {
|
||||
// Create the final destination file location
|
||||
let fileName = ContentAreaUtils.getDefaultFileName(aBrowser.contentTitle, aBrowser.currentURI, null, null);
|
||||
fileName = fileName.trim() + ".pdf";
|
||||
|
||||
let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
file.append(fileName);
|
||||
file.createUnique(file.NORMAL_FILE_TYPE, parseInt("666", 8));
|
||||
|
||||
let printSettings = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(Ci.nsIPrintSettingsService).newPrintSettings;
|
||||
printSettings.printSilent = true;
|
||||
printSettings.showPrintProgress = false;
|
||||
printSettings.printBGImages = false;
|
||||
printSettings.printBGColors = false;
|
||||
printSettings.printToFile = true;
|
||||
printSettings.toFileName = file.path;
|
||||
printSettings.printFrameType = Ci.nsIPrintSettings.kFramesAsIs;
|
||||
printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
|
||||
|
||||
let webBrowserPrint = aBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebBrowserPrint);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
webBrowserPrint.print(printSettings, {
|
||||
onStateChange: function(webProgress, request, stateFlags, status) {
|
||||
// We get two STATE_STOP calls, one for STATE_IS_DOCUMENT and one for STATE_IS_NETWORK
|
||||
if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP && stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
|
||||
if (Components.isSuccessCode(status)) {
|
||||
// Send the details to Java
|
||||
resolve({ file: file.path, title: fileName });
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
},
|
||||
onProgressChange: function () {},
|
||||
onLocationChange: function () {},
|
||||
onStatusChange: function () {},
|
||||
onSecurityChange: function () {},
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
|
@ -153,6 +153,7 @@ let lazilyLoadedObserverScripts = [
|
|||
["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"],
|
||||
["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
|
||||
["Reader", ["Reader:FetchContent", "Reader:Added", "Reader:Removed"], "chrome://browser/content/Reader.js"],
|
||||
["PrintHelper", ["Print:PDF"], "chrome://browser/content/PrintHelper.js"],
|
||||
];
|
||||
if (AppConstants.NIGHTLY_BUILD) {
|
||||
lazilyLoadedObserverScripts.push(
|
||||
|
|
|
@ -41,6 +41,7 @@ chrome.jar:
|
|||
content/MemoryObserver.js (content/MemoryObserver.js)
|
||||
content/ConsoleAPI.js (content/ConsoleAPI.js)
|
||||
content/PluginHelper.js (content/PluginHelper.js)
|
||||
content/PrintHelper.js (content/PrintHelper.js)
|
||||
content/OfflineApps.js (content/OfflineApps.js)
|
||||
content/MasterPassword.js (content/MasterPassword.js)
|
||||
content/FindHelper.js (content/FindHelper.js)
|
||||
|
|
|
@ -112,6 +112,43 @@ let Messaging = {
|
|||
this.sendRequest(aMessage);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles a request from Java, using the given listener method.
|
||||
* This is mainly an internal method used by the RequestHandler object, but can be
|
||||
* used in nsIObserver.observe implmentations that fall outside the normal usage
|
||||
* patterns.
|
||||
*
|
||||
* @param aTopic The string name of the message
|
||||
* @param aData The data sent to the observe method from Java
|
||||
* @param aListener A function that takes a JSON data argument and returns a
|
||||
* response which is sent to Java.
|
||||
*/
|
||||
handleRequest: Task.async(function* (aTopic, aData, aListener) {
|
||||
let wrapper = JSON.parse(aData);
|
||||
|
||||
try {
|
||||
let response = yield aListener(wrapper.data);
|
||||
if (typeof response !== "object" || response === null) {
|
||||
throw new Error("Gecko request listener did not return an object");
|
||||
}
|
||||
|
||||
Messaging.sendRequest({
|
||||
type: "Gecko:Request" + wrapper.id,
|
||||
response: response
|
||||
});
|
||||
} catch (e) {
|
||||
Cu.reportError("Error in Messaging handler for " + aTopic + ": " + e);
|
||||
|
||||
Messaging.sendRequest({
|
||||
type: "Gecko:Request" + wrapper.id,
|
||||
error: {
|
||||
message: e.message || (e && e.toString()),
|
||||
stack: e.stack || Components.stack.formattedStack,
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let requestHandler = {
|
||||
|
@ -139,30 +176,8 @@ let requestHandler = {
|
|||
Services.obs.removeObserver(this, aMessage);
|
||||
},
|
||||
|
||||
observe: Task.async(function* (aSubject, aTopic, aData) {
|
||||
let wrapper = JSON.parse(aData);
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
let listener = this._listeners[aTopic];
|
||||
|
||||
try {
|
||||
let response = yield listener(wrapper.data);
|
||||
if (typeof response !== "object" || response === null) {
|
||||
throw new Error("Gecko request listener did not return an object");
|
||||
}
|
||||
|
||||
Messaging.sendRequest({
|
||||
type: "Gecko:Request" + wrapper.id,
|
||||
response: response
|
||||
});
|
||||
} catch (e) {
|
||||
Cu.reportError("Error in Messaging handler for " + aTopic + ": " + e);
|
||||
|
||||
Messaging.sendRequest({
|
||||
type: "Gecko:Request" + wrapper.id,
|
||||
error: {
|
||||
message: e.message || (e && e.toString()),
|
||||
stack: e.stack || Components.stack.formattedStack,
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
Messaging.handleRequest(aTopic, aData, listener);
|
||||
}
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче