merge m-c to fx-team

This commit is contained in:
Tim Taubert 2012-07-04 14:16:49 +02:00
commit f440f14fe4
19 changed files with 959 additions and 78 deletions

View File

@ -1073,6 +1073,11 @@ pref("devtools.ruleview.enabled", true);
// Enable the Scratchpad tool.
pref("devtools.scratchpad.enabled", true);
// The maximum number of recently-opened files stored.
// Setting this preference to 0 will not clear any recent files, but rather hide
// the 'Open Recent'-menu.
pref("devtools.scratchpad.recentFilesMax", 10);
// Enable the Style Editor.
pref("devtools.styleeditor.enabled", true);
pref("devtools.styleeditor.transitions", true);

View File

@ -101,7 +101,7 @@ var ScratchpadManager = {
}
let win = Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank",
SCRATCHPAD_WINDOW_FEATURES, params);
// Only add shutdown observer if we've opened a scratchpad window
// Only add the shutdown observer if we've opened a scratchpad window.
ShutdownObserver.init();
return win;

View File

@ -31,6 +31,7 @@ const SCRATCHPAD_CONTEXT_CONTENT = 1;
const SCRATCHPAD_CONTEXT_BROWSER = 2;
const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
const BUTTON_POSITION_SAVE = 0;
const BUTTON_POSITION_CANCEL = 1;
const BUTTON_POSITION_DONT_SAVE = 2;
@ -160,7 +161,7 @@ var Scratchpad = {
* @param object aState
* An object with filename and executionContext properties.
*/
setState: function SP_getState(aState)
setState: function SP_setState(aState)
{
if (aState.filename) {
this.setFilename(aState.filename);
@ -615,19 +616,203 @@ var Scratchpad = {
/**
* Open a file to edit in the Scratchpad.
*
* @param integer aIndex
* Optional integer: clicked menuitem in the 'Open Recent'-menu.
*/
openFile: function SP_openFile()
openFile: function SP_openFile(aIndex)
{
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, this.strings.GetStringFromName("openFile.title"),
Ci.nsIFilePicker.modeOpen);
fp.defaultString = "";
if (fp.show() != Ci.nsIFilePicker.returnCancel) {
this.setFilename(fp.file.path);
this.importFromFile(fp.file, false);
let fp;
if (!aIndex && aIndex !== 0) {
fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, this.strings.GetStringFromName("openFile.title"),
Ci.nsIFilePicker.modeOpen);
fp.defaultString = "";
}
if (aIndex > -1 || fp.show() != Ci.nsIFilePicker.returnCancel) {
this.promptSave(function(aCloseFile, aSaved, aStatus) {
let shouldOpen = aCloseFile;
if (aSaved && !Components.isSuccessCode(aStatus)) {
shouldOpen = false;
}
if (shouldOpen) {
this._skipClosePrompt = true;
let file;
if (fp) {
file = fp.file;
} else {
file = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
let filePath = this.getRecentFiles()[aIndex];
file.initWithPath(filePath);
}
this.setFilename(file.path);
this.importFromFile(file, false);
this.setRecentFile(file);
}
}.bind(this));
}
},
/**
* Get recent files.
*
* @return Array
* File paths.
*/
getRecentFiles: function SP_getRecentFiles()
{
let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX);
let branch = Services.prefs.
getBranch("devtools.scratchpad.");
let filePaths = [];
if (branch.prefHasUserValue("recentFilePaths")) {
filePaths = JSON.parse(branch.getCharPref("recentFilePaths"));
}
return filePaths;
},
/**
* Save a recent file in a JSON parsable string.
*
* @param nsILocalFile aFile
* The nsILocalFile we want to save as a recent file.
*/
setRecentFile: function SP_setRecentFile(aFile)
{
let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX);
if (maxRecent < 1) {
return;
}
let filePaths = this.getRecentFiles();
let filesCount = filePaths.length;
let pathIndex = filePaths.indexOf(aFile.path);
// We are already storing this file in the list of recent files.
if (pathIndex > -1) {
// If it's already the most recent file, we don't have to do anything.
if (pathIndex === (filesCount - 1)) {
// Updating the menu to clear the disabled state from the wrong menuitem
// in rare cases when two or more Scratchpad windows are open and the
// same file has been opened in two or more windows.
this.populateRecentFilesMenu();
return;
}
// It is not the most recent file. Remove it from the list, we add it as
// the most recent farther down.
filePaths.splice(pathIndex, 1);
}
// If we are not storing the file and the 'recent files'-list is full,
// remove the oldest file from the list.
else if (filesCount === maxRecent) {
filePaths.shift();
}
filePaths.push(aFile.path);
let branch = Services.prefs.
getBranch("devtools.scratchpad.");
branch.setCharPref("recentFilePaths", JSON.stringify(filePaths));
return;
},
/**
* Populates the 'Open Recent'-menu.
*/
populateRecentFilesMenu: function SP_populateRecentFilesMenu()
{
let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX);
let recentFilesMenu = document.getElementById("sp-open_recent-menu");
if (maxRecent < 1) {
recentFilesMenu.setAttribute("hidden", true);
return;
}
let recentFilesPopup = recentFilesMenu.firstChild;
let filePaths = this.getRecentFiles();
let filename = this.getState().filename;
recentFilesMenu.setAttribute("disabled", true);
while (recentFilesPopup.hasChildNodes()) {
recentFilesPopup.removeChild(recentFilesPopup.firstChild);
}
if (filePaths.length > 0) {
recentFilesMenu.removeAttribute("disabled");
// Print out menuitems with the most recent file first.
for (let i = filePaths.length - 1; i >= 0; --i) {
let menuitem = document.createElement("menuitem");
menuitem.setAttribute("type", "radio");
menuitem.setAttribute("label", filePaths[i]);
if (filePaths[i] === filename) {
menuitem.setAttribute("checked", true);
menuitem.setAttribute("disabled", true);
}
menuitem.setAttribute("oncommand", "Scratchpad.openFile(" + i + ");");
recentFilesPopup.appendChild(menuitem);
}
recentFilesPopup.appendChild(document.createElement("menuseparator"));
let clearItems = document.createElement("menuitem");
clearItems.setAttribute("id", "sp-menu-clear_recent");
clearItems.setAttribute("label",
this.strings.
GetStringFromName("clearRecentMenuItems.label"));
clearItems.setAttribute("command", "sp-cmd-clearRecentFiles");
recentFilesPopup.appendChild(clearItems);
}
},
/**
* Clear all recent files.
*/
clearRecentFiles: function SP_clearRecentFiles()
{
Services.prefs.clearUserPref("devtools.scratchpad.recentFilePaths");
},
/**
* Handle changes to the 'PREF_RECENT_FILES_MAX'-preference.
*/
handleRecentFileMaxChange: function SP_handleRecentFileMaxChange()
{
let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX);
let menu = document.getElementById("sp-open_recent-menu");
// Hide the menu if the 'PREF_RECENT_FILES_MAX'-pref is set to zero or less.
if (maxRecent < 1) {
menu.setAttribute("hidden", true);
} else {
if (menu.hasAttribute("hidden")) {
if (!menu.firstChild.hasChildNodes()) {
this.populateRecentFilesMenu();
}
menu.removeAttribute("hidden");
}
let filePaths = this.getRecentFiles();
if (maxRecent < filePaths.length) {
let branch = Services.prefs.
getBranch("devtools.scratchpad.");
let diff = filePaths.length - maxRecent;
filePaths.splice(0, diff);
branch.setCharPref("recentFilePaths", JSON.stringify(filePaths));
}
}
},
/**
* Save the textbox content to the currently open file.
*
@ -646,6 +831,7 @@ var Scratchpad = {
this.exportToFile(file, true, false, function(aStatus) {
if (Components.isSuccessCode(aStatus)) {
this.editor.dirty = false;
this.setRecentFile(file);
}
if (aCallback) {
aCallback(aStatus);
@ -671,6 +857,7 @@ var Scratchpad = {
this.exportToFile(fp.file, true, false, function(aStatus) {
if (Components.isSuccessCode(aStatus)) {
this.editor.dirty = false;
this.setRecentFile(fp.file);
}
if (aCallback) {
aCallback(aStatus);
@ -827,6 +1014,9 @@ var Scratchpad = {
this.initialized = true;
this._triggerObservers("Ready");
this.populateRecentFilesMenu();
PreferenceObserver.init();
},
/**
@ -887,6 +1077,8 @@ var Scratchpad = {
this.resetContext();
this.editor.removeEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
this._onDirtyChanged);
PreferenceObserver.uninit();
this.editor.destroy();
this.editor = null;
this.initialized = false;
@ -1063,6 +1255,48 @@ var Scratchpad = {
},
};
/**
* The PreferenceObserver listens for preference changes while Scratchpad is
* running.
*/
var PreferenceObserver = {
_initialized: false,
init: function PO_init()
{
if (this._initialized) {
return;
}
this.branch = Services.prefs.getBranch("devtools.scratchpad.");
this.branch.addObserver("", this, false);
this._initialized = true;
},
observe: function PO_observe(aMessage, aTopic, aData)
{
if (aTopic != "nsPref:changed") {
return;
}
if (aData == "recentFilesMax") {
Scratchpad.handleRecentFileMaxChange();
}
else if (aData == "recentFilePaths") {
Scratchpad.populateRecentFilesMenu();
}
},
uninit: function PO_uninit () {
if (!this.branch) {
return;
}
this.branch.removeObserver("", this);
this.branch = null;
}
};
XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function () {
return Services.strings.createBundle(SCRATCHPAD_L10N);
});

View File

@ -30,6 +30,7 @@
<commandset id="sp-commandset">
<command id="sp-cmd-newWindow" oncommand="Scratchpad.openScratchpad();"/>
<command id="sp-cmd-openFile" oncommand="Scratchpad.openFile();"/>
<command id="sp-cmd-clearRecentFiles" oncommand="Scratchpad.clearRecentFiles();"/>
<command id="sp-cmd-save" oncommand="Scratchpad.saveFile();"/>
<command id="sp-cmd-saveas" oncommand="Scratchpad.saveFileAs();"/>
@ -117,6 +118,11 @@
command="sp-cmd-openFile"
key="sp-key-open"
accesskey="&openFileCmd.accesskey;"/>
<menu id="sp-open_recent-menu" label="&openRecentMenu.label;"
accesskey="&openRecentMenu.accesskey;"
disabled="true">
<menupopup id="sp-menu-open_recentPopup"/>
</menu>
<menuitem id="sp-menu-save"
label="&saveFileCmd.label;"
accesskey="&saveFileCmd.accesskey;"

View File

@ -32,6 +32,7 @@ _BROWSER_TEST_FILES = \
browser_scratchpad_bug650345_find_ui.js \
browser_scratchpad_bug714942_goto_line_ui.js \
browser_scratchpad_bug_650760_help_key.js \
browser_scratchpad_bug_651942_recent_files.js \
head.js \
libs:: $(_BROWSER_TEST_FILES)

View File

@ -0,0 +1,314 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let tempScope = {};
Cu.import("resource://gre/modules/NetUtil.jsm", tempScope);
Cu.import("resource://gre/modules/FileUtils.jsm", tempScope);
let NetUtil = tempScope.NetUtil;
let FileUtils = tempScope.FileUtils;
// Reference to the Scratchpad object.
let gScratchpad;
// References to the temporary nsIFiles.
let gFile01;
let gFile02;
let gFile03;
let gFile04;
// lists of recent files.
var lists = {
recentFiles01: null,
recentFiles02: null,
recentFiles03: null,
recentFiles04: null,
};
// Temporary file names.
let gFileName01 = "file01_ForBug651942.tmp"
let gFileName02 = "file02_ForBug651942.tmp"
let gFileName03 = "file03_ForBug651942.tmp"
let gFileName04 = "file04_ForBug651942.tmp"
// Content for the temporary files.
let gFileContent;
let gFileContent01 = "hello.world.01('bug651942');";
let gFileContent02 = "hello.world.02('bug651942');";
let gFileContent03 = "hello.world.03('bug651942');";
let gFileContent04 = "hello.world.04('bug651942');";
function startTest()
{
gScratchpad = gScratchpadWindow.Scratchpad;
gFile01 = createAndLoadTemporaryFile(gFile01, gFileName01, gFileContent01);
gFile02 = createAndLoadTemporaryFile(gFile02, gFileName02, gFileContent02);
gFile03 = createAndLoadTemporaryFile(gFile03, gFileName03, gFileContent03);
}
// Test to see if the three files we created in the 'startTest()'-method have
// been added to the list of recent files.
function testAddedToRecent()
{
lists.recentFiles01 = gScratchpad.getRecentFiles();
is(lists.recentFiles01.length, 3,
"Temporary files created successfully and added to list of recent files.");
// Create a 4th file, this should clear the oldest file.
gFile04 = createAndLoadTemporaryFile(gFile04, gFileName04, gFileContent04);
}
// We have opened a 4th file. Test to see if the oldest recent file was removed,
// and that the other files were reordered successfully.
function testOverwriteRecent()
{
lists.recentFiles02 = gScratchpad.getRecentFiles();
is(lists.recentFiles02[0], lists.recentFiles01[1],
"File02 was reordered successfully in the 'recent files'-list.");
is(lists.recentFiles02[1], lists.recentFiles01[2],
"File03 was reordered successfully in the 'recent files'-list.");
isnot(lists.recentFiles02[2], lists.recentFiles01[2],
"File04: was added successfully.");
// Open the oldest recent file.
gScratchpad.openFile(0);
}
// We have opened the "oldest"-recent file. Test to see if it is now the most
// recent file, and that the other files were reordered successfully.
function testOpenOldestRecent()
{
lists.recentFiles03 = gScratchpad.getRecentFiles();
is(lists.recentFiles02[0], lists.recentFiles03[2],
"File04 was reordered successfully in the 'recent files'-list.");
is(lists.recentFiles02[1], lists.recentFiles03[0],
"File03 was reordered successfully in the 'recent files'-list.");
is(lists.recentFiles02[2], lists.recentFiles03[1],
"File02 was reordered successfully in the 'recent files'-list.");
Services.prefs.setIntPref("devtools.scratchpad.recentFilesMax", 0);
}
// The "devtools.scratchpad.recentFilesMax"-preference was set to zero (0).
// This should disable the "Open Recent"-menu by hiding it (this should not
// remove any files from the list). Test to see if it's been hidden.
function testHideMenu()
{
let menu = gScratchpadWindow.document.getElementById("sp-open_recent-menu");
ok(menu.hasAttribute("hidden"), "The menu was hidden successfully.");
Services.prefs.setIntPref("devtools.scratchpad.recentFilesMax", 1);
}
// We have set the recentFilesMax-pref to one (1), this enables the feature,
// removes the two oldest files, rebuilds the menu and removes the
// "hidden"-attribute from it. Test to see if this works.
function testChangedMaxRecent()
{
let menu = gScratchpadWindow.document.getElementById("sp-open_recent-menu");
ok(!menu.hasAttribute("hidden"), "The menu is visible. \\o/");
lists.recentFiles04 = gScratchpad.getRecentFiles();
is(lists.recentFiles04.length, 1,
"Two recent files were successfully removed from the 'recent files'-list");
let doc = gScratchpadWindow.document;
let popup = doc.getElementById("sp-menu-open_recentPopup");
let menuitemLabel = popup.children[0].getAttribute("label");
let correctMenuItem = false;
if (menuitemLabel === lists.recentFiles03[2] &&
menuitemLabel === lists.recentFiles04[0]) {
correctMenuItem = true;
}
is(correctMenuItem, true,
"Two recent files were successfully removed from the 'Open Recent'-menu");
gScratchpad.clearRecentFiles();
}
// We have cleared the last file. Test to see if the last file was removed,
// the menu is empty and was disabled successfully.
function testClearedAll()
{
let doc = gScratchpadWindow.document;
let menu = doc.getElementById("sp-open_recent-menu");
let popup = doc.getElementById("sp-menu-open_recentPopup");
is(gScratchpad.getRecentFiles().length, 0,
"All recent files removed successfully.");
is(popup.children.length, 0, "All menuitems removed successfully.");
ok(menu.hasAttribute("disabled"),
"No files in the menu, it was disabled successfully.");
finishTest();
}
function createAndLoadTemporaryFile(aFile, aFileName, aFileContent)
{
// Create a temporary file.
aFile = FileUtils.getFile("TmpD", [aFileName]);
aFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
// Write the temporary file.
let fout = Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
fout.init(aFile.QueryInterface(Ci.nsILocalFile), 0x02 | 0x08 | 0x20,
0644, fout.DEFER_OPEN);
gScratchpad.setFilename(aFile.path);
gScratchpad.importFromFile(aFile.QueryInterface(Ci.nsILocalFile), true,
fileImported);
gScratchpad.saveFile(fileSaved);
return aFile;
}
function fileImported(aStatus)
{
ok(Components.isSuccessCode(aStatus),
"the temporary file was imported successfully with Scratchpad");
}
function fileSaved(aStatus)
{
ok(Components.isSuccessCode(aStatus),
"the temporary file was saved successfully with Scratchpad");
checkIfMenuIsPopulated();
}
function checkIfMenuIsPopulated()
{
let doc = gScratchpadWindow.document;
let expectedMenuitemCount = doc.getElementById("sp-menu-open_recentPopup").
children.length;
// The number of recent files stored, plus the separator and the
// clearRecentMenuItems-item.
let recentFilesPlusExtra = gScratchpad.getRecentFiles().length + 2;
if (expectedMenuitemCount > 2) {
is(expectedMenuitemCount, recentFilesPlusExtra,
"the recent files menu was populated successfully.");
}
}
/**
* The PreferenceObserver listens for preference changes while Scratchpad is
* running.
*/
var PreferenceObserver = {
_initialized: false,
_timesFired: 0,
set timesFired(aNewValue) {
this._timesFired = aNewValue;
},
get timesFired() {
return this._timesFired;
},
init: function PO_init()
{
if (this._initialized) {
return;
}
this.branch = Services.prefs.getBranch("devtools.scratchpad.");
this.branch.addObserver("", this, false);
this._initialized = true;
},
observe: function PO_observe(aMessage, aTopic, aData)
{
if (aTopic != "nsPref:changed") {
return;
}
switch (this.timesFired) {
case 0:
this.timesFired = 1;
break;
case 1:
this.timesFired = 2;
break;
case 2:
this.timesFired = 3;
testAddedToRecent();
break;
case 3:
this.timesFired = 4;
testOverwriteRecent();
break;
case 4:
this.timesFired = 5;
testOpenOldestRecent();
break;
case 5:
this.timesFired = 6;
testHideMenu();
break;
case 6:
this.timesFired = 7;
testChangedMaxRecent();
break;
case 7:
this.timesFired = 8;
testClearedAll();
break;
}
},
uninit: function PO_uninit () {
this.branch.removeObserver("", this);
}
};
function test()
{
waitForExplicitFinish();
registerCleanupFunction(function () {
gFile01.remove(false);
gFile01 = null;
gFile02.remove(false);
gFile02 = null;
gFile03.remove(false);
gFile03 = null;
gFile04.remove(false);
gFile04 = null;
lists.recentFiles01 = null;
lists.recentFiles02 = null;
lists.recentFiles03 = null;
lists.recentFiles04 = null;
gScratchpad = null;
PreferenceObserver.uninit();
Services.prefs.clearUserPref("devtools.scratchpad.recentFilesMax");
});
Services.prefs.setIntPref("devtools.scratchpad.recentFilesMax", 3);
// Initiate the preference observer after we have set the temporary recent
// files max for this test.
PreferenceObserver.init();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
openScratchpad(startTest);
}, true);
content.location = "data:text/html,<p>test recent files in Scratchpad";
}
function finishTest()
{
finish();
}

View File

@ -33,6 +33,9 @@
<!ENTITY openFileCmd.accesskey "O">
<!ENTITY openFileCmd.commandkey "o">
<!ENTITY openRecentMenu.label "Open Recent">
<!ENTITY openRecentMenu.accesskey "R">
<!ENTITY saveFileCmd.label "Save">
<!ENTITY saveFileCmd.accesskey "S">
<!ENTITY saveFileCmd.commandkey "s">

View File

@ -34,6 +34,10 @@ openFile.title=Open File
# open fails.
openFile.failed=Failed to read the file.
# LOCALIZATION NOTE (clearRecentMenuItems.label): This is the label for the
# menuitem in the 'Open Recent'-menu which clears all recent files.
clearRecentMenuItems.label=Clear Items
# LOCALIZATION NOTE (saveFileAs): This is the file picker title, when you save
# a file in Scratchpad.
saveFileAs=Save File As

View File

@ -9,7 +9,7 @@
interface nsIInputStream;
interface imgIContainer;
[scriptable, uuid(1f19a2ce-cf5c-4a6b-8ba7-63785b45053f)]
[scriptable, uuid(8e16f39e-7012-46bd-aa22-2a7a3265608f)]
interface imgITools : nsISupports
{
/**
@ -60,7 +60,8 @@ interface imgITools : nsISupports
* @param aMimeType
* Type of encoded image desired (eg "image/png").
* @param aWidth, aHeight
* The size (in pixels) desired for the resulting image.
* The size (in pixels) desired for the resulting image. Specify 0 to
* use the given image's width or height. Values must be >= 0.
* @param outputOptions
* Encoder-specific output options.
*/
@ -69,4 +70,32 @@ interface imgITools : nsISupports
in long aWidth,
in long aHeight,
[optional] in AString outputOptions);
/**
* encodeCroppedImage
* Caller provides an image container, and the mime type it should be
* encoded to. We return an input stream for the encoded image data.
* The encoded image is cropped to the specified dimensions.
*
* The given offset and size must not exceed the image bounds.
*
* @param aContainer
* An image container.
* @param aMimeType
* Type of encoded image desired (eg "image/png").
* @param aOffsetX, aOffsetY
* The crop offset (in pixels). Values must be >= 0.
* @param aWidth, aHeight
* The size (in pixels) desired for the resulting image. Specify 0 to
* use the given image's width or height. Values must be >= 0.
* @param outputOptions
* Encoder-specific output options.
*/
nsIInputStream encodeCroppedImage(in imgIContainer aContainer,
in ACString aMimeType,
in long aOffsetX,
in long aOffsetY,
in long aWidth,
in long aHeight,
[optional] in AString outputOptions);
};

View File

@ -102,12 +102,14 @@ NS_IMETHODIMP imgTools::EncodeImage(imgIContainer *aContainer,
const nsAString& aOutputOptions,
nsIInputStream **aStream)
{
return EncodeScaledImage(aContainer,
aMimeType,
0,
0,
aOutputOptions,
aStream);
nsresult rv;
// Use frame 0 from the image container.
nsRefPtr<gfxImageSurface> frame;
rv = GetFirstImageFrame(aContainer, getter_AddRefs(frame));
NS_ENSURE_SUCCESS(rv, rv);
return EncodeImageData(frame, aMimeType, aOutputOptions, aStream);
}
NS_IMETHODIMP imgTools::EncodeScaledImage(imgIContainer *aContainer,
@ -117,20 +119,111 @@ NS_IMETHODIMP imgTools::EncodeScaledImage(imgIContainer *aContainer,
const nsAString& aOutputOptions,
nsIInputStream **aStream)
{
nsresult rv;
bool doScaling = true;
PRUint8 *bitmapData;
PRUint32 bitmapDataLength, strideSize;
NS_ENSURE_ARG(aScaledWidth >= 0 && aScaledHeight >= 0);
// If no scaled size is specified, we'll just encode the image at its
// original size (no scaling).
if (aScaledWidth == 0 && aScaledHeight == 0) {
doScaling = false;
} else {
NS_ENSURE_ARG(aScaledWidth > 0);
NS_ENSURE_ARG(aScaledHeight > 0);
return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
}
// Use frame 0 from the image container.
nsRefPtr<gfxImageSurface> frame;
nsresult rv = GetFirstImageFrame(aContainer, getter_AddRefs(frame));
NS_ENSURE_SUCCESS(rv, rv);
PRInt32 frameWidth = frame->Width(), frameHeight = frame->Height();
// If the given width or height is zero we'll replace it with the image's
// original dimensions.
if (aScaledWidth == 0) {
aScaledWidth = frameWidth;
} else if (aScaledHeight == 0) {
aScaledHeight = frameHeight;
}
// Create a temporary image surface
nsRefPtr<gfxImageSurface> dest = new gfxImageSurface(gfxIntSize(aScaledWidth, aScaledHeight),
gfxASurface::ImageFormatARGB32);
gfxContext ctx(dest);
// Set scaling
gfxFloat sw = (double) aScaledWidth / frameWidth;
gfxFloat sh = (double) aScaledHeight / frameHeight;
ctx.Scale(sw, sh);
// Paint a scaled image
ctx.SetOperator(gfxContext::OPERATOR_SOURCE);
ctx.SetSource(frame);
ctx.Paint();
return EncodeImageData(dest, aMimeType, aOutputOptions, aStream);
}
NS_IMETHODIMP imgTools::EncodeCroppedImage(imgIContainer *aContainer,
const nsACString& aMimeType,
PRInt32 aOffsetX,
PRInt32 aOffsetY,
PRInt32 aWidth,
PRInt32 aHeight,
const nsAString& aOutputOptions,
nsIInputStream **aStream)
{
NS_ENSURE_ARG(aOffsetX >= 0 && aOffsetY >= 0 && aWidth >= 0 && aHeight >= 0);
// Offsets must be zero when no width and height are given or else we're out
// of bounds.
NS_ENSURE_ARG(aWidth + aHeight > 0 || aOffsetX + aOffsetY == 0);
// If no size is specified then we'll preserve the image's original dimensions
// and don't need to crop.
if (aWidth == 0 && aHeight == 0) {
return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
}
// Use frame 0 from the image container.
nsRefPtr<gfxImageSurface> frame;
nsresult rv = GetFirstImageFrame(aContainer, getter_AddRefs(frame));
NS_ENSURE_SUCCESS(rv, rv);
PRInt32 frameWidth = frame->Width(), frameHeight = frame->Height();
// If the given width or height is zero we'll replace it with the image's
// original dimensions.
if (aWidth == 0) {
aWidth = frameWidth;
} else if (aHeight == 0) {
aHeight = frameHeight;
}
// Check that the given crop rectangle is within image bounds.
NS_ENSURE_ARG(frameWidth >= aOffsetX + aWidth &&
frameHeight >= aOffsetY + aHeight);
// Create a temporary image surface
nsRefPtr<gfxImageSurface> dest = new gfxImageSurface(gfxIntSize(aWidth, aHeight),
gfxASurface::ImageFormatARGB32);
gfxContext ctx(dest);
// Set translate
ctx.Translate(gfxPoint(-aOffsetX, -aOffsetY));
// Paint a scaled image
ctx.SetOperator(gfxContext::OPERATOR_SOURCE);
ctx.SetSource(frame);
ctx.Paint();
return EncodeImageData(dest, aMimeType, aOutputOptions, aStream);
}
NS_IMETHODIMP imgTools::EncodeImageData(gfxImageSurface *aSurface,
const nsACString& aMimeType,
const nsAString& aOutputOptions,
nsIInputStream **aStream)
{
PRUint8 *bitmapData;
PRUint32 bitmapDataLength, strideSize;
// Get an image encoder for the media type
nsCAutoString encoderCID(
NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType);
@ -139,65 +232,39 @@ NS_IMETHODIMP imgTools::EncodeScaledImage(imgIContainer *aContainer,
if (!encoder)
return NS_IMAGELIB_ERROR_NO_ENCODER;
// Use frame 0 from the image container.
nsRefPtr<gfxImageSurface> frame;
rv = aContainer->CopyFrame(imgIContainer::FRAME_CURRENT, true,
getter_AddRefs(frame));
NS_ENSURE_SUCCESS(rv, rv);
if (!frame)
return NS_ERROR_NOT_AVAILABLE;
PRInt32 w = frame->Width(), h = frame->Height();
if (!w || !h)
bitmapData = aSurface->Data();
if (!bitmapData)
return NS_ERROR_FAILURE;
nsRefPtr<gfxImageSurface> dest;
strideSize = aSurface->Stride();
if (!doScaling) {
// If we're not scaling the image, use the actual width/height.
aScaledWidth = w;
aScaledHeight = h;
bitmapData = frame->Data();
if (!bitmapData)
return NS_ERROR_FAILURE;
strideSize = frame->Stride();
bitmapDataLength = aScaledHeight * strideSize;
} else {
// Prepare to draw a scaled version of the image to a temporary surface...
// Create a temporary image surface
dest = new gfxImageSurface(gfxIntSize(aScaledWidth, aScaledHeight),
gfxASurface::ImageFormatARGB32);
gfxContext ctx(dest);
// Set scaling
gfxFloat sw = (double) aScaledWidth / w;
gfxFloat sh = (double) aScaledHeight / h;
ctx.Scale(sw, sh);
// Paint a scaled image
ctx.SetOperator(gfxContext::OPERATOR_SOURCE);
ctx.SetSource(frame);
ctx.Paint();
bitmapData = dest->Data();
strideSize = dest->Stride();
bitmapDataLength = aScaledHeight * strideSize;
}
PRInt32 width = aSurface->Width(), height = aSurface->Height();
bitmapDataLength = height * strideSize;
// Encode the bitmap
rv = encoder->InitFromData(bitmapData,
bitmapDataLength,
aScaledWidth,
aScaledHeight,
strideSize,
imgIEncoder::INPUT_FORMAT_HOSTARGB,
aOutputOptions);
nsresult rv = encoder->InitFromData(bitmapData,
bitmapDataLength,
width,
height,
strideSize,
imgIEncoder::INPUT_FORMAT_HOSTARGB,
aOutputOptions);
NS_ENSURE_SUCCESS(rv, rv);
return CallQueryInterface(encoder, aStream);
}
NS_IMETHODIMP imgTools::GetFirstImageFrame(imgIContainer *aContainer,
gfxImageSurface **aSurface)
{
nsRefPtr<gfxImageSurface> frame;
nsresult rv = aContainer->CopyFrame(imgIContainer::FRAME_CURRENT, true,
getter_AddRefs(frame));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(frame, NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(frame->Width() && frame->Height(), NS_ERROR_FAILURE);
frame.forget(aSurface);
return NS_OK;
}

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "imgITools.h"
#include "gfxContext.h"
#define NS_IMGTOOLS_CID \
{ /* fd9a9e8a-a77b-496a-b7bb-263df9715149 */ \
@ -22,4 +23,13 @@ public:
imgTools();
virtual ~imgTools();
private:
NS_IMETHODIMP EncodeImageData(gfxImageSurface *aSurface,
const nsACString& aMimeType,
const nsAString& aOutputOptions,
nsIInputStream **aStream);
NS_IMETHODIMP GetFirstImageFrame(imgIContainer *aContainer,
gfxImageSurface **aSurface);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -431,6 +431,214 @@ referenceBytes = streamToArray(istream);
compareArrays(encodedBytes, referenceBytes);
/* ========== 15 ========== */
testnum++;
testdesc = "test cropping a JPG";
// 32x32 jpeg, 3494 bytes.
imgName = "image2.jpg";
inMimeType = "image/jpeg";
imgFile = do_get_file(imgName);
istream = getFileInputStream(imgFile);
do_check_eq(istream.available(), 3494);
outParam = {};
imgTools.decodeImageData(istream, inMimeType, outParam);
container = outParam.value;
// It's not easy to look at the pixel values from JS, so just
// check the container's size.
do_check_eq(container.width, 32);
do_check_eq(container.height, 32);
// encode a cropped image
istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 16, 16);
encodedBytes = streamToArray(istream);
// Get bytes for exected result
refName = "image2jpg16x16cropped.jpg";
refFile = do_get_file(refName);
istream = getFileInputStream(refFile);
do_check_eq(istream.available(), 879);
referenceBytes = streamToArray(istream);
// compare the encoder's output to the reference file.
compareArrays(encodedBytes, referenceBytes);
/* ========== 16 ========== */
testnum++;
testdesc = "test cropping a JPG with an offset";
// we'll reuse the container from the previous test
istream = imgTools.encodeCroppedImage(container, "image/jpeg", 16, 16, 16, 16);
encodedBytes = streamToArray(istream);
// Get bytes for exected result
refName = "image2jpg16x16cropped2.jpg";
refFile = do_get_file(refName);
istream = getFileInputStream(refFile);
do_check_eq(istream.available(), 878);
referenceBytes = streamToArray(istream);
// compare the encoder's output to the reference file.
compareArrays(encodedBytes, referenceBytes);
/* ========== 17 ========== */
testnum++;
testdesc = "test cropping a JPG without a given height";
// we'll reuse the container from the previous test
istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 16, 0);
encodedBytes = streamToArray(istream);
// Get bytes for exected result
refName = "image2jpg16x32cropped3.jpg";
refFile = do_get_file(refName);
istream = getFileInputStream(refFile);
do_check_eq(istream.available(), 1127);
referenceBytes = streamToArray(istream);
// compare the encoder's output to the reference file.
compareArrays(encodedBytes, referenceBytes);
/* ========== 18 ========== */
testnum++;
testdesc = "test cropping a JPG without a given width";
// we'll reuse the container from the previous test
istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 0, 16);
encodedBytes = streamToArray(istream);
// Get bytes for exected result
refName = "image2jpg32x16cropped4.jpg";
refFile = do_get_file(refName);
istream = getFileInputStream(refFile);
do_check_eq(istream.available(), 1135);
referenceBytes = streamToArray(istream);
// compare the encoder's output to the reference file.
compareArrays(encodedBytes, referenceBytes);
/* ========== 19 ========== */
testnum++;
testdesc = "test cropping a JPG without a given width and height";
// we'll reuse the container from the previous test
istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 0, 0);
encodedBytes = streamToArray(istream);
// Get bytes for exected result
refName = "image2jpg32x32.jpg";
refFile = do_get_file(refName);
istream = getFileInputStream(refFile);
do_check_eq(istream.available(), 1634);
referenceBytes = streamToArray(istream);
// compare the encoder's output to the reference file.
compareArrays(encodedBytes, referenceBytes);
/* ========== 20 ========== */
testnum++;
testdesc = "test scaling a JPG without a given width";
// we'll reuse the container from the previous test
istream = imgTools.encodeScaledImage(container, "image/jpeg", 0, 16);
encodedBytes = streamToArray(istream);
// Get bytes for exected result
refName = "image2jpg32x16scaled.jpg";
refFile = do_get_file(refName);
istream = getFileInputStream(refFile);
do_check_eq(istream.available(), 1227);
referenceBytes = streamToArray(istream);
// compare the encoder's output to the reference file.
compareArrays(encodedBytes, referenceBytes);
/* ========== 21 ========== */
testnum++;
testdesc = "test scaling a JPG without a given height";
// we'll reuse the container from the previous test
istream = imgTools.encodeScaledImage(container, "image/jpeg", 16, 0);
encodedBytes = streamToArray(istream);
// Get bytes for exected result
refName = "image2jpg16x32scaled.jpg";
refFile = do_get_file(refName);
istream = getFileInputStream(refFile);
do_check_eq(istream.available(), 1219);
referenceBytes = streamToArray(istream);
// compare the encoder's output to the reference file.
compareArrays(encodedBytes, referenceBytes);
/* ========== 22 ========== */
testnum++;
testdesc = "test scaling a JPG without a given width and height";
// we'll reuse the container from the previous test
istream = imgTools.encodeScaledImage(container, "image/jpeg", 0, 0);
encodedBytes = streamToArray(istream);
// Get bytes for exected result
refName = "image2jpg32x32.jpg";
refFile = do_get_file(refName);
istream = getFileInputStream(refFile);
do_check_eq(istream.available(), 1634);
referenceBytes = streamToArray(istream);
// compare the encoder's output to the reference file.
compareArrays(encodedBytes, referenceBytes);
/* ========== 22 ========== */
testnum++;
testdesc = "test invalid arguments for cropping";
var numErrors = 0;
try {
// width/height can't be negative
imgTools.encodeScaledImage(container, "image/jpeg", -1, -1);
} catch (e) { numErrors++; }
try {
// offsets can't be negative
imgTools.encodeCroppedImage(container, "image/jpeg", -1, -1, 16, 16);
} catch (e) { numErrors++; }
try {
// width/height can't be negative
imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, -1, -1);
} catch (e) { numErrors++; }
try {
// out of bounds
imgTools.encodeCroppedImage(container, "image/jpeg", 17, 17, 16, 16);
} catch (e) { numErrors++; }
try {
// out of bounds
imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 33, 33);
} catch (e) { numErrors++; }
try {
// out of bounds
imgTools.encodeCroppedImage(container, "image/jpeg", 1, 1, 0, 0);
} catch (e) { numErrors++; }
do_check_eq(numErrors, 6);
/* ========== bug 363986 ========== */
testnum = 363986;
testdesc = "test PNG and JPEG encoders' Read/ReadSegments methods";