Merge from m-c in order to fix e10s necko hangs caused by last m-c merge.

This commit is contained in:
Jason Duell 2010-03-19 23:55:40 -07:00
commit f78fd1ae1e
97 changed files with 3925 additions and 1068 deletions

View File

@ -25,7 +25,7 @@
#brandName {
font-weight: bold; font-size: larger;
}
}
#userAgent {
direction: ltr;

View File

@ -520,6 +520,7 @@
<toolbar id="TabsToolbar"
fullscreentoolbar="true"
ordinal="100"
collapsed="true">
<tabs id="tabbrowser-tabs" class="tabbrowser-tabs"
tabbrowser="content"

View File

@ -71,19 +71,14 @@ const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const STATE_RUNNING_STR = "running";
const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 megabytes
XPCOMUtils.defineLazyServiceGetter(this, "ConsoleSvc",
"@mozilla.org/consoleservice;1", "nsIConsoleService");
XPCOMUtils.defineLazyServiceGetter(this, "ObserverSvc",
"@mozilla.org/observer-service;1", "nsIObserverService");
function debug(aMsg) {
aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
ConsoleSvc.logStringMessage(aMsg);
Services.console.logStringMessage(aMsg);
}
/* :::::::: The Service ::::::::::::::: */
@ -152,8 +147,8 @@ SessionStartup.prototype = {
if (this._sessionType != Ci.nsISessionStartup.NO_SESSION) {
// wait for the first browser window to open
ObserverSvc.addObserver(this, "domwindowopened", true);
ObserverSvc.addObserver(this, "browser:purge-session-history", true);
Services.obs.addObserver(this, "domwindowopened", true);
Services.obs.addObserver(this, "browser:purge-session-history", true);
}
},
@ -163,18 +158,18 @@ SessionStartup.prototype = {
observe: function sss_observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "app-startup":
ObserverSvc.addObserver(this, "final-ui-startup", true);
ObserverSvc.addObserver(this, "quit-application", true);
Services.obs.addObserver(this, "final-ui-startup", true);
Services.obs.addObserver(this, "quit-application", true);
break;
case "final-ui-startup":
ObserverSvc.removeObserver(this, "final-ui-startup");
ObserverSvc.removeObserver(this, "quit-application");
Services.obs.removeObserver(this, "final-ui-startup");
Services.obs.removeObserver(this, "quit-application");
this.init();
break;
case "quit-application":
// no reason for initializing at this point (cf. bug 409115)
ObserverSvc.removeObserver(this, "final-ui-startup");
ObserverSvc.removeObserver(this, "quit-application");
Services.obs.removeObserver(this, "final-ui-startup");
Services.obs.removeObserver(this, "quit-application");
break;
case "domwindowopened":
var window = aSubject;
@ -189,7 +184,7 @@ SessionStartup.prototype = {
this._iniString = null;
this._sessionType = Ci.nsISessionStartup.NO_SESSION;
// no need in repeating this, since startup state won't change
ObserverSvc.removeObserver(this, "browser:purge-session-history");
Services.obs.removeObserver(this, "browser:purge-session-history");
break;
}
},
@ -223,7 +218,7 @@ SessionStartup.prototype = {
aWindow.arguments[0] == defaultArgs)
aWindow.arguments[0] = null;
ObserverSvc.removeObserver(this, "domwindowopened");
Services.obs.removeObserver(this, "domwindowopened");
},
/* ........ Public API ................*/
@ -264,7 +259,7 @@ SessionStartup.prototype = {
createInstance(Ci.nsISupportsString);
stateString.data = this._readFile(aFile) || "";
ObserverSvc.notifyObservers(stateString, "sessionstore-state-read", "");
Services.obs.notifyObservers(stateString, "sessionstore-state-read", "");
return stateString.data;
},

View File

@ -115,15 +115,13 @@ const CAPABILITIES = [
#endif
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
Cu.import("resource://gre/modules/NetUtil.jsm");
return NetUtil;
});
XPCOMUtils.defineLazyServiceGetter(this, "ConsoleSvc",
"@mozilla.org/consoleservice;1", "nsIConsoleService");
XPCOMUtils.defineLazyServiceGetter(this, "CookieSvc",
"@mozilla.org/cookiemanager;1", "nsICookieManager2");
@ -132,24 +130,12 @@ XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
"@mozilla.org/xre/app-info;1", "nsICrashReporter");
#endif
XPCOMUtils.defineLazyServiceGetter(this, "IOSvc",
"@mozilla.org/network/io-service;1", "nsIIOService");
XPCOMUtils.defineLazyServiceGetter(this, "ObserverSvc",
"@mozilla.org/observer-service;1", "nsIObserverService");
XPCOMUtils.defineLazyServiceGetter(this, "SecuritySvc",
"@mozilla.org/scriptsecuritymanager;1", "nsIScriptSecurityManager");
XPCOMUtils.defineLazyServiceGetter(this, "WindowMediator",
"@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator");
XPCOMUtils.defineLazyServiceGetter(this, "WindowWatcher",
"@mozilla.org/embedcomp/window-watcher;1", "nsIWindowWatcher");
function debug(aMsg) {
aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
ConsoleSvc.logStringMessage(aMsg);
Services.console.logStringMessage(aMsg);
}
/* :::::::: The Service ::::::::::::::: */
@ -229,12 +215,11 @@ SessionStoreService.prototype = {
return;
}
this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService).getBranch("browser.");
this._prefBranch = Services.prefs.getBranch("browser.");
this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
OBSERVING.forEach(function(aTopic) {
ObserverSvc.addObserver(this, aTopic, true);
Services.obs.addObserver(this, aTopic, true);
}, this);
var pbs = Cc["@mozilla.org/privatebrowsing;1"].
@ -258,9 +243,7 @@ SessionStoreService.prototype = {
this._prefBranch.getIntPref("sessionhistory.max_entries");
// get file references
var dirService = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
this._sessionFile = dirService.get("ProfD", Ci.nsILocalFile);
this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
this._sessionFileBackup = this._sessionFile.clone();
this._sessionFile.append("sessionstore.js");
this._sessionFileBackup.append("sessionstore.bak");
@ -632,7 +615,7 @@ SessionStoreService.prototype = {
}
else {
// Nothing to restore, notify observers things are complete.
ObserverSvc.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
// the next delayed save request should execute immediately
this._lastSaveTime -= this._interval;
@ -1581,33 +1564,68 @@ SessionStoreService.prototype = {
let node = formNodes.iterateNext();
if (!node)
return null;
const MAX_GENERATED_XPATHS = 100;
let generatedCount = 0;
let data = {};
do {
let nId = node.id;
let hasDefaultValue = true;
let value;
// Only generate a limited number of XPath expressions for perf reasons (cf. bug 477564)
if (!node.id && ++generatedCount > MAX_GENERATED_XPATHS)
if (!nId && generatedCount > MAX_GENERATED_XPATHS)
continue;
let id = node.id ? "#" + node.id : XPathHelper.generate(node);
if (node instanceof Ci.nsIDOMHTMLInputElement) {
if (node.type != "file")
data[id] = node.type == "checkbox" || node.type == "radio" ? node.checked : node.value;
else
data[id] = { type: "file", fileList: node.mozGetFileNameArray() };
if (node instanceof Ci.nsIDOMHTMLInputElement ||
node instanceof Ci.nsIDOMHTMLTextAreaElement) {
switch (node.type) {
case "checkbox":
case "radio":
value = node.checked;
hasDefaultValue = value == node.defaultChecked;
break;
case "file":
value = { type: "file", fileList: node.mozGetFileNameArray() };
hasDefaultValue = !value.fileList.length;
break;
default: // text, textarea
value = node.value;
hasDefaultValue = value == node.defaultValue;
break;
}
}
else if (!node.multiple) {
// <select>s without the multiple attribute are hard to determine the
// default value, so assume we don't have the default.
hasDefaultValue = false;
value = node.selectedIndex;
}
else if (node instanceof Ci.nsIDOMHTMLTextAreaElement)
data[id] = node.value;
else if (!node.multiple)
data[id] = node.selectedIndex;
else {
let options = Array.map(node.options, function(aOpt, aIx) aOpt.selected ? aIx : -1);
data[id] = options.filter(function(aIx) aIx >= 0);
// <select>s with the multiple attribute are easier to determine the
// default value since each <option> has a defaultSelected
let options = Array.map(node.options, function(aOpt, aIx) {
let oSelected = aOpt.selected;
hasDefaultValue = hasDefaultValue && (oSelected == aOpt.defaultSelected);
return oSelected ? aIx : -1;
});
value = options.filter(function(aIx) aIx >= 0);
}
// In order to reduce XPath generation (which is slow), we only save data
// for form fields that have been changed. (cf. bug 537289)
if (!hasDefaultValue) {
if (nId) {
data["#" + nId] = value;
}
else {
generatedCount++;
data[XPathHelper.generate(node)] = value;
}
}
} while ((node = formNodes.iterateNext()));
return data;
},
@ -2178,7 +2196,7 @@ SessionStoreService.prototype = {
var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
createInstance(Ci.nsISHEntry);
shEntry.setURI(IOSvc.newURI(aEntry.url, null, null));
shEntry.setURI(this._getURIFromString(aEntry.url));
shEntry.setTitle(aEntry.title || aEntry.url);
if (aEntry.subframe)
shEntry.setIsSubFrame(aEntry.subframe || false);
@ -2186,7 +2204,7 @@ SessionStoreService.prototype = {
if (aEntry.contentType)
shEntry.contentType = aEntry.contentType;
if (aEntry.referrer)
shEntry.referrerURI = IOSvc.newURI(aEntry.referrer, null, null);
shEntry.referrerURI = this._getURIFromString(aEntry.referrer);
if (aEntry.cacheKey) {
var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
@ -2282,7 +2300,7 @@ SessionStoreService.prototype = {
*/
_deserializeSessionStorage: function sss_deserializeSessionStorage(aStorageData, aDocShell) {
for (let url in aStorageData) {
let uri = IOSvc.newURI(url, null, null);
let uri = this._getURIFromString(url);
let storage = aDocShell.getSessionStorageForURI(uri, "");
for (let key in aStorageData[url]) {
try {
@ -2491,7 +2509,7 @@ SessionStoreService.prototype = {
restoreCookies: function sss_restoreCookies(aCookies) {
// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
var MAX_EXPIRY = Math.pow(2, 62);
for (i = 0; i < aCookies.length; i++) {
for (let i = 0; i < aCookies.length; i++) {
var cookie = aCookies[i];
try {
CookieSvc.add(cookie.host, cookie.path || "", cookie.name || "",
@ -2568,7 +2586,7 @@ SessionStoreService.prototype = {
// parentheses are for backwards compatibility with Firefox 2.0 and 3.0
stateString.data = "(" + this._toJSONString(aStateObj) + ")";
ObserverSvc.notifyObservers(stateString, "sessionstore-state-write", "");
Services.obs.notifyObservers(stateString, "sessionstore-state-write", "");
// don't touch the file if an observer has deleted all state data
if (stateString.data)
@ -2604,7 +2622,7 @@ SessionStoreService.prototype = {
* Callback each window is passed to
*/
_forEachBrowserWindow: function sss_forEachBrowserWindow(aFunc) {
var windowsEnum = WindowMediator.getEnumerator("navigator:browser");
var windowsEnum = Services.wm.getEnumerator("navigator:browser");
while (windowsEnum.hasMoreElements()) {
var window = windowsEnum.getNext();
@ -2619,7 +2637,7 @@ SessionStoreService.prototype = {
* @returns Window reference
*/
_getMostRecentBrowserWindow: function sss_getMostRecentBrowserWindow() {
var win = WindowMediator.getMostRecentWindow("navigator:browser");
var win = Services.wm.getMostRecentWindow("navigator:browser");
if (!win)
return null;
if (!win.closed)
@ -2627,7 +2645,7 @@ SessionStoreService.prototype = {
#ifdef BROKEN_WM_Z_ORDER
win = null;
var windowsEnum = WindowMediator.getEnumerator("navigator:browser");
var windowsEnum = Services.wm.getEnumerator("navigator:browser");
// this is oldest to newest, so this gets a bit ugly
while (windowsEnum.hasMoreElements()) {
let nextWin = windowsEnum.getNext();
@ -2637,7 +2655,7 @@ SessionStoreService.prototype = {
return win;
#else
var windowsEnum =
WindowMediator.getZOrderDOMWindowEnumerator("navigator:browser", true);
Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
while (windowsEnum.hasMoreElements()) {
win = windowsEnum.getNext();
if (!win.closed)
@ -2653,9 +2671,7 @@ SessionStoreService.prototype = {
* setBrowserState to treat them as open windows.
*/
_handleClosedWindows: function sss_handleClosedWindows() {
var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator);
var windowsEnum = windowMediator.getEnumerator("navigator:browser");
var windowsEnum = Services.wm.getEnumerator("navigator:browser");
while (windowsEnum.hasMoreElements()) {
var window = windowsEnum.getNext();
@ -2686,8 +2702,8 @@ SessionStoreService.prototype = {
});
var window =
WindowWatcher.openWindow(null, this._prefBranch.getCharPref("chromeURL"),
"_blank", features, argString);
Services.ww.openWindow(null, this._prefBranch.getCharPref("chromeURL"),
"_blank", features, argString);
do {
var ID = "window" + Math.random();
@ -2785,7 +2801,7 @@ SessionStoreService.prototype = {
* @returns nsIURI
*/
_getURIFromString: function sss_getURIFromString(aString) {
return IOSvc.newURI(aString, null, null);
return Services.io.newURI(aString, null, null);
},
/**
@ -2832,8 +2848,7 @@ SessionStoreService.prototype = {
return false;
// don't automatically restore in Safe Mode
let XRE = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
if (XRE.inSafeMode)
if (Services.appinfo.inSafeMode)
return true;
let max_resumed_crashes =
@ -2882,7 +2897,7 @@ SessionStoreService.prototype = {
this._restoreCount--;
if (this._restoreCount == 0) {
// This was the last window restored at startup, notify observers.
ObserverSvc.notifyObservers(null,
Services.obs.notifyObservers(null,
this._browserSetState ? NOTIFY_BROWSER_STATE_RESTORED : NOTIFY_WINDOWS_RESTORED,
"");
this._browserSetState = false;
@ -2963,9 +2978,9 @@ SessionStoreService.prototype = {
var self = this;
NetUtil.asyncCopy(istream, ostream, function(rc) {
if (Components.isSuccessCode(rc)) {
ObserverSvc.notifyObservers(null,
"sessionstore-state-write-complete",
"");
Services.obs.notifyObservers(null,
"sessionstore-state-write-complete",
"");
}
});
}

View File

@ -47,6 +47,14 @@ function test() {
let tab = gBrowser.addTab(testURL);
tab.linkedBrowser.addEventListener("load", function(aEvent) {
this.removeEventListener("load", arguments.callee, true);
let expectedValue = "try to save me";
// Since bug 537289 we only save non-default values, so we need to set each
// form field's value after load.
let formEls = aEvent.originalTarget.forms[0].elements;
for (let i = 0; i < formEls.length; i++)
formEls[i].value = expectedValue;
gBrowser.removeTab(tab);
let ss = Cc["@mozilla.org/browser/sessionstore;1"]
@ -56,7 +64,7 @@ function test() {
let countGood = 0, countBad = 0;
for each (let value in savedFormData) {
if (value == "save me")
if (value == expectedValue)
countGood++;
else
countBad++;

View File

@ -5,22 +5,24 @@
<head><title>Test for bug 456342</title></head>
<body>
<form>
<h3>Non-standard &lt;input&gt;s</h3>
<p>Search <input type="search" value="save me" id="searchTerm"/></p>
<p>Image Search: <input type="image search" value="save me" /></p>
<p>Autocomplete: <input type="autocomplete" value="save me" name="fill-in"/></p>
<p>Mistyped: <input type="txet" value="save me" name="mistyped"/></p>
<p>Search <input type="search" id="searchTerm"/></p>
<p>Image Search: <input type="image search" /></p>
<p>Autocomplete: <input type="autocomplete" name="fill-in"/></p>
<p>Mistyped: <input type="txet" name="mistyped"/></p>
<h3>Ignored types</h3>
<input type="hidden" value="don't save" name="hideme"/>
<input type="HIDDEN" value="don't save" name="hideme2"/>
<input type="submit" value="don't save" name="submit"/>
<input type="reset" value="don't save" name="reset"/>
<input type="image" value="don't save" name="image"/>
<input type="button" value="don't save" name="button"/>
<input type="password" value="don't save" name="password"/>
<input type="PassWord" value="don't save" name="password2"/>
<input type="PASSWORD" value="don't save" name="password3"/>
<input type="hidden" name="hideme"/>
<input type="HIDDEN" name="hideme2"/>
<input type="submit" name="submit"/>
<input type="reset" name="reset"/>
<input type="image" name="image"/>
<input type="button" name="button"/>
<input type="password" name="password"/>
<input type="PassWord" name="password2"/>
<input type="PASSWORD" name="password3"/>
</form>
</body>
</html>

View File

@ -81,7 +81,7 @@
#ifdef XP_MACOSX
@BINPATH@/plugins/DefaultPlugin.plugin/
#elifdef XP_UNIX
@BINPATH@/plugins/libnullplugin.so
@BINPATH@/plugins/libnullplugin@DLL_SUFFIX@
#elifdef XP_WIN32
#ifndef WINCE
@BINPATH@/plugins/npnul32.dll
@ -191,7 +191,7 @@
@BINPATH@/components/layout_xul.xpt
#ifdef XP_UNIX
#ifndef XP_MACOSX
@BINPATH@/components/libimgicon.so
@BINPATH@/components/libimgicon@DLL_SUFFIX@
#endif
#endif
@BINPATH@/components/locale.xpt

View File

@ -48,6 +48,7 @@ class nsITransferable;
class nsACString;
class nsAString;
class nsIDOMNode;
class nsIPresShell;
class nsCopySupport
{
@ -66,16 +67,51 @@ class nsCopySupport
static nsresult ImageCopy(nsIImageLoadingContent* aImageElement,
PRInt32 aCopyFlags);
// Given the current selection, find the target that
// before[copy,cut,paste] and [copy,cut,paste] events will fire on.
static nsresult GetClipboardEventTarget(nsISelection *aSel,
nsIDOMNode **aEventTarget);
// Get the selection as a transferable. Similar to HTMLCopy except does
// not deal with the clipboard.
static nsresult GetTransferableForSelection(nsISelection * aSelection,
nsIDocument * aDocument,
nsITransferable ** aTransferable);
/**
* Retrieve the selection for the given document. If the current focus
* within the document has its own selection, aSelection will be set to it
* and this focused content node returned. Otherwise, aSelection will be
* set to the document's selection and null will be returned.
*/
static nsIContent* GetSelectionForCopy(nsIDocument* aDocument,
nsISelection** aSelection);
/**
* Returns true if a copy operation is currently permitted based on the
* current focus and selection within the specified document.
*/
static PRBool CanCopy(nsIDocument* aDocument);
/**
* Fires a cut, copy or paste event, on the given presshell, depending
* on the value of aType, which should be either NS_CUT, NS_COPY or
* NS_PASTE, and perform the default copy action if the event was not
* cancelled.
*
* If aSelection is specified, then this selection is used as the target
* of the operation. Otherwise, GetSelectionForCopy is used to retrieve
* the current selection.
*
* This will fire a cut, copy or paste event at the node at the start
* point of the selection. If a cut or copy event is not cancelled, the
* selection is copied to the clipboard and true is returned. Paste events
* have no default behaviour but true will be returned. It is expected
* that the caller will execute any needed default paste behaviour. Also,
* note that this method only copies text to the clipboard, the caller is
* responsible for removing the content during a cut operation if true is
* returned.
*
* If the event is cancelled or an error occurs, false will be returned.
*/
static PRBool FireClipboardEvent(PRInt32 aType,
nsIPresShell* aPresShell,
nsISelection* aSelection);
};
#endif

View File

@ -300,7 +300,9 @@ public:
returns a non-null value for nsIContent::GetText() */
eDATA_NODE = 1 << 10,
/** nsHTMLMediaElement */
eMEDIA = 1 << 11
eMEDIA = 1 << 11,
/** animation elements */
eANIMATION = 1 << 12
};
/**

View File

@ -50,18 +50,26 @@
#include "nsISupportsPrimitives.h"
#include "nsIDOMRange.h"
#include "imgIContainer.h"
#include "nsIPresShell.h"
#include "nsFocusManager.h"
#include "nsEventDispatcher.h"
#include "nsIDocShell.h"
#include "nsIContentViewerEdit.h"
#include "nsIClipboardDragDropHooks.h"
#include "nsIClipboardDragDropHookList.h"
#include "nsIClipboardHelper.h"
#include "nsISelectionController.h"
#include "nsPIDOMWindow.h"
#include "nsIDocument.h"
#include "nsIDOMNode.h"
#include "nsIDOMElement.h"
#include "nsIDOMDocument.h"
#include "nsIHTMLDocument.h"
#include "nsGkAtoms.h"
#include "nsGUIEvent.h"
#include "nsIFrame.h"
// image copy stuff
#include "nsIImageLoadingContent.h"
@ -582,26 +590,128 @@ static nsresult AppendDOMNode(nsITransferable *aTransferable,
return AppendString(aTransferable, context, kHTMLContext);
}
// Find the target that onbefore[copy,cut,paste] and on[copy,cut,paste]
// events will fire on -- the start node of the copy selection.
nsresult nsCopySupport::GetClipboardEventTarget(nsISelection *aSel,
nsIDOMNode **aEventTarget)
nsIContent*
nsCopySupport::GetSelectionForCopy(nsIDocument* aDocument, nsISelection** aSelection)
{
NS_ENSURE_ARG(aSel);
NS_ENSURE_ARG_POINTER(aEventTarget);
*aEventTarget = nsnull;
*aSelection = nsnull;
nsCOMPtr<nsIDOMRange> range;
nsresult rv = aSel->GetRangeAt(0, getter_AddRefs(range));
if (rv == NS_ERROR_INVALID_ARG) // empty selection
return NS_ERROR_FAILURE;
NS_ENSURE_SUCCESS(rv, rv);
nsIPresShell* presShell = aDocument->GetPrimaryShell();
if (!presShell)
return nsnull;
if (!range)
return NS_ERROR_FAILURE;
// check if the focused node in the window has a selection
nsCOMPtr<nsPIDOMWindow> focusedWindow;
nsIContent* content =
nsFocusManager::GetFocusedDescendant(aDocument->GetWindow(), PR_FALSE,
getter_AddRefs(focusedWindow));
if (content) {
nsIFrame* frame = content->GetPrimaryFrame();
if (frame) {
nsCOMPtr<nsISelectionController> selCon;
frame->GetSelectionController(presShell->GetPresContext(), getter_AddRefs(selCon));
if (selCon) {
selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, aSelection);
return content;
}
}
}
rv = range->GetStartContainer(aEventTarget);
NS_ENSURE_SUCCESS(rv, rv);
return (*aEventTarget) ? NS_OK : NS_ERROR_FAILURE;
// if no selection was found, use the main selection for the window
NS_IF_ADDREF(*aSelection = presShell->GetCurrentSelection(nsISelectionController::SELECTION_NORMAL));
return nsnull;
}
PRBool
nsCopySupport::CanCopy(nsIDocument* aDocument)
{
if (!aDocument)
return PR_FALSE;
nsCOMPtr<nsISelection> sel;
GetSelectionForCopy(aDocument, getter_AddRefs(sel));
PRBool isCollapsed;
sel->GetIsCollapsed(&isCollapsed);
return !isCollapsed;
}
PRBool
nsCopySupport::FireClipboardEvent(PRInt32 aType, nsIPresShell* aPresShell, nsISelection* aSelection)
{
NS_ASSERTION(aType == NS_CUT || aType == NS_COPY || aType == NS_PASTE,
"Invalid clipboard event type");
nsCOMPtr<nsIPresShell> presShell = aPresShell;
if (!presShell)
return PR_FALSE;
nsCOMPtr<nsIDocument> doc = presShell->GetDocument();
if (!doc)
return PR_FALSE;
nsCOMPtr<nsPIDOMWindow> piWindow = doc->GetWindow();
if (!piWindow)
return PR_FALSE;
// if a selection was not supplied, try to find it
nsCOMPtr<nsIContent> content;
nsCOMPtr<nsISelection> sel = aSelection;
if (!sel)
content = GetSelectionForCopy(doc, getter_AddRefs(sel));
// retrieve the event target node from the start of the selection
if (sel) {
// Only cut or copy when there is an uncollapsed selection
if (aType == NS_CUT || aType == NS_COPY) {
PRBool isCollapsed;
sel->GetIsCollapsed(&isCollapsed);
if (isCollapsed)
return PR_FALSE;
}
nsCOMPtr<nsIDOMRange> range;
nsresult rv = sel->GetRangeAt(0, getter_AddRefs(range));
if (NS_SUCCEEDED(rv) && range) {
nsCOMPtr<nsIDOMNode> startContainer;
range->GetStartContainer(getter_AddRefs(startContainer));
if (startContainer)
content = do_QueryInterface(startContainer);
}
}
// if no content node was set, just get the root
if (!content) {
content = doc->GetRootContent();
if (!content)
return PR_FALSE;
}
// It seems to be unsafe to fire an event handler during reflow (bug 393696)
if (!nsContentUtils::IsSafeToRunScript())
return PR_FALSE;
// next, fire the cut or copy event
nsEventStatus status = nsEventStatus_eIgnore;
nsEvent evt(PR_TRUE, aType);
nsEventDispatcher::Dispatch(content, presShell->GetPresContext(), &evt, nsnull,
&status);
// if the event was cancelled, don't do the clipboard operation
if (status == nsEventStatus_eConsumeNoDefault)
return PR_FALSE;
// no need to do anything special during a paste. Either an event listener
// took care of it and cancelled the event, or the caller will handle it.
// Return true to indicate the event wasn't cancelled.
if (aType == NS_PASTE)
return PR_TRUE;
// call the copy code
if (NS_FAILED(nsCopySupport::HTMLCopy(sel, doc, nsIClipboard::kGlobalClipboard)))
return PR_FALSE;
// Now that we have copied, update the clipboard commands. This should have
// the effect of updating the paste menu item.
piWindow->UpdateCommands(NS_LITERAL_STRING("clipboard"));
return PR_TRUE;
}

View File

@ -77,33 +77,23 @@ nsContentEventHandler::nsContentEventHandler(
}
nsresult
nsContentEventHandler::Init(nsQueryContentEvent* aEvent)
nsContentEventHandler::InitCommon()
{
NS_ASSERTION(aEvent, "aEvent must not be null");
if (mSelection)
return NS_OK;
aEvent->mSucceeded = PR_FALSE;
if (!mPresShell)
return NS_ERROR_NOT_AVAILABLE;
NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_AVAILABLE);
// If text frame which has overflowing selection underline is dirty,
// we need to flush the pending reflow here.
nsresult rv = mPresShell->FlushPendingNotifications(Flush_Layout);
NS_ENSURE_SUCCESS(rv, rv);
rv = mPresShell->GetSelectionForCopy(getter_AddRefs(mSelection));
NS_ENSURE_SUCCESS(rv, rv);
nsCopySupport::GetSelectionForCopy(mPresShell->GetDocument(),
getter_AddRefs(mSelection));
NS_ASSERTION(mSelection,
"GetSelectionForCopy succeeded, but the result is null");
PRBool isCollapsed;
rv = mSelection->GetIsCollapsed(&isCollapsed);
if (NS_FAILED(rv))
return NS_ERROR_NOT_AVAILABLE;
aEvent->mReply.mHasSelection = !isCollapsed;
nsCOMPtr<nsIDOMRange> firstRange;
rv = mSelection->GetRangeAt(0, getter_AddRefs(firstRange));
@ -126,9 +116,26 @@ nsContentEventHandler::Init(nsQueryContentEvent* aEvent)
mRootContent = startNode->GetSelectionRootContent(mPresShell);
NS_ENSURE_TRUE(mRootContent, NS_ERROR_FAILURE);
return NS_OK;
}
nsresult
nsContentEventHandler::Init(nsQueryContentEvent* aEvent)
{
NS_ASSERTION(aEvent, "aEvent must not be null");
nsresult rv = InitCommon();
NS_ENSURE_SUCCESS(rv, rv);
aEvent->mSucceeded = PR_FALSE;
aEvent->mReply.mContentsRoot = mRootContent.get();
PRBool isCollapsed;
rv = mSelection->GetIsCollapsed(&isCollapsed);
NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_AVAILABLE);
aEvent->mReply.mHasSelection = !isCollapsed;
nsRefPtr<nsCaret> caret;
rv = mPresShell->GetCaret(getter_AddRefs(caret));
NS_ENSURE_SUCCESS(rv, rv);
@ -143,6 +150,19 @@ nsContentEventHandler::Init(nsQueryContentEvent* aEvent)
return NS_OK;
}
nsresult
nsContentEventHandler::Init(nsSelectionEvent* aEvent)
{
NS_ASSERTION(aEvent, "aEvent must not be null");
nsresult rv = InitCommon();
NS_ENSURE_SUCCESS(rv, rv);
aEvent->mSucceeded = PR_FALSE;
return NS_OK;
}
// Editor places a bogus BR node under its root content if the editor doesn't
// have any text. This happens even for single line editors.
// When we get text content and when we change the selection,
@ -664,9 +684,8 @@ nsContentEventHandler::OnQueryCaretRect(nsQueryContentEvent* aEvent)
nsIFrame* caretFrame = caret->GetGeometry(mSelection, &rect);
if (!caretFrame)
return NS_ERROR_FAILURE;
nsPoint windowOffset(0, 0);
caretFrame->GetWindowOffset(windowOffset);
rect.MoveBy(windowOffset);
rv = ConvertToRootViewRelativeOffset(caretFrame, rect);
NS_ENSURE_SUCCESS(rv, rv);
aEvent->mReply.mRect =
rect.ToOutsidePixels(caretFrame->PresContext()->AppUnitsPerDevPixel());
aEvent->mSucceeded = PR_TRUE;
@ -747,8 +766,32 @@ nsContentEventHandler::OnQueryCharacterAtPoint(nsQueryContentEvent* aEvent)
return rv;
nsIFrame* rootFrame = mPresShell->GetRootFrame();
NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE);
nsIWidget* rootWidget = rootFrame->GetWindow();
NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE);
// The root frame's widget might be different, e.g., the event was fired on
// a popup but the rootFrame is the document root.
if (rootWidget != aEvent->widget) {
NS_PRECONDITION(aEvent->widget, "The event must have the widget");
nsIView* view = nsIView::GetViewFor(aEvent->widget);
NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
rootFrame = nsLayoutUtils::GetFrameFor(view);
NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE);
rootWidget = rootFrame->GetWindow();
NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE);
}
nsQueryContentEvent eventOnRoot(PR_TRUE, NS_QUERY_CHARACTER_AT_POINT,
rootWidget);
eventOnRoot.refPoint = aEvent->refPoint;
if (rootWidget != aEvent->widget) {
eventOnRoot.refPoint += aEvent->widget->WidgetToScreenOffset();
eventOnRoot.refPoint -= rootWidget->WidgetToScreenOffset();
}
nsPoint ptInRoot =
nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, rootFrame);
nsLayoutUtils::GetEventCoordinatesRelativeTo(&eventOnRoot, rootFrame);
nsIFrame* targetFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot);
if (!targetFrame || targetFrame->GetType() != nsGkAtoms::textFrame) {
// there is no character at the point.
@ -882,11 +925,17 @@ nsContentEventHandler::OnSelectionEvent(nsSelectionEvent* aEvent)
aEvent->mSucceeded = PR_FALSE;
// Get selection to manipulate
nsCOMPtr<nsISelection> sel;
// XXX why do we need to get them from ISM? This method should work fine
// without ISM.
nsresult rv = nsIMEStateManager::
GetFocusSelectionAndRoot(getter_AddRefs(sel),
GetFocusSelectionAndRoot(getter_AddRefs(mSelection),
getter_AddRefs(mRootContent));
NS_ENSURE_SUCCESS(rv, rv);
if (rv != NS_ERROR_NOT_AVAILABLE) {
NS_ENSURE_SUCCESS(rv, rv);
} else {
rv = Init(aEvent);
NS_ENSURE_SUCCESS(rv, rv);
}
// Get range from offset and length
nsRefPtr<nsRange> range = new nsRange();
@ -906,32 +955,32 @@ nsContentEventHandler::OnSelectionEvent(nsSelectionEvent* aEvent)
nsCOMPtr<nsIDOMNode> endDomNode(do_QueryInterface(endNode));
NS_ENSURE_TRUE(startDomNode && endDomNode, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsISelectionPrivate> selPrivate = do_QueryInterface(sel);
nsCOMPtr<nsISelectionPrivate> selPrivate = do_QueryInterface(mSelection);
NS_ENSURE_TRUE(selPrivate, NS_ERROR_UNEXPECTED);
selPrivate->StartBatchChanges();
// Clear selection first before setting
rv = sel->RemoveAllRanges();
rv = mSelection->RemoveAllRanges();
// Need to call EndBatchChanges at the end even if call failed
if (NS_SUCCEEDED(rv)) {
if (aEvent->mReversed) {
rv = sel->Collapse(endDomNode, endOffset);
rv = mSelection->Collapse(endDomNode, endOffset);
} else {
rv = sel->Collapse(startDomNode, startOffset);
rv = mSelection->Collapse(startDomNode, startOffset);
}
if (NS_SUCCEEDED(rv) &&
(startDomNode != endDomNode || startOffset != endOffset)) {
if (aEvent->mReversed) {
rv = sel->Extend(startDomNode, startOffset);
rv = mSelection->Extend(startDomNode, startOffset);
} else {
rv = sel->Extend(endDomNode, endOffset);
rv = mSelection->Extend(endDomNode, endOffset);
}
}
}
selPrivate->EndBatchChanges();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISelection2>(do_QueryInterface(sel))->ScrollIntoView(
nsCOMPtr<nsISelection2>(do_QueryInterface(mSelection))->ScrollIntoView(
nsISelectionController::SELECTION_FOCUS_REGION, PR_FALSE, -1, -1);
aEvent->mSucceeded = PR_TRUE;
return NS_OK;

View File

@ -95,6 +95,10 @@ protected:
nsCOMPtr<nsIContent> mRootContent;
nsresult Init(nsQueryContentEvent* aEvent);
nsresult Init(nsSelectionEvent* aEvent);
// InitCommon() is called from each Init().
nsresult InitCommon();
public:
// FlatText means the text that is generated from DOM tree. The BR elements

View File

@ -483,7 +483,12 @@ nsEventDispatcher::Dispatch(nsISupports* aTarget,
nsresult rv = NS_ERROR_FAILURE;
if (target->GetContextForEventHandlers(&rv) ||
NS_FAILED(rv)) {
NS_ERROR("This is unsafe!");
nsCOMPtr<nsINode> node = do_QueryInterface(target);
if (node && nsContentUtils::IsChromeDoc(node->GetOwnerDoc())) {
NS_WARNING("Fix the caller!");
} else {
NS_ERROR("This is unsafe! Fix the caller!");
}
}
}

View File

@ -81,6 +81,7 @@ GetRefreshDriverForDoc(nsIDocument* aDoc)
nsSMILAnimationController::nsSMILAnimationController()
: mResampleNeeded(PR_FALSE),
mDeferredStartSampling(PR_FALSE),
mDocument(nsnull)
{
mAnimationElementTable.Init();
@ -150,7 +151,11 @@ nsSMILAnimationController::Resume(PRUint32 aType)
if (wasPaused && !mPauseState && mChildContainerTable.Count()) {
Sample(); // Run the first sample manually
StartSampling(GetRefreshDriverForDoc(mDocument));
if (mAnimationElementTable.Count()) {
StartSampling(GetRefreshDriverForDoc(mDocument));
} else {
mDeferredStartSampling = PR_TRUE;
}
}
}
@ -185,6 +190,14 @@ nsSMILAnimationController::RegisterAnimationElement(
nsISMILAnimationElement* aAnimationElement)
{
mAnimationElementTable.PutEntry(aAnimationElement);
if (mDeferredStartSampling) {
// mAnimationElementTable was empty until we just inserted its first element
NS_ABORT_IF_FALSE(mAnimationElementTable.Count() == 1,
"we shouldn't have deferred sampling if we already had "
"animations registered");
mDeferredStartSampling = PR_FALSE;
StartSampling(GetRefreshDriverForDoc(mDocument));
}
}
void
@ -695,7 +708,11 @@ nsSMILAnimationController::AddChild(nsSMILTimeContainer& aChild)
if (!mPauseState && mChildContainerTable.Count() == 1) {
Sample(); // Run the first sample manually
StartSampling(GetRefreshDriverForDoc(mDocument));
if (mAnimationElementTable.Count()) {
StartSampling(GetRefreshDriverForDoc(mDocument));
} else {
mDeferredStartSampling = PR_TRUE;
}
}
return NS_OK;

View File

@ -183,6 +183,7 @@ protected:
AnimationElementHashtable mAnimationElementTable;
TimeContainerHashtable mChildContainerTable;
PRPackedBool mResampleNeeded;
PRPackedBool mDeferredStartSampling;
// Store raw ptr to mDocument. It owns the controller, so controller
// shouldn't outlive it

View File

@ -408,6 +408,12 @@ nsSVGAnimationElement::UnsetAttr(PRInt32 aNamespaceID,
return NS_OK;
}
PRBool
nsSVGAnimationElement::IsNodeOfType(PRUint32 aFlags) const
{
return !(aFlags & ~(eCONTENT | eELEMENT | eSVG | eANIMATION));
}
//----------------------------------------------------------------------
// Implementation helpers

View File

@ -74,10 +74,11 @@ public:
PRBool aCompileEventHandlers);
virtual void UnbindFromTree(PRBool aDeep, PRBool aNullParent);
// nsIContent specializations
virtual nsresult UnsetAttr(PRInt32 aNamespaceID, nsIAtom* aAttribute,
PRBool aNotify);
virtual PRBool IsNodeOfType(PRUint32 aFlags) const;
// nsGenericElement specializations
virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
nsIAtom* aAttribute,

View File

@ -151,7 +151,13 @@ function checkResults(root, step)
if (step > 0)
adjtestid += " dynamic step " + step;
if (debug) {
var stilltodo = ((step == 0 && notWorkingYet) || (step > 0 && notWorkingYetDynamic));
if (stilltodo)
todo(false, adjtestid);
else
ok(!error, adjtestid);
if ((!stilltodo && error) || debug) {
// for debugging, serialize the XML output
var serializedXML = "";
var rootNodes = actualoutput.childNodes;
@ -163,14 +169,11 @@ function checkResults(root, step)
// remove the XUL namespace declarations to make the output more readable
const nsrepl = new RegExp("xmlns=\"" + XUL_NS + "\" ", "g");
dump("-------- " + adjtestid + " " + error + ":\n" +
serializedXML.replace(nsrepl, "") + "\n");
serializedXML = serializedXML.replace(nsrepl, "");
if (debug)
dump("-------- " + adjtestid + " " + error + ":\n" + serializedXML + "\n");
is(serializedXML, "Same", "Error is: " + error);
}
if ((step == 0 && notWorkingYet) || (step > 0 && notWorkingYetDynamic))
todo(false, adjtestid);
else
ok(!error, adjtestid);
}
/**

View File

@ -40,12 +40,9 @@
#include "nsISupports.idl"
[scriptable, uuid(1691a02f-53b2-4cb8-8769-48e7efc908b8)]
[scriptable, uuid(AF13EA3A-D488-4308-B843-526E055AB943)]
interface nsIContentViewerEdit : nsISupports
{
void search();
readonly attribute boolean searchable;
void clearSelection();
void selectAll();
@ -62,12 +59,6 @@ interface nsIContentViewerEdit : nsISupports
void copyImage(in long aCopyFlags);
readonly attribute boolean inImage;
void cutSelection();
readonly attribute boolean cutable;
void paste();
readonly attribute boolean pasteable;
AString getContents(in string aMimeType, in boolean aSelectionOnly);
readonly attribute boolean canGetContents;
};

View File

@ -99,6 +99,7 @@ CPPSRCS = \
nsDOMClassInfo.cpp \
nsScriptNameSpaceManager.cpp \
nsDOMScriptObjectFactory.cpp \
nsQueryContentEventResult.cpp \
$(NULL)
include $(topsrcdir)/dom/dom-config.mk

View File

@ -42,6 +42,7 @@
#include "nsIDOMNSEvent.h"
#include "nsIPrivateDOMEvent.h"
#include "nsDOMWindowUtils.h"
#include "nsQueryContentEventResult.h"
#include "nsGlobalWindow.h"
#include "nsIDocument.h"
#include "nsFocusManager.h"
@ -50,6 +51,7 @@
#include "nsIScrollableFrame.h"
#include "nsContentUtils.h"
#include "nsLayoutUtils.h"
#include "nsIFrame.h"
#include "nsIWidget.h"
@ -70,6 +72,15 @@
#include <gdk/gdkx.h>
#endif
static PRBool IsUniversalXPConnectCapable()
{
PRBool hasCap = PR_FALSE;
nsresult rv = nsContentUtils::GetSecurityManager()->
IsCapabilityEnabled("UniversalXPConnect", &hasCap);
NS_ENSURE_SUCCESS(rv, PR_FALSE);
return hasCap;
}
NS_INTERFACE_MAP_BEGIN(nsDOMWindowUtils)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMWindowUtils)
NS_INTERFACE_MAP_ENTRY(nsIDOMWindowUtils)
@ -131,9 +142,9 @@ nsDOMWindowUtils::GetDocCharsetIsForced(PRBool *aIsForced)
{
*aIsForced = PR_FALSE;
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap)) || !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
if (mWindow) {
nsCOMPtr<nsIDocument> doc(do_QueryInterface(mWindow->GetExtantDocument()));
@ -147,10 +158,9 @@ NS_IMETHODIMP
nsDOMWindowUtils::GetDocumentMetadata(const nsAString& aName,
nsAString& aValue)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
if (mWindow) {
nsCOMPtr<nsIDocument> doc(do_QueryInterface(mWindow->GetExtantDocument()));
@ -211,10 +221,9 @@ nsDOMWindowUtils::SendMouseEvent(const nsAString& aType,
PRInt32 aModifiers,
PRBool aIgnoreRootScrollFrame)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsPoint offset;
@ -276,10 +285,9 @@ nsDOMWindowUtils::SendMouseScrollEvent(const nsAString& aType,
PRInt32 aDelta,
PRInt32 aModifiers)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsPoint offset;
@ -327,10 +335,9 @@ nsDOMWindowUtils::SendKeyEvent(const nsAString& aType,
PRBool aPreventDefault,
PRBool* aDefaultActionTaken)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
@ -378,10 +385,9 @@ nsDOMWindowUtils::SendNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
@ -399,10 +405,9 @@ nsDOMWindowUtils::SendNativeMouseEvent(PRInt32 aScreenX,
PRInt32 aModifierFlags,
nsIDOMElement* aElement)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
@ -416,10 +421,9 @@ nsDOMWindowUtils::SendNativeMouseEvent(PRInt32 aScreenX,
NS_IMETHODIMP
nsDOMWindowUtils::ActivateNativeMenuItemAt(const nsAString& indexString)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
@ -432,10 +436,9 @@ nsDOMWindowUtils::ActivateNativeMenuItemAt(const nsAString& indexString)
NS_IMETHODIMP
nsDOMWindowUtils::ForceUpdateNativeMenuAt(const nsAString& indexString)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
@ -489,10 +492,9 @@ nsDOMWindowUtils::GetWidgetForElement(nsIDOMElement* aElement)
NS_IMETHODIMP
nsDOMWindowUtils::Focus(nsIDOMElement* aElement)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled(
"UniversalXPConnect", &hasCap)) || !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
@ -511,11 +513,9 @@ nsDOMWindowUtils::GarbageCollect()
// NOTE: Only do this in NON debug builds, as this function can useful
// during debugging.
#ifndef DEBUG
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->
IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
#endif
nsJSContext::CC();
@ -554,10 +554,9 @@ nsDOMWindowUtils::SendSimpleGestureEvent(const nsAString& aType,
PRFloat64 aDelta,
PRInt32 aModifiers)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsPoint offset;
@ -634,9 +633,9 @@ nsDOMWindowUtils::CompareCanvases(nsIDOMHTMLCanvasElement *aCanvas1,
PRUint32* aMaxDifference,
PRUint32* retVal)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap)) || !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
if (aCanvas1 == nsnull ||
aCanvas2 == nsnull ||
@ -723,11 +722,9 @@ nsDOMWindowUtils::ClearMozAfterPaintEvents()
NS_IMETHODIMP
nsDOMWindowUtils::DisableNonTestMouseEvents(PRBool aDisable)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->
IsCapabilityEnabled("UniversalXPConnect", &hasCap)) ||
!hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
nsIDocShell *docShell = mWindow->GetDocShell();
@ -741,9 +738,9 @@ nsDOMWindowUtils::DisableNonTestMouseEvents(PRBool aDisable)
NS_IMETHODIMP
nsDOMWindowUtils::SuppressEventHandling(PRBool aSuppress)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap)) || !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<nsIDocument> doc(do_QueryInterface(mWindow->GetExtantDocument()));
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
@ -832,11 +829,9 @@ nsDOMWindowUtils::GetScreenPixelsPerCSSPixel(float* aScreenPixels)
NS_IMETHODIMP
nsDOMWindowUtils::GetCOWForObject()
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->
IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<nsIXPConnect> xpc = nsContentUtils::XPConnect();
@ -918,14 +913,243 @@ nsDOMWindowUtils::DispatchDOMEventViaPresShell(nsIDOMNode* aTarget,
return NS_OK;
}
static void
InitEvent(nsGUIEvent &aEvent, nsIntPoint *aPt = nsnull)
{
if (aPt) {
aEvent.refPoint = *aPt;
}
aEvent.time = PR_IntervalNow();
}
NS_IMETHODIMP
nsDOMWindowUtils::SendCompositionEvent(const nsAString& aType)
{
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) {
return NS_ERROR_FAILURE;
}
PRUint32 msg;
if (aType.EqualsLiteral("compositionstart")) {
msg = NS_COMPOSITION_START;
} else if (aType.EqualsLiteral("compositionend")) {
msg = NS_COMPOSITION_END;
} else {
return NS_ERROR_FAILURE;
}
nsCompositionEvent compositionEvent(PR_TRUE, msg, widget);
InitEvent(compositionEvent);
nsEventStatus status;
nsresult rv = widget->DispatchEvent(&compositionEvent, status);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
static void
AppendClause(PRInt32 aClauseLength, PRUint32 aClauseAttr,
nsTArray<nsTextRange>* aRanges)
{
NS_PRECONDITION(aRanges, "aRange is null");
if (aClauseLength == 0) {
return;
}
nsTextRange range;
range.mStartOffset = aRanges->Length() == 0 ? 0 :
aRanges->ElementAt(aRanges->Length() - 1).mEndOffset + 1;
range.mEndOffset = range.mStartOffset + aClauseLength;
NS_ASSERTION(range.mStartOffset <= range.mEndOffset, "range is invalid");
NS_PRECONDITION(aClauseAttr == NS_TEXTRANGE_RAWINPUT ||
aClauseAttr == NS_TEXTRANGE_SELECTEDRAWTEXT ||
aClauseAttr == NS_TEXTRANGE_CONVERTEDTEXT ||
aClauseAttr == NS_TEXTRANGE_SELECTEDCONVERTEDTEXT,
"aClauseAttr is invalid value");
range.mRangeType = aClauseAttr;
aRanges->AppendElement(range);
}
NS_IMETHODIMP
nsDOMWindowUtils::SendTextEvent(const nsAString& aCompositionString,
PRInt32 aFirstClauseLength,
PRUint32 aFirstClauseAttr,
PRInt32 aSecondClauseLength,
PRUint32 aSecondClauseAttr,
PRInt32 aThirdClauseLength,
PRUint32 aThirdClauseAttr,
PRInt32 aCaretStart,
PRInt32 aCaretLength)
{
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) {
return NS_ERROR_FAILURE;
}
nsTextEvent textEvent(PR_TRUE, NS_TEXT_TEXT, widget);
InitEvent(textEvent);
nsAutoTArray<nsTextRange, 4> textRanges;
NS_ENSURE_TRUE(aFirstClauseLength >= 0, NS_ERROR_INVALID_ARG);
NS_ENSURE_TRUE(aSecondClauseLength >= 0, NS_ERROR_INVALID_ARG);
NS_ENSURE_TRUE(aThirdClauseLength >= 0, NS_ERROR_INVALID_ARG);
AppendClause(aFirstClauseLength, aFirstClauseAttr, &textRanges);
AppendClause(aSecondClauseLength, aSecondClauseAttr, &textRanges);
AppendClause(aThirdClauseLength, aThirdClauseAttr, &textRanges);
PRInt32 len = aFirstClauseLength + aSecondClauseLength + aThirdClauseLength;
NS_ENSURE_TRUE(len == 0 || len == aCompositionString.Length(),
NS_ERROR_FAILURE);
if (aCaretStart >= 0) {
nsTextRange range;
range.mStartOffset = aCaretStart;
range.mEndOffset = range.mStartOffset + aCaretLength;
range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
textRanges.AppendElement(range);
}
textEvent.theText = aCompositionString;
textEvent.rangeCount = textRanges.Length();
textEvent.rangeArray = textRanges.Elements();
nsEventStatus status;
nsresult rv = widget->DispatchEvent(&textEvent, status);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::SendQueryContentEvent(PRUint32 aType,
PRUint32 aOffset, PRUint32 aLength,
PRInt32 aX, PRInt32 aY,
nsIQueryContentEventResult **aResult)
{
*aResult = nsnull;
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) {
return NS_ERROR_FAILURE;
}
if (aType != NS_QUERY_SELECTED_TEXT &&
aType != NS_QUERY_TEXT_CONTENT &&
aType != NS_QUERY_CARET_RECT &&
aType != NS_QUERY_TEXT_RECT &&
aType != NS_QUERY_EDITOR_RECT &&
aType != NS_QUERY_CHARACTER_AT_POINT) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIWidget> targetWidget = widget;
nsIntPoint pt(aX, aY);
if (aType == QUERY_CHARACTER_AT_POINT) {
// Looking for the widget at the point.
nsQueryContentEvent dummyEvent(PR_TRUE, NS_QUERY_CONTENT_STATE, widget);
InitEvent(dummyEvent, &pt);
nsIFrame* popupFrame =
nsLayoutUtils::GetPopupFrameForEventCoordinates(&dummyEvent);
nsIntRect widgetBounds;
nsresult rv = widget->GetClientBounds(widgetBounds);
// There is no popup frame at the point and the point isn't in our widget,
// we cannot process this request.
NS_ENSURE_TRUE(popupFrame || widgetBounds.Contains(pt),
NS_ERROR_FAILURE);
// Fire the event on the widget at the point
if (popupFrame) {
targetWidget = popupFrame->GetWindow();
}
}
pt += widget->WidgetToScreenOffset() - targetWidget->WidgetToScreenOffset();
nsQueryContentEvent queryEvent(PR_TRUE, aType, targetWidget);
InitEvent(queryEvent, &pt);
switch (aType) {
case NS_QUERY_TEXT_CONTENT:
queryEvent.InitForQueryTextContent(aOffset, aLength);
break;
case NS_QUERY_CARET_RECT:
queryEvent.InitForQueryCaretRect(aOffset);
break;
case NS_QUERY_TEXT_RECT:
queryEvent.InitForQueryTextRect(aOffset, aLength);
break;
}
nsEventStatus status;
nsresult rv = targetWidget->DispatchEvent(&queryEvent, status);
NS_ENSURE_SUCCESS(rv, rv);
nsQueryContentEventResult* result = new nsQueryContentEventResult();
NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
result->SetEventResult(widget, queryEvent);
NS_ADDREF(*aResult = result);
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::SendSelectionSetEvent(PRUint32 aOffset,
PRUint32 aLength,
PRBool aReverse,
PRBool *aResult)
{
*aResult = PR_FALSE;
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) {
return NS_ERROR_FAILURE;
}
nsSelectionEvent selectionEvent(PR_TRUE, NS_SELECTION_SET, widget);
InitEvent(selectionEvent);
selectionEvent.mOffset = aOffset;
selectionEvent.mLength = aLength;
selectionEvent.mReversed = aReverse;
nsEventStatus status;
nsresult rv = widget->DispatchEvent(&selectionEvent, status);
NS_ENSURE_SUCCESS(rv, rv);
*aResult = selectionEvent.mSucceeded;
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::SendContentCommandEvent(const nsAString& aType,
nsITransferable * aTransferable)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();

View File

@ -3682,33 +3682,34 @@ nsGlobalWindow::CheckSecurityLeftAndTop(PRInt32* aLeft, PRInt32* aTop)
nsContentUtils::HidePopupsInDocument(doc);
#endif
PRInt32 screenLeft, screenTop, screenWidth, screenHeight;
PRInt32 winLeft, winTop, winWidth, winHeight;
nsGlobalWindow* rootWindow =
static_cast<nsGlobalWindow*>(GetPrivateRoot());
if (rootWindow) {
rootWindow->FlushPendingNotifications(Flush_Layout);
}
// Get the window size
nsCOMPtr<nsIBaseWindow> treeOwner;
GetTreeOwner(getter_AddRefs(treeOwner));
if (treeOwner)
treeOwner->GetPositionAndSize(&winLeft, &winTop, &winWidth, &winHeight);
// convert those values to CSS pixels
// XXX four separate retrievals of the prescontext
winLeft = DevToCSSIntPixels(winLeft);
winTop = DevToCSSIntPixels(winTop);
winWidth = DevToCSSIntPixels(winWidth);
winHeight = DevToCSSIntPixels(winHeight);
// Get the screen dimensions
// XXX This should use nsIScreenManager once it's fully fleshed out.
nsCOMPtr<nsIDOMScreen> screen;
GetScreen(getter_AddRefs(screen));
if (screen) {
if (treeOwner && screen) {
PRInt32 screenLeft, screenTop, screenWidth, screenHeight;
PRInt32 winLeft, winTop, winWidth, winHeight;
// Get the window size
treeOwner->GetPositionAndSize(&winLeft, &winTop, &winWidth, &winHeight);
// convert those values to CSS pixels
// XXX four separate retrievals of the prescontext
winLeft = DevToCSSIntPixels(winLeft);
winTop = DevToCSSIntPixels(winTop);
winWidth = DevToCSSIntPixels(winWidth);
winHeight = DevToCSSIntPixels(winHeight);
// Get the screen dimensions
// XXX This should use nsIScreenManager once it's fully fleshed out.
screen->GetAvailLeft(&screenLeft);
screen->GetAvailWidth(&screenWidth);
screen->GetAvailHeight(&screenHeight);
@ -3724,9 +3725,7 @@ nsGlobalWindow::CheckSecurityLeftAndTop(PRInt32* aLeft, PRInt32* aTop)
#else
screen->GetAvailTop(&screenTop);
#endif
}
if (screen && treeOwner) {
if (aLeft) {
if (screenLeft+screenWidth < *aLeft+winWidth)
*aLeft = screenLeft+screenWidth - winWidth;

View File

@ -59,6 +59,8 @@
#include "nsIContentViewerEdit.h"
#include "nsIContentViewer.h"
#include "nsFocusManager.h"
#include "nsCopySupport.h"
#include "nsGUIEvent.h"
#include "nsIClipboardDragDropHooks.h"
#include "nsIClipboardDragDropHookList.h"
@ -407,7 +409,71 @@ nsSelectCommand::DoSelectCommand(const char *aCommandName, nsIDOMWindow *aWindow
#pragma mark -
#endif
class nsClipboardBaseCommand : public nsIControllerCommand
class nsClipboardCommand : public nsIControllerCommand
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSICONTROLLERCOMMAND
};
NS_IMPL_ISUPPORTS1(nsClipboardCommand, nsIControllerCommand)
nsresult
nsClipboardCommand::IsCommandEnabled(const char* aCommandName, nsISupports *aContext, PRBool *outCmdEnabled)
{
NS_ENSURE_ARG_POINTER(outCmdEnabled);
*outCmdEnabled = PR_FALSE;
if (strcmp(aCommandName, "cmd_copy"))
return NS_OK;
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aContext);
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
nsCOMPtr<nsIDocument> doc = do_QueryInterface(window->GetExtantDocument());
*outCmdEnabled = nsCopySupport::CanCopy(doc);
return NS_OK;
}
nsresult
nsClipboardCommand::DoCommand(const char *aCommandName, nsISupports *aContext)
{
if (strcmp(aCommandName, "cmd_copy"))
return NS_OK;
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aContext);
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
nsIDocShell *docShell = window->GetDocShell();
NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
nsCOMPtr<nsIPresShell> presShell;
docShell->GetPresShell(getter_AddRefs(presShell));
NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
nsCopySupport::FireClipboardEvent(NS_COPY, presShell, nsnull);
return NS_OK;
}
NS_IMETHODIMP
nsClipboardCommand::GetCommandStateParams(const char *aCommandName,
nsICommandParams *aParams, nsISupports *aCommandContext)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
nsClipboardCommand::DoCommandParams(const char *aCommandName, nsICommandParams* aParams, nsISupports *aContext)
{
return DoCommand(aCommandName, aContext);
}
#if 0
#pragma mark -
#endif
class nsSelectionCommand : public nsIControllerCommand
{
public:
@ -425,19 +491,19 @@ protected:
};
NS_IMPL_ISUPPORTS1(nsClipboardBaseCommand, nsIControllerCommand)
NS_IMPL_ISUPPORTS1(nsSelectionCommand, nsIControllerCommand)
/*---------------------------------------------------------------------------
nsClipboardBaseCommand
nsSelectionCommand
----------------------------------------------------------------------------*/
NS_IMETHODIMP
nsClipboardBaseCommand::IsCommandEnabled(const char * aCommandName,
nsISupports *aCommandContext,
PRBool *outCmdEnabled)
nsSelectionCommand::IsCommandEnabled(const char * aCommandName,
nsISupports *aCommandContext,
PRBool *outCmdEnabled)
{
NS_ENSURE_ARG_POINTER(outCmdEnabled);
*outCmdEnabled = PR_FALSE;
@ -450,8 +516,8 @@ nsClipboardBaseCommand::IsCommandEnabled(const char * aCommandName,
}
NS_IMETHODIMP
nsClipboardBaseCommand::DoCommand(const char *aCommandName,
nsISupports *aCommandContext)
nsSelectionCommand::DoCommand(const char *aCommandName,
nsISupports *aCommandContext)
{
nsCOMPtr<nsIContentViewerEdit> contentEdit;
GetContentViewerEditFromContext(aCommandContext, getter_AddRefs(contentEdit));
@ -461,17 +527,17 @@ nsClipboardBaseCommand::DoCommand(const char *aCommandName,
}
NS_IMETHODIMP
nsClipboardBaseCommand::GetCommandStateParams(const char *aCommandName,
nsICommandParams *aParams,
nsISupports *aCommandContext)
nsSelectionCommand::GetCommandStateParams(const char *aCommandName,
nsICommandParams *aParams,
nsISupports *aCommandContext)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsClipboardBaseCommand::DoCommandParams(const char *aCommandName,
nsICommandParams *aParams,
nsISupports *aCommandContext)
nsSelectionCommand::DoCommandParams(const char *aCommandName,
nsICommandParams *aParams,
nsISupports *aCommandContext)
{
nsCOMPtr<nsIContentViewerEdit> contentEdit;
GetContentViewerEditFromContext(aCommandContext, getter_AddRefs(contentEdit));
@ -481,8 +547,8 @@ nsClipboardBaseCommand::DoCommandParams(const char *aCommandName,
}
nsresult
nsClipboardBaseCommand::GetContentViewerEditFromContext(nsISupports *aContext,
nsIContentViewerEdit **aEditInterface)
nsSelectionCommand::GetContentViewerEditFromContext(nsISupports *aContext,
nsIContentViewerEdit **aEditInterface)
{
NS_ENSURE_ARG(aEditInterface);
*aEditInterface = nsnull;
@ -508,7 +574,7 @@ nsClipboardBaseCommand::GetContentViewerEditFromContext(nsISupports *aContext,
#endif
#define NS_DECL_CLIPBOARD_COMMAND(_cmd) \
class _cmd : public nsClipboardBaseCommand \
class _cmd : public nsSelectionCommand \
{ \
protected: \
\
@ -519,68 +585,11 @@ protected:
/* no member variables, please, we're stateless! */ \
};
NS_DECL_CLIPBOARD_COMMAND(nsClipboardCopyCommand)
NS_DECL_CLIPBOARD_COMMAND(nsClipboardCutCommand)
NS_DECL_CLIPBOARD_COMMAND(nsClipboardPasteCommand)
NS_DECL_CLIPBOARD_COMMAND(nsClipboardCopyLinkCommand)
NS_DECL_CLIPBOARD_COMMAND(nsClipboardImageCommands)
NS_DECL_CLIPBOARD_COMMAND(nsClipboardSelectAllNoneCommands)
NS_DECL_CLIPBOARD_COMMAND(nsClipboardGetContentsCommand)
#if 0
#pragma mark -
#endif
nsresult
nsClipboardCutCommand::IsClipboardCommandEnabled(const char* aCommandName, nsIContentViewerEdit* aEdit, PRBool *outCmdEnabled)
{
return aEdit->GetCutable(outCmdEnabled);
}
nsresult
nsClipboardCutCommand::DoClipboardCommand(const char *aCommandName, nsIContentViewerEdit* aEdit, nsICommandParams* aParams)
{
return aEdit->CutSelection();
}
#if 0
#pragma mark -
#endif
nsresult
nsClipboardCopyCommand::IsClipboardCommandEnabled(const char* aCommandName, nsIContentViewerEdit* aEdit, PRBool *outCmdEnabled)
{
return aEdit->GetCopyable(outCmdEnabled);
}
nsresult
nsClipboardCopyCommand::DoClipboardCommand(const char *aCommandName, nsIContentViewerEdit* aEdit, nsICommandParams* aParams)
{
return aEdit->CopySelection();
}
#if 0
#pragma mark -
#endif
nsresult
nsClipboardPasteCommand::IsClipboardCommandEnabled(const char* aCommandName, nsIContentViewerEdit* aEdit, PRBool *outCmdEnabled)
{
return aEdit->GetPasteable(outCmdEnabled);
}
nsresult
nsClipboardPasteCommand::DoClipboardCommand(const char *aCommandName, nsIContentViewerEdit* aEdit, nsICommandParams* aParams)
{
return aEdit->Paste();
}
#if 0
#pragma mark -
#endif
nsresult
nsClipboardCopyLinkCommand::IsClipboardCommandEnabled(const char* aCommandName, nsIContentViewerEdit* aEdit, PRBool *outCmdEnabled)
{
@ -965,9 +974,9 @@ nsWindowCommandRegistration::RegisterWindowCommands(
NS_REGISTER_NEXT_COMMAND(nsSelectCommand, sSelectTopString);
NS_REGISTER_LAST_COMMAND(nsSelectCommand, sSelectBottomString);
NS_REGISTER_ONE_COMMAND(nsClipboardCopyCommand, "cmd_copy");
NS_REGISTER_ONE_COMMAND(nsClipboardCutCommand, "cmd_cut");
NS_REGISTER_ONE_COMMAND(nsClipboardPasteCommand, "cmd_paste");
NS_REGISTER_ONE_COMMAND(nsClipboardCommand, "cmd_cut");
NS_REGISTER_ONE_COMMAND(nsClipboardCommand, "cmd_copy");
NS_REGISTER_ONE_COMMAND(nsClipboardCommand, "cmd_paste");
NS_REGISTER_ONE_COMMAND(nsClipboardCopyLinkCommand, "cmd_copyLink");
NS_REGISTER_FIRST_COMMAND(nsClipboardImageCommands, sCopyImageLocationString);
NS_REGISTER_NEXT_COMMAND(nsClipboardImageCommands, sCopyImageContentsString);

View File

@ -0,0 +1,184 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Mozilla Japan.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Masayuki Nakano <masayuki@d-toybox.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsQueryContentEventResult.h"
#include "nsGUIEvent.h"
#include "nsIWidget.h"
#include "nsPoint.h"
NS_INTERFACE_MAP_BEGIN(nsQueryContentEventResult)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIQueryContentEventResult)
NS_INTERFACE_MAP_ENTRY(nsIQueryContentEventResult)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(nsQueryContentEventResult)
NS_IMPL_RELEASE(nsQueryContentEventResult)
nsQueryContentEventResult::nsQueryContentEventResult() :
mEventID(0), mSucceeded(PR_FALSE)
{
}
nsQueryContentEventResult::~nsQueryContentEventResult()
{
}
NS_IMETHODIMP
nsQueryContentEventResult::GetOffset(PRUint32 *aOffset)
{
PRBool notFound;
nsresult rv = GetNotFound(&notFound);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(!notFound, NS_ERROR_NOT_AVAILABLE);
*aOffset = mOffset;
return NS_OK;
}
static PRBool IsRectEnabled(PRUint32 aEventID)
{
return aEventID == NS_QUERY_CARET_RECT ||
aEventID == NS_QUERY_TEXT_RECT ||
aEventID == NS_QUERY_EDITOR_RECT ||
aEventID == NS_QUERY_CHARACTER_AT_POINT;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetReversed(PRBool *aReversed)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(mEventID == NS_QUERY_SELECTED_TEXT,
NS_ERROR_NOT_AVAILABLE);
*aReversed = mReversed;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetLeft(PRInt32 *aLeft)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(IsRectEnabled(mEventID),
NS_ERROR_NOT_AVAILABLE);
*aLeft = mRect.x;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetWidth(PRInt32 *aWidth)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(IsRectEnabled(mEventID),
NS_ERROR_NOT_AVAILABLE);
*aWidth = mRect.width;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetTop(PRInt32 *aTop)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(IsRectEnabled(mEventID),
NS_ERROR_NOT_AVAILABLE);
*aTop = mRect.y;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetHeight(PRInt32 *aHeight)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(IsRectEnabled(mEventID),
NS_ERROR_NOT_AVAILABLE);
*aHeight = mRect.height;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetText(nsAString &aText)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(mEventID == NS_QUERY_SELECTED_TEXT ||
mEventID == NS_QUERY_TEXT_CONTENT,
NS_ERROR_NOT_AVAILABLE);
aText = mString;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetSucceeded(PRBool *aSucceeded)
{
NS_ENSURE_TRUE(mEventID != 0, NS_ERROR_NOT_INITIALIZED);
*aSucceeded = mSucceeded;
return NS_OK;
}
NS_IMETHODIMP
nsQueryContentEventResult::GetNotFound(PRBool *aNotFound)
{
NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(mEventID == NS_QUERY_SELECTED_TEXT ||
mEventID == NS_QUERY_CHARACTER_AT_POINT,
NS_ERROR_NOT_AVAILABLE);
*aNotFound = (mOffset == nsQueryContentEvent::NOT_FOUND);
return NS_OK;
}
void
nsQueryContentEventResult::SetEventResult(nsIWidget* aWidget,
const nsQueryContentEvent &aEvent)
{
mEventID = aEvent.message;
mSucceeded = aEvent.mSucceeded;
mReversed = aEvent.mReply.mReversed;
mRect = aEvent.mReply.mRect;
mOffset = aEvent.mReply.mOffset;
mString = aEvent.mReply.mString;
if (!IsRectEnabled(mEventID) || !aWidget || !mSucceeded) {
return;
}
nsIWidget* topWidget = aWidget->GetTopLevelWidget();
if (!topWidget || topWidget == aWidget) {
return;
}
// Convert the top widget related coordinates to the given widget's.
nsIntPoint offset =
aWidget->WidgetToScreenOffset() - topWidget->WidgetToScreenOffset();
mRect.MoveBy(-offset);
}

View File

@ -0,0 +1,65 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Mozilla Japan.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Masayuki Nakano <masayuki@d-toybox.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsIQueryContentEventResult.h"
#include "nsString.h"
#include "nsRect.h"
class nsQueryContentEvent;
class nsIWidget;
class nsQueryContentEventResult : public nsIQueryContentEventResult
{
public:
nsQueryContentEventResult();
~nsQueryContentEventResult();
NS_DECL_ISUPPORTS
NS_DECL_NSIQUERYCONTENTEVENTRESULT
void SetEventResult(nsIWidget* aWidget, const nsQueryContentEvent &aEvent);
protected:
PRUint32 mEventID;
PRUint32 mOffset;
nsString mString;
nsIntRect mRect;
PRPackedBool mSucceeded;
PRPackedBool mReversed;
};

View File

@ -80,6 +80,7 @@ XPIDLSRCS = \
nsIDOMClientRect.idl \
nsIDOMClientRectList.idl \
nsIFocusManager.idl \
nsIQueryContentEventResult.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -50,8 +50,9 @@ interface nsIDOMElement;
interface nsIDOMHTMLCanvasElement;
interface nsIDOMEvent;
interface nsITransferable;
interface nsIQueryContentEventResult;
[scriptable, uuid(5ab44028-20ed-499a-bbe4-1805a1f350c8)]
[scriptable, uuid(00ca8d4f-61f1-4d9c-a7c1-82651b0cf02b)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -443,4 +444,173 @@ interface nsIDOMWindowUtils : nsISupports {
*/
void sendContentCommandEvent(in AString aType,
[optional] in nsITransferable aTransferable);
/**
* Synthesize a composition event to the window.
*
* Cannot be accessed from unprivileged context (not content-accessible)
* Will throw a DOM security error if called without UniversalXPConnect
* privileges.
*
* @param aType The event type: "compositionstart" or "compositionend".
*/
void sendCompositionEvent(in AString aType);
/**
* Synthesize a text event to the window.
*
* Cannot be accessed from unprivileged context (not content-accessible)
* Will throw a DOM security error if called without UniversalXPConnect
* privileges.
*
* Currently, this method doesn't support 4 or more clauses composition
* string.
*
* @param aCompositionString composition string
* @param a*ClauseLengh the length of nth clause, set 0 when you
* don't need second or third clause.
* @param a*ClauseAttr the attribute of nth clause, uese following
* const values.
* @param aCaretStart the caret position in the composition string,
* if you set negative value, this method don't
* set the caret position to the event.
* @param aCaretLength the caret length, if this is one or more,
* the caret will be wide caret, otherwise,
* it's collapsed.
* XXX nsEditor doesn't support wide caret yet.
*/
// NOTE: These values must be same to NS_TEXTRANGE_* in nsGUIEvent.h
const unsigned long COMPOSITION_ATTR_RAWINPUT = 0x02;
const unsigned long COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
const unsigned long COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
const unsigned long COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
void sendTextEvent(in AString aCompositionString,
in long aFirstClauseLength,
in unsigned long aFirstClauseAttr,
in long aSecondClauseLength,
in unsigned long aSecondClauseAttr,
in long aThirdClauseLength,
in unsigned long aThirdClauseAttr,
in long aCaretStart,
in long aCaretLength);
/**
* Synthesize a query content event.
*
* @param aType On of the following const values. And see also each comment
* for the other parameters and the result.
*/
nsIQueryContentEventResult sendQueryContentEvent(in unsigned long aType,
in unsigned long aOffset,
in unsigned long aLength,
in long aX,
in long aY);
// NOTE: following values are same as NS_QUERY_* in nsGUIEvent.h
/**
* QUERY_SELECTED_TEXT queries the first selection range's information.
*
* @param aOffset Not used.
* @param aLength Not used.
* @param aX Not used.
* @param aY Not used.
*
* @return offset, reversed and text properties of the result are available.
*/
const unsigned long QUERY_SELECTED_TEXT = 3200;
/**
* QUERY_TEXT_CONTENT queries the text at the specified range.
*
* @param aOffset The first character's offset. 0 is the first character.
* @param aLength The length of getting text. If the aLength is too long,
* the result text is shorter than this value.
* @param aX Not used.
* @param aY Not used.
*
* @return text property of the result is available.
*/
const unsigned long QUERY_TEXT_CONTENT = 3201;
/**
* QUERY_CARET_RECT queries the (collapsed) caret rect of the offset.
* If the actual caret is there at the specified offset, this returns the
* actual caret rect. Otherwise, this guesses the caret rect from the
* metrics of the text.
*
* @param aOffset The caret offset. 0 is the left side of the first
* caracter in LTR text.
* @param aLength Not used.
* @param aX Not used.
* @param aY Not used.
*
* @return left, top, width and height properties of the result are available.
* The left and the top properties are offset in the client area of
* the DOM window.
*/
const unsigned long QUERY_CARET_RECT = 3203;
/**
* QUERY_TEXT_RECT queries the specified text's rect.
*
* @param aOffset The first character's offset. 0 is the first character.
* @param aLength The length of getting text. If the aLength is too long,
* the extra length is ignored.
* @param aX Not used.
* @param aY Not used.
*
* @return left, top, width and height properties of the result are available.
* The left and the top properties are offset in the client area of
* the DOM window.
*/
const unsigned long QUERY_TEXT_RECT = 3204;
/**
* QUERY_TEXT_RECT queries the focused editor's rect.
*
* @param aOffset Not used.
* @param aLength Not used.
* @param aX Not used.
* @param aY Not used.
*
* @return left, top, width and height properties of the result are available.
*/
const unsigned long QUERY_EDITOR_RECT = 3205;
/**
* QUERY_CHARACTER_AT_POINT queries the character information at the
* specified point. The point is offset in the window.
* NOTE: If there are some panels at the point, this method send the query
* event to the panel's widget automatically.
*
* @param aOffset Not used.
* @param aLength Not used.
* @param aX X offset in the widget.
* @param aY Y offset in the widget.
*
* @return offset, notFound, left, top, width and height properties of the
* result are available.
*/
const unsigned long QUERY_CHARACTER_AT_POINT = 3208;
/**
* Synthesize a selection set event to the window.
*
* This sets the selection as the specified information.
*
* @param aOffset The caret offset of the selection start.
* @param aLength The length of the selection. If this is too long, the
* extra length is ignored.
* @param aReverse If true, the selection set from |aOffset + aLength| to
* |aOffset|. Otherwise, set from |aOffset| to
* |aOffset + aLength|.
* @return True, if succeeded. Otherwise, false.
*/
boolean sendSelectionSetEvent(in unsigned long aOffset,
in unsigned long aLength,
in boolean aReverse);
};

View File

@ -0,0 +1,61 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Mozilla Japan.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Masayuki Nakano <masayuki@d-toybox.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
/**
* The result of query content events. succeeded propery can be used always.
* Whether other properties can be used or not depends on the event.
* See nsIDOMWindowUtils.idl, which properites can be used was documented.
*/
[scriptable, uuid(4b4ba266-b51e-4f0f-8d0e-9f13cb2a0056)]
interface nsIQueryContentEventResult : nsISupports
{
readonly attribute unsigned long offset;
readonly attribute boolean reversed;
readonly attribute long left;
readonly attribute long top;
readonly attribute long width;
readonly attribute long height;
readonly attribute AString text;
readonly attribute boolean succeeded;
readonly attribute boolean notFound;
};

View File

@ -40,7 +40,7 @@
#include "nsIDOMSVGCSS2Properties.idl"
[scriptable, uuid(be39bc6d-3b24-4421-9599-8fdf3ad33baf)]
[scriptable, uuid(649B0B41-C5F7-4EA2-B6DF-CFFF6B6DD30A)]
interface nsIDOMNSCSS2Properties : nsIDOMSVGCSS2Properties
{
/* Non-DOM 2 extensions */
@ -281,4 +281,7 @@ interface nsIDOMNSCSS2Properties : nsIDOMSVGCSS2Properties
attribute DOMString MozTabSize;
// raises(DOMException) on setting
attribute DOMString MozResize;
// raises(DOMException) on setting
};

View File

@ -55,6 +55,7 @@ _TEST_FILES = \
test_offsets.js \
test_offsets.xul \
test_windowProperties.html \
test_clipboard_events.html \
$(NULL)
libs:: $(_TEST_FILES)

View File

@ -0,0 +1,322 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Clipboard Events</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="border: 3px solid black; padding: 3em;">CONTENT TEXT<input id="content-input" value="INPUT TEXT"></div>
<pre id="test">
<script class="testbody" type="text/javascript;version=1.7">
// Enable full privledges for clipboard read/write operations.
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var content = document.getElementById("content");
var contentInput = document.getElementById("content-input");
var clipboardInitialValue = "empty";
// Test that clearing and reading the clipboard works. A random number
// is used to make sure that leftover clipboard values from a previous
// test run don't cause a false-positive test.
var cb_text = "empty_" + Math.random();
setClipboardText(cb_text);
is(getClipboardText(), cb_text, "set/get clipboard text failed");
// Some test functions need to be run with delays.
var delayedTests = [];
// Ensure window focus before running tests, otherwise key events can
// misfire. We set the onfocus event handler here to actually begin
// running tests, and call window.focus() afterwards.
window.onfocus = function()
{
window.onfocus = null;
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
// A list of test functions to run. Before each test function is run, the
// clipboard is initialized to clipboardInitialValue, and the contents of
// div#content are set as the window's selection.
var testFunctions = [
test_dom_oncopy,
test_dom_oncut,
test_dom_onpaste,
test_dom_oncopy_abort,
test_input_oncopy,
test_input_oncut,
test_input_onpaste,
test_input_oncopy_abort,
test_input_oncut_abort,
test_input_onpaste_abort,
];
// Run the main tests. This will also populate the delayedTests array
for (let i = 0; i < testFunctions.length; i++) {
// Init clipboard
setClipboardText(clipboardInitialValue);
// Reset value of contentInput.
contentInput.value = "INPUT TEXT";
var func = testFunctions[i];
func();
}
SimpleTest.finish();
}
// Calling .focus begins the test run.
SimpleTest.waitForExplicitFinish();
window.focus();
function getClipboardText() {
var trans = Components.classes["@mozilla.org/widget/transferable;1"]
.createInstance();
trans = trans.QueryInterface(Components.interfaces.nsITransferable);
trans.addDataFlavor("text/unicode");
var clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
.getService();
clipboard = clipboard.QueryInterface(Components.interfaces.nsIClipboard);
clipboard.getData(trans, clipboard.kGlobalClipboard);
var str = new Object();
var strLen = new Object();
try {
trans.getTransferData("text/unicode", str, strLen);
} catch(e) {
// NS_ERROR_FAILURE will occur if the transferable object has no
// text/unicode data in it. In that case, it's not an error:
if (e instanceof Components.interfaces.nsIXPCException &&
e.result == Components.results.NS_ERROR_FAILURE) {
return null;
} else {
// if we don't know how to handle it then rethrow
throw e;
}
}
if (!str) return null;
str = str.value.QueryInterface(Components.interfaces.nsISupportsString);
if (!str) return null;
str = str.data.substring(0, strLen.value / 2);
if (!str) return null;
return str;
}
function setClipboardText(text) {
var helper = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
.getService(Components.interfaces.nsIClipboardHelper);
helper.copyString(text);
}
function selectContentDiv() {
// Set selection
var selection = window.getSelection();
selection.removeAllRanges();
selection.selectAllChildren(content);
}
function selectContentInput() {
contentInput.select();
contentInput.focus();
}
function test_dom_oncopy() {
// Setup an oncopy event handler, fire copy. Ensure that the event
// handler was called, and the clipboard contents have set to CONTENT TEXT.
// Test firing oncopy event on ctrl-c:
selectContentDiv();
var oncopy_fired = false;
content.oncopy = function() { oncopy_fired = true; };
try {
synthesizeKey("c", {accelKey: 1});
ok(oncopy_fired, "copy event firing on DOM element");
is(getClipboardText(), "CONTENT TEXT",
"copy on DOM element set clipboard correctly");
} finally {
content.oncopy = null;
}
}
function test_dom_oncut() {
// Setup an oncut event handler, fire cut. Ensure that the event handler
// was called. The <div> doesn't handle a cut, so ensure that the
// clipboard text is clipboardInitialValue, NOT "CONTENT TEXT".
selectContentDiv();
var oncut_fired = false;
content.oncut = function() { oncut_fired = true; };
try {
synthesizeKey("x", {accelKey: 1});
ok(!oncut_fired, "cut event firing on DOM element")
is(getClipboardText(), clipboardInitialValue,
"cut on DOM element did not modify clipboard");
} finally {
content.oncut = null;
}
}
function test_dom_onpaste() {
// Setup an onpaste event handler, fire paste. Ensure that the event
// handler was called.
selectContentDiv();
var onpaste_fired = false;
content.onpaste = function() { onpaste_fired = true; };
try {
synthesizeKey("v", {accelKey: 1});
ok(!onpaste_fired, "paste event firing on DOM element");
} finally {
content.onpaste = null;
}
}
function test_dom_oncopy_abort() {
// Setup an oncopy event handler that aborts the copy, and fire the copy
// event. Ensure that the event handler was fired, and the clipboard
// contents have not been modified.
selectContentDiv();
var oncopy_fired = false;
content.oncopy = function() { oncopy_fired = true; return false; };
try {
synthesizeKey("c", {accelKey: 1});
ok(oncopy_fired, "copy event (to-be-cancelled) firing on DOM element");
is(getClipboardText(), clipboardInitialValue,
"aborted copy on DOM element did not modify clipboard");
} finally {
content.oncopy = null;
}
}
function test_input_oncopy() {
// Setup an oncopy event handler, fire copy. Ensure that the event
// handler was called, and the clipboard contents have set to INPUT TEXT.
// Test firing oncopy event on ctrl-c:
selectContentInput();
var oncopy_fired = false;
contentInput.oncopy = function() { oncopy_fired = true; };
try {
synthesizeKey("c", {accelKey: 1});
ok(oncopy_fired, "copy event firing on plaintext editor");
is(getClipboardText(), "INPUT TEXT",
"copy on plaintext editor set clipboard correctly");
} finally {
contentInput.oncopy = null;
}
}
function test_input_oncut() {
// Setup an oncut event handler, and fire cut. Ensure that the event
// handler was fired, the clipboard contains the INPUT TEXT, and
// that the input itself is empty.
selectContentInput();
var oncut_fired = false;
contentInput.oncut = function() { oncut_fired = true; };
try {
synthesizeKey("x", {accelKey: 1});
ok(oncut_fired, "cut event firing on plaintext editor");
is(getClipboardText(), "INPUT TEXT",
"cut on plaintext editor set clipboard correctly");
is(contentInput.value, "",
"cut on plaintext editor emptied editor");
} finally {
contentInput.oncut = null;
}
}
function test_input_onpaste() {
// Setup an onpaste event handler, and fire paste. Ensure that the event
// handler was fired, the clipboard contents didn't change, and that the
// input value did change (ie. paste succeeded).
selectContentInput();
var onpaste_fired = false;
contentInput.onpaste = function() { onpaste_fired = true; };
try {
synthesizeKey("v", {accelKey: 1});
ok(onpaste_fired, "paste event firing on plaintext editor");
is(getClipboardText(), clipboardInitialValue,
"paste on plaintext editor did not modify clipboard contents");
is(contentInput.value, clipboardInitialValue,
"paste on plaintext editor did modify editor value");
} finally {
contentInput.onpaste = null;
}
}
function test_input_oncopy_abort() {
// Setup an oncopy event handler, fire copy. Ensure that the event
// handler was called, and that the clipboard value did NOT change.
selectContentInput();
var oncopy_fired = false;
contentInput.oncopy = function() { oncopy_fired = true; return false; };
try {
synthesizeKey("c", {accelKey: 1});
ok(oncopy_fired, "copy event (to-be-cancelled) firing on plaintext editor");
is(getClipboardText(), clipboardInitialValue,
"aborted copy on plaintext editor did not modify clipboard");
} finally {
contentInput.oncopy = null;
}
}
function test_input_oncut_abort() {
// Setup an oncut event handler, and fire cut. Ensure that the event
// handler was fired, the clipboard contains the INPUT TEXT, and
// that the input itself is empty.
selectContentInput();
var oncut_fired = false;
contentInput.oncut = function() { oncut_fired = true; return false; };
try {
synthesizeKey("x", {accelKey: 1});
ok(oncut_fired, "cut event (to-be-cancelled) firing on plaintext editor");
is(getClipboardText(), clipboardInitialValue,
"aborted cut on plaintext editor did not modify clipboard.");
is(contentInput.value, "INPUT TEXT",
"aborted cut on plaintext editor did not modify editor contents");
} finally {
contentInput.oncut = null;
}
}
function test_input_onpaste_abort() {
// Setup an onpaste event handler, and fire paste. Ensure that the event
// handler was fired, the clipboard contents didn't change, and that the
// input value did change (ie. paste succeeded).
selectContentInput();
var onpaste_fired = false;
contentInput.onpaste = function() { onpaste_fired = true; return false; };
try {
synthesizeKey("v", {accelKey: 1});
ok(onpaste_fired,
"paste event (to-be-cancelled) firing on plaintext editor");
is(getClipboardText(), clipboardInitialValue,
"aborted paste on plaintext editor did not modify clipboard");
is(contentInput.value, "INPUT TEXT",
"aborted paste on plaintext editor did not modify modified editor value");
} finally {
contentInput.onpaste = null;
}
}
</script>
</pre>
</body>
</html>

View File

@ -1847,14 +1847,11 @@ PRBool nsHTMLEditor::HavePrivateHTMLFlavor(nsIClipboard *aClipboard)
NS_IMETHODIMP nsHTMLEditor::Paste(PRInt32 aSelectionType)
{
ForceCompositionEnd();
PRBool preventDefault;
nsresult rv = FireClipboardEvent(NS_PASTE, &preventDefault);
if (NS_FAILED(rv) || preventDefault)
return rv;
if (!FireClipboardEvent(NS_PASTE))
return NS_OK;
// Get Clipboard Service
nsresult rv;
nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
if (NS_FAILED(rv))
return rv;
@ -1932,12 +1929,8 @@ NS_IMETHODIMP nsHTMLEditor::Paste(PRInt32 aSelectionType)
NS_IMETHODIMP nsHTMLEditor::PasteTransferable(nsITransferable *aTransferable)
{
ForceCompositionEnd();
PRBool preventDefault;
nsresult rv = FireClipboardEvent(NS_PASTE, &preventDefault);
if (NS_FAILED(rv) || preventDefault)
return rv;
if (!FireClipboardEvent(NS_PASTE))
return NS_OK;
// handle transferable hooks
nsCOMPtr<nsIDOMDocument> domdoc;
@ -1948,10 +1941,8 @@ NS_IMETHODIMP nsHTMLEditor::PasteTransferable(nsITransferable *aTransferable)
// Beware! This may flush notifications via synchronous
// ScrollSelectionIntoView.
nsAutoString contextStr, infoStr;
rv = InsertFromTransferable(aTransferable, nsnull, contextStr, infoStr,
nsnull, 0, PR_TRUE);
return rv;
return InsertFromTransferable(aTransferable, nsnull, contextStr, infoStr,
nsnull, 0, PR_TRUE);
}
//

View File

@ -60,6 +60,7 @@
#include "nsITransferable.h"
#include "nsIDragService.h"
#include "nsIDOMNSUIEvent.h"
#include "nsCopySupport.h"
// Misc
#include "nsEditorUtils.h"
@ -409,14 +410,11 @@ NS_IMETHODIMP nsPlaintextEditor::DoDrag(nsIDOMEvent *aDragEvent)
NS_IMETHODIMP nsPlaintextEditor::Paste(PRInt32 aSelectionType)
{
ForceCompositionEnd();
PRBool preventDefault;
nsresult rv = FireClipboardEvent(NS_PASTE, &preventDefault);
if (NS_FAILED(rv) || preventDefault)
return rv;
if (!FireClipboardEvent(NS_PASTE))
return NS_OK;
// Get Clipboard Service
nsresult rv;
nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
if ( NS_FAILED(rv) )
return rv;
@ -446,12 +444,8 @@ NS_IMETHODIMP nsPlaintextEditor::Paste(PRInt32 aSelectionType)
NS_IMETHODIMP nsPlaintextEditor::PasteTransferable(nsITransferable *aTransferable)
{
ForceCompositionEnd();
PRBool preventDefault;
nsresult rv = FireClipboardEvent(NS_PASTE, &preventDefault);
if (NS_FAILED(rv) || preventDefault)
return rv;
if (!FireClipboardEvent(NS_PASTE))
return NS_OK;
if (!IsModifiable())
return NS_OK;
@ -464,9 +458,7 @@ NS_IMETHODIMP nsPlaintextEditor::PasteTransferable(nsITransferable *aTransferabl
// Beware! This may flush notifications via synchronous
// ScrollSelectionIntoView.
rv = InsertTextFromTransferable(aTransferable, nsnull, nsnull, PR_TRUE);
return rv;
return InsertTextFromTransferable(aTransferable, nsnull, nsnull, PR_TRUE);
}
NS_IMETHODIMP nsPlaintextEditor::CanPaste(PRInt32 aSelectionType, PRBool *aCanPaste)

View File

@ -1142,128 +1142,66 @@ nsPlaintextEditor::Redo(PRUint32 aCount)
return result;
}
nsresult nsPlaintextEditor::GetClipboardEventTarget(nsIDOMNode** aEventTarget)
PRBool
nsPlaintextEditor::CanCutOrCopy()
{
NS_ENSURE_ARG_POINTER(aEventTarget);
*aEventTarget = nsnull;
nsCOMPtr<nsISelection> selection;
nsresult res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res))
return res;
if (NS_FAILED(GetSelection(getter_AddRefs(selection))))
return PR_FALSE;
return nsCopySupport::GetClipboardEventTarget(selection, aEventTarget);
PRBool isCollapsed;
selection->GetIsCollapsed(&isCollapsed);
return !isCollapsed;
}
nsresult nsPlaintextEditor::FireClipboardEvent(PRUint32 msg,
PRBool* aPreventDefault)
PRBool
nsPlaintextEditor::FireClipboardEvent(PRInt32 aType)
{
*aPreventDefault = PR_FALSE;
if (aType == NS_PASTE)
ForceCompositionEnd();
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps)
return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShellWeak);
NS_ENSURE_TRUE(presShell, PR_FALSE);
// Unsafe to fire event during reflow (bug 396108)
PRBool isReflowing = PR_TRUE;
nsresult rv = ps->IsReflowLocked(&isReflowing);
if (NS_FAILED(rv) || isReflowing)
return NS_OK;
nsCOMPtr<nsISelection> selection;
if (NS_FAILED(GetSelection(getter_AddRefs(selection))))
return PR_FALSE;
nsCOMPtr<nsIDOMNode> eventTarget;
rv = GetClipboardEventTarget(getter_AddRefs(eventTarget));
if (NS_FAILED(rv))
// On failure to get event target, just forget about it and don't fire.
return NS_OK;
if (!nsCopySupport::FireClipboardEvent(aType, presShell, selection))
return PR_FALSE;
nsEventStatus status = nsEventStatus_eIgnore;
nsEvent evt(PR_TRUE, msg);
nsEventDispatcher::Dispatch(eventTarget, ps->GetPresContext(), &evt,
nsnull, &status);
// if event handler return'd false (PreventDefault)
if (status == nsEventStatus_eConsumeNoDefault)
*aPreventDefault = PR_TRUE;
// Did the event handler cause the editor to be destroyed? (ie. the input
// element was removed from the document) Don't proceed with command,
// could crash, definitely does during paste.
if (mDidPreDestroy)
return NS_ERROR_NOT_INITIALIZED;
return NS_OK;
// If the event handler caused the editor to be destroyed, return false.
// Otherwise return true to indicate that the event was not cancelled.
return !mDidPreDestroy;
}
NS_IMETHODIMP nsPlaintextEditor::Cut()
{
PRBool preventDefault;
nsresult rv = FireClipboardEvent(NS_CUT, &preventDefault);
if (NS_FAILED(rv) || preventDefault)
return rv;
nsCOMPtr<nsISelection> selection;
rv = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(rv))
return rv;
PRBool isCollapsed;
if (NS_SUCCEEDED(selection->GetIsCollapsed(&isCollapsed)) && isCollapsed)
return NS_OK; // just return ok so no JS error is thrown
// ps should be guaranteed by FireClipboardEvent not failing
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
rv = ps->DoCopy();
if (NS_SUCCEEDED(rv))
rv = DeleteSelection(eNone);
return rv;
if (FireClipboardEvent(NS_CUT))
return DeleteSelection(eNone);
return NS_OK;
}
NS_IMETHODIMP nsPlaintextEditor::CanCut(PRBool *aCanCut)
{
NS_ENSURE_ARG_POINTER(aCanCut);
*aCanCut = PR_FALSE;
nsCOMPtr<nsISelection> selection;
nsresult rv = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(rv)) return rv;
PRBool isCollapsed;
rv = selection->GetIsCollapsed(&isCollapsed);
if (NS_FAILED(rv)) return rv;
*aCanCut = !isCollapsed && IsModifiable();
*aCanCut = IsModifiable() && CanCutOrCopy();
return NS_OK;
}
NS_IMETHODIMP nsPlaintextEditor::Copy()
{
PRBool preventDefault;
nsresult rv = FireClipboardEvent(NS_COPY, &preventDefault);
if (NS_FAILED(rv) || preventDefault)
return rv;
// ps should be guaranteed by FireClipboardEvent not failing
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
return ps->DoCopy();
FireClipboardEvent(NS_COPY);
return NS_OK;
}
NS_IMETHODIMP nsPlaintextEditor::CanCopy(PRBool *aCanCopy)
{
NS_ENSURE_ARG_POINTER(aCanCopy);
*aCanCopy = PR_FALSE;
nsCOMPtr<nsISelection> selection;
nsresult rv = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(rv)) return rv;
PRBool isCollapsed;
rv = selection->GetIsCollapsed(&isCollapsed);
if (NS_FAILED(rv)) return rv;
*aCanCopy = !isCollapsed;
*aCanCopy = CanCutOrCopy();
return NS_OK;
}
// Shared between OutputToString and OutputToStream
NS_IMETHODIMP
nsPlaintextEditor::GetAndInitDocEncoder(const nsAString& aFormatType,

View File

@ -219,10 +219,8 @@ protected:
PRBool mIgnoreSpuriousDragEvent;
NS_IMETHOD IgnoreSpuriousDragEvent(PRBool aIgnoreSpuriousDragEvent) {mIgnoreSpuriousDragEvent = aIgnoreSpuriousDragEvent; return NS_OK;}
// Wrapper for nsCopySupport::GetClipboardEventTarget, finds target to fire
// [cut,copy,paste] and [beforecut,beforecopy,beforepaste] events at.
nsresult GetClipboardEventTarget(nsIDOMNode** aEventTarget);
nsresult FireClipboardEvent(PRUint32 msg, PRBool* aPreventDefault);
PRBool CanCutOrCopy();
PRBool FireClipboardEvent(PRInt32 aType);
// Data members
protected:

View File

@ -367,22 +367,6 @@ RPCChannel::OnMaybeDequeueOne()
AssertWorkerThread();
mMutex.AssertNotCurrentThreadOwns();
if (IsOnCxxStack())
// We're running in a nested event loop, and there's
// RPCChannel code below us on the stack. We don't want to
// dispatch this new message here because the assumptions made
// by the code below us on the stack have changed. Just
// bailing here isn't enough, however, because we also have to
// ensure that the messages received in this nested loop
// aren't "lost". We might be running in this nested context
// above a non-interruptable handler; these are async
// in/out-msg, sync in/out-msg, and rpc in-call. So, we still
// bail here, but ensure that at each exit point, we fix up
// the IO thread's invariant. Luckily, since we already track
// the C++ stack, we know when these exit points are hit:
// ExitedCxxStack().
return;
Message recvd;
{
MutexAutoLock lock(mMutex);
@ -402,7 +386,13 @@ RPCChannel::OnMaybeDequeueOne()
mPending.pop();
}
RPC_ASSERT(!IsOnCxxStack(), "RPCChannel code not on C++ stack");
if (IsOnCxxStack() && recvd.is_rpc() && recvd.is_reply()) {
// We probably just received a reply in a nested loop for an
// RPC call sent before entering that loop.
mOutOfTurnReplies[recvd.seqno()] = recvd;
return;
}
CxxStackFrame f(*this, IN_MESSAGE, &recvd);
if (recvd.is_rpc())

View File

@ -54,6 +54,7 @@
#define MOZ_IPDL_TESTFAIL_LABEL "TEST-UNEXPECTED-FAIL"
#define MOZ_IPDL_TESTPASS_LABEL "TEST-PASS"
#define MOZ_IPDL_TESTINFO_LABEL "TEST-INFO"
namespace mozilla {

View File

@ -126,12 +126,14 @@ IPDLUnitTestMain(void* aData)
IPDLUnitTestType test = IPDLUnitTestFromString(testString);
if (!test) {
// use this instead of |fail()| because we don't know what the test is
fprintf(stderr, MOZ_IPDL_TESTFAIL_LABEL "| %s | unknown unit test %s\\n",
fprintf(stderr, MOZ_IPDL_TESTFAIL_LABEL "| %s | unknown unit test %s\n",
"<--->", testString);
NS_RUNTIMEABORT("can't continue");
}
gIPDLUnitTestName = testString;
printf(MOZ_IPDL_TESTINFO_LABEL "| running test | %s\n", gIPDLUnitTestName);
std::vector<std::string> testCaseArgs;
testCaseArgs.push_back(testString);

View File

@ -4745,6 +4745,11 @@ nsCSSFrameConstructor::FindSVGData(nsIContent* aContent,
return &sSuppressData;
}
// We don't need frames for animation elements
if (aContent->IsNodeOfType(nsINode::eANIMATION)) {
return &sSuppressData;
}
// Reduce the number of frames we create unnecessarily. Note that this is not
// where we select which frame in a <switch> to render! That happens in
// nsSVGSwitchFrame::PaintSVG.

View File

@ -398,9 +398,6 @@ private:
nsresult GetDocumentSelection(nsISelection **aSelection);
nsresult GetClipboardEventTarget(nsIDOMNode **aEventTarget);
nsresult FireClipboardEvent(PRUint32 msg, PRBool* aPreventDefault);
void DestroyPresShell();
#ifdef NS_PRINTING
@ -2410,8 +2407,7 @@ DocumentViewerImpl::CreateDeviceContext(nsIView* aContainerView)
}
// Return the selection for the document. Note that text fields have their
// own selection, which cannot be accessed with this method. Use
// mPresShell->GetSelectionForCopy() instead.
// own selection, which cannot be accessed with this method.
nsresult DocumentViewerImpl::GetDocumentSelection(nsISelection **aSelection)
{
NS_ENSURE_ARG_POINTER(aSelection);
@ -2431,25 +2427,12 @@ nsresult DocumentViewerImpl::GetDocumentSelection(nsISelection **aSelection)
* nsIContentViewerEdit
* ======================================================================================== */
NS_IMETHODIMP DocumentViewerImpl::Search()
{
// Nothing to do here.
return NS_OK;
}
NS_IMETHODIMP DocumentViewerImpl::GetSearchable(PRBool *aSearchable)
{
// Nothing to do here.
*aSearchable = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP DocumentViewerImpl::ClearSelection()
{
nsresult rv;
nsCOMPtr<nsISelection> selection;
// use mPresShell->GetSelectionForCopy() ?
// use nsCopySupport::GetSelectionForCopy() ?
rv = GetDocumentSelection(getter_AddRefs(selection));
if (NS_FAILED(rv)) return rv;
@ -2464,7 +2447,7 @@ NS_IMETHODIMP DocumentViewerImpl::SelectAll()
nsCOMPtr<nsISelection> selection;
nsresult rv;
// use mPresShell->GetSelectionForCopy() ?
// use nsCopySupport::GetSelectionForCopy() ?
rv = GetDocumentSelection(getter_AddRefs(selection));
if (NS_FAILED(rv)) return rv;
@ -2494,12 +2477,8 @@ NS_IMETHODIMP DocumentViewerImpl::SelectAll()
NS_IMETHODIMP DocumentViewerImpl::CopySelection()
{
PRBool preventDefault;
nsresult rv = FireClipboardEvent(NS_COPY, &preventDefault);
if (NS_FAILED(rv) || preventDefault)
return rv;
return mPresShell->DoCopy();
nsCopySupport::FireClipboardEvent(NS_COPY, mPresShell, nsnull);
return NS_OK;
}
NS_IMETHODIMP DocumentViewerImpl::CopyLinkLocation()
@ -2532,118 +2511,47 @@ NS_IMETHODIMP DocumentViewerImpl::CopyImage(PRInt32 aCopyFlags)
return nsCopySupport::ImageCopy(node, aCopyFlags);
}
nsresult DocumentViewerImpl::GetClipboardEventTarget(nsIDOMNode** aEventTarget)
{
NS_ENSURE_ARG_POINTER(aEventTarget);
*aEventTarget = nsnull;
if (!mPresShell)
return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsISelection> sel;
nsresult rv = mPresShell->GetSelectionForCopy(getter_AddRefs(sel));
if (NS_FAILED(rv))
return rv;
if (!sel)
return NS_ERROR_FAILURE;
return nsCopySupport::GetClipboardEventTarget(sel, aEventTarget);
}
nsresult DocumentViewerImpl::FireClipboardEvent(PRUint32 msg,
PRBool* aPreventDefault)
{
*aPreventDefault = PR_FALSE;
NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED);
// It seems to be unsafe to fire an event handler during reflow (bug 393696)
PRBool isReflowing = PR_TRUE;
nsresult rv = mPresShell->IsReflowLocked(&isReflowing);
if (NS_FAILED(rv) || isReflowing)
return NS_OK;
nsCOMPtr<nsIDOMNode> eventTarget;
rv = GetClipboardEventTarget(getter_AddRefs(eventTarget));
if (NS_FAILED(rv))
// On failure to get event target, just forget about it and don't fire.
return NS_OK;
nsEventStatus status = nsEventStatus_eIgnore;
nsEvent evt(PR_TRUE, msg);
nsEventDispatcher::Dispatch(eventTarget, mPresContext, &evt, nsnull,
&status);
// if event handler return'd false (PreventDefault)
if (status == nsEventStatus_eConsumeNoDefault)
*aPreventDefault = PR_TRUE;
// Ensure that the calling function can use mPresShell -- if the event
// handler closed this window, mPresShell will be gone.
NS_ENSURE_STATE(mPresShell);
return NS_OK;
}
NS_IMETHODIMP DocumentViewerImpl::GetCopyable(PRBool *aCopyable)
{
NS_ENSURE_ARG_POINTER(aCopyable);
*aCopyable = PR_FALSE;
NS_ENSURE_STATE(mPresShell);
nsCOMPtr<nsISelection> selection;
nsresult rv = mPresShell->GetSelectionForCopy(getter_AddRefs(selection));
if (NS_FAILED(rv))
return rv;
PRBool isCollapsed;
selection->GetIsCollapsed(&isCollapsed);
*aCopyable = !isCollapsed;
return NS_OK;
}
NS_IMETHODIMP DocumentViewerImpl::CutSelection()
{
// preventDefault's value is ignored because cut from the document has no
// default behaviour.
PRBool preventDefault;
return FireClipboardEvent(NS_CUT, &preventDefault);
}
NS_IMETHODIMP DocumentViewerImpl::GetCutable(PRBool *aCutable)
{
NS_ENSURE_ARG_POINTER(aCutable);
*aCutable = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP DocumentViewerImpl::Paste()
{
// preventDefault's value is ignored because paste into the document has no
// default behaviour.
PRBool preventDefault;
return FireClipboardEvent(NS_PASTE, &preventDefault);
}
NS_IMETHODIMP DocumentViewerImpl::GetPasteable(PRBool *aPasteable)
{
NS_ENSURE_ARG_POINTER(aPasteable);
*aPasteable = PR_FALSE;
*aCopyable = nsCopySupport::CanCopy(mDocument);
return NS_OK;
}
/* AString getContents (in string mimeType, in boolean selectionOnly); */
NS_IMETHODIMP DocumentViewerImpl::GetContents(const char *mimeType, PRBool selectionOnly, nsAString& aOutValue)
{
aOutValue.Truncate();
NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED);
return mPresShell->DoGetContents(nsDependentCString(mimeType), 0, selectionOnly, aOutValue);
NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
// Now we have the selection. Make sure it's nonzero:
nsCOMPtr<nsISelection> sel;
if (selectionOnly) {
nsCopySupport::GetSelectionForCopy(mDocument, getter_AddRefs(sel));
NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE);
PRBool isCollapsed;
sel->GetIsCollapsed(&isCollapsed);
if (isCollapsed)
return NS_OK;
}
// call the copy code
return nsCopySupport::GetContents(nsDependentCString(mimeType), 0, sel,
mDocument, aOutValue);
}
/* readonly attribute boolean canGetContents; */
NS_IMETHODIMP DocumentViewerImpl::GetCanGetContents(PRBool *aCanGetContents)
{
return GetCopyable(aCanGetContents);
NS_ENSURE_ARG_POINTER(aCanGetContents);
*aCanGetContents = PR_FALSE;
NS_ENSURE_STATE(mDocument);
*aCanGetContents = nsCopySupport::CanCopy(mDocument);
return NS_OK;
}
#ifdef XP_MAC

View File

@ -127,8 +127,8 @@ typedef struct CapturingContentInfo {
} CapturingContentInfo;
#define NS_IPRESSHELL_IID \
{ 0xe5e070ce, 0xbc17, 0x4b5f, \
{ 0xb2, 0x21, 0xbf, 0xc3, 0xe1, 0x68, 0xbe, 0x9b } }
{ 0x0e170e5f, 0xf6d4, 0x44c5, \
{ 0xbc, 0x2c, 0x44, 0x94, 0x20, 0x7e, 0xcc, 0x30 } }
// Constants for ScrollContentIntoView() function
#define NS_PRESSHELL_SCROLL_TOP 0
@ -578,27 +578,11 @@ public:
*/
NS_IMETHOD NotifyDestroyingFrame(nsIFrame* aFrame) = 0;
/**
* Notify the Clipboard that we have something to copy.
*/
NS_IMETHOD DoCopy() = 0;
/**
* Get the selection of the focussed element (either the page selection,
* or the selection for a text field).
*/
NS_IMETHOD GetSelectionForCopy(nsISelection** outSelection) = 0;
/**
* Get link location.
*/
NS_IMETHOD GetLinkLocation(nsIDOMNode* aNode, nsAString& aLocation) = 0;
/**
* Get the doc or the selection as text or html.
*/
NS_IMETHOD DoGetContents(const nsACString& aMimeType, PRUint32 aFlags, PRBool aSelectionOnly, nsAString& outValue) = 0;
/**
* Get the caret, if it exists. AddRefs it.
*/

View File

@ -98,6 +98,10 @@
#include "nsSVGOuterSVGFrame.h"
#endif
#ifdef MOZ_XUL
#include "nsXULPopupManager.h"
#endif
using namespace mozilla::layers;
/**
@ -779,6 +783,28 @@ nsLayoutUtils::GetEventCoordinatesRelativeTo(const nsEvent* aEvent, nsIFrame* aF
return widgetToView - aFrame->GetOffsetTo(rootFrame);
}
nsIFrame*
nsLayoutUtils::GetPopupFrameForEventCoordinates(const nsEvent* aEvent)
{
#ifdef MOZ_XUL
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (!pm) {
return nsnull;
}
nsTArray<nsIFrame*> popups = pm->GetVisiblePopups();
PRUint32 i;
// Search from top to bottom
for (i = 0; i < popups.Length(); i++) {
nsIFrame* popup = popups[i];
if (popup->GetOverflowRect().Contains(
GetEventCoordinatesRelativeTo(aEvent, popup))) {
return popup;
}
}
#endif
return nsnull;
}
gfxMatrix
nsLayoutUtils::ChangeMatrixBasis(const gfxPoint &aOrigin,
const gfxMatrix &aMatrix)

View File

@ -388,6 +388,14 @@ public:
nsIFrame* aFrame);
/**
* Get the popup frame of a given native mouse event.
* @param aEvent the event.
* @return Null, if there is no popup frame at the point, otherwise,
* returns top-most popup frame at the point.
*/
static nsIFrame* GetPopupFrameForEventCoordinates(const nsEvent* aEvent);
/**
* Translate from widget coordinates to the view's coordinates
* @param aPresContext the PresContext for the view
* @param aWidget the widget

View File

@ -738,12 +738,8 @@ public:
NS_IMETHOD SetIgnoreFrameDestruction(PRBool aIgnore);
NS_IMETHOD NotifyDestroyingFrame(nsIFrame* aFrame);
NS_IMETHOD DoCopy();
NS_IMETHOD GetSelectionForCopy(nsISelection** outSelection);
NS_IMETHOD GetLinkLocation(nsIDOMNode* aNode, nsAString& aLocationString);
NS_IMETHOD DoGetContents(const nsACString& aMimeType, PRUint32 aFlags, PRBool aSelectionOnly, nsAString& outValue);
NS_IMETHOD CaptureHistoryState(nsILayoutHistoryState** aLayoutHistoryState, PRBool aLeavingPage);
@ -4298,57 +4294,6 @@ NS_IMETHODIMP PresShell::GetLinkLocation(nsIDOMNode* aNode, nsAString& aLocation
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
PresShell::GetSelectionForCopy(nsISelection** outSelection)
{
nsresult rv = NS_OK;
*outSelection = nsnull;
if (!mDocument) return NS_ERROR_FAILURE;
nsCOMPtr<nsIContent> content;
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(mDocument->GetWindow());
nsCOMPtr<nsIDOMElement> focusedElement;
fm->GetFocusedElementForWindow(window, PR_FALSE, nsnull, getter_AddRefs(focusedElement));
content = do_QueryInterface(focusedElement);
}
nsCOMPtr<nsISelection> sel;
if (content)
{
//check to see if we need to get selection from frame
//optimization that MAY need to be expanded as more things implement their own "selection"
nsCOMPtr<nsIDOMNSHTMLInputElement> htmlInputElement(do_QueryInterface(content));
nsCOMPtr<nsIDOMNSHTMLTextAreaElement> htmlTextAreaElement(do_QueryInterface(content));
if (htmlInputElement || htmlTextAreaElement)
{
nsIFrame *htmlInputFrame = content->GetPrimaryFrame();
if (!htmlInputFrame) return NS_ERROR_FAILURE;
nsCOMPtr<nsISelectionController> selCon;
rv = htmlInputFrame->
GetSelectionController(mPresContext,getter_AddRefs(selCon));
if (NS_FAILED(rv)) return rv;
if (!selCon) return NS_ERROR_FAILURE;
rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
getter_AddRefs(sel));
}
}
if (!sel) {
sel = mSelection->GetSelection(nsISelectionController::SELECTION_NORMAL);
rv = NS_OK;
}
*outSelection = sel;
NS_IF_ADDREF(*outSelection);
return rv;
}
NS_IMETHODIMP_(void)
PresShell::DispatchSynthMouseMove(nsGUIEvent *aEvent,
PRBool aFlushOnHoverChange)
@ -4405,67 +4350,6 @@ PresShell::ClearMouseCapture(nsIView* aView)
gCaptureInfo.mAllowed = PR_FALSE;
}
NS_IMETHODIMP
PresShell::DoGetContents(const nsACString& aMimeType, PRUint32 aFlags, PRBool aSelectionOnly, nsAString& aOutValue)
{
aOutValue.Truncate();
if (!mDocument) return NS_ERROR_FAILURE;
nsresult rv;
nsCOMPtr<nsISelection> sel;
// Now we have the selection. Make sure it's nonzero:
if (aSelectionOnly)
{
rv = GetSelectionForCopy(getter_AddRefs(sel));
if (NS_FAILED(rv)) return rv;
if (!sel) return NS_ERROR_FAILURE;
PRBool isCollapsed;
sel->GetIsCollapsed(&isCollapsed);
if (isCollapsed)
return NS_OK;
}
// call the copy code
return nsCopySupport::GetContents(aMimeType, aFlags, sel,
mDocument, aOutValue);
}
NS_IMETHODIMP
PresShell::DoCopy()
{
if (!mDocument) return NS_ERROR_FAILURE;
nsCOMPtr<nsISelection> sel;
nsresult rv = GetSelectionForCopy(getter_AddRefs(sel));
if (NS_FAILED(rv))
return rv;
if (!sel)
return NS_ERROR_FAILURE;
// Now we have the selection. Make sure it's nonzero:
PRBool isCollapsed;
sel->GetIsCollapsed(&isCollapsed);
if (isCollapsed)
return NS_OK;
// call the copy code
rv = nsCopySupport::HTMLCopy(sel, mDocument, nsIClipboard::kGlobalClipboard);
if (NS_FAILED(rv))
return rv;
// Now that we have copied, update the Paste menu item
nsPIDOMWindow *domWindow = mDocument->GetWindow();
if (domWindow)
{
domWindow->UpdateCommands(NS_LITERAL_STRING("clipboard"));
}
return NS_OK;
}
NS_IMETHODIMP
PresShell::CaptureHistoryState(nsILayoutHistoryState** aState, PRBool aLeavingPage)
{
@ -6156,27 +6040,16 @@ PresShell::HandleEvent(nsIView *aView,
// list.
if (framePresContext == rootPresContext &&
frame == FrameManager()->GetRootFrame()) {
#ifdef MOZ_XUL
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) {
nsTArray<nsIFrame*> popups = pm->GetVisiblePopups();
PRUint32 i;
// Search from top to bottom
nsIDocument* doc = framePresContext->GetPresShell()->GetDocument();
for (i = 0; i < popups.Length(); i++) {
nsIFrame* popup = popups[i];
if (popup->GetOverflowRect().Contains(
nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, popup)) &&
!nsContentUtils::ContentIsCrossDocDescendantOf(
doc, popup->GetContent())) {
// The event should target the popup
frame = popup;
break;
}
}
nsIFrame* popupFrame =
nsLayoutUtils::GetPopupFrameForEventCoordinates(aEvent);
// If the popupFrame is an ancestor of the 'frame', the frame should
// handle the event, otherwise, the popup should handle it.
if (popupFrame &&
!nsContentUtils::ContentIsCrossDocDescendantOf(
framePresContext->GetPresShell()->GetDocument(),
popupFrame->GetContent())) {
frame = popupFrame;
}
#endif
}
PRBool captureRetarget = PR_FALSE;

View File

@ -593,6 +593,12 @@
#define NS_STYLE_POINTER_EVENTS_ALL 8
#define NS_STYLE_POINTER_EVENTS_AUTO 9
// See nsStyleDisplay
#define NS_STYLE_RESIZE_NONE 0
#define NS_STYLE_RESIZE_BOTH 1
#define NS_STYLE_RESIZE_HORIZONTAL 2
#define NS_STYLE_RESIZE_VERTICAL 3
// See nsStyleText
#define NS_STYLE_TEXT_ALIGN_DEFAULT 0
#define NS_STYLE_TEXT_ALIGN_LEFT 1

View File

@ -57,6 +57,7 @@ _TEST_FILES = test_bug231389.html \
test_bug476308.html \
test_bug477531.html \
test_bug477700.html \
test_textarea_resize.html \
test_bug478219.xhtml \
test_bug542914.html \
bug477700_subframe.html \

View File

@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Bug 477700</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div id="content" style="display: none">
</div>
<textarea id="textarea" style="-moz-appearance: none; border: 0;">Text</textarea>
<pre id="test">
<script type="application/javascript">
/** Test for textbox resizing **/
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() SimpleTest.executeSoon(doTheTest));
// First, test the default value which is 'both', then test explicitly
// setting each possible value.
var currentResize = "both";
var resizeTypes = [ "horizontal", "vertical", "none", "inherit", "both" ];
function doTheTest() {
var textarea = $("textarea");
var rect = textarea.getBoundingClientRect();
// assume that the resizer is in the lower right corner
synthesizeMouse(textarea, rect.width - 8, rect.height - 8, { type:"mousedown" });
synthesizeMouse(textarea, rect.width + 40, rect.height + 40, { type:"mousemove" });
var newrect = textarea.getBoundingClientRect();
var hchange = (currentResize == "both" || currentResize == "horizontal");
var vchange = (currentResize == "both" || currentResize == "vertical");
is(Math.round(newrect.width), Math.round(rect.width + (hchange ? 48 : 0)),
currentResize + " width has increased");
is(Math.round(newrect.height), Math.round(rect.height + (vchange ? 48 : 0)),
currentResize + " height has increased");
synthesizeMouse(textarea, rect.width - 20, rect.height - 20, { type:"mousemove" });
newrect = textarea.getBoundingClientRect();
is(Math.round(newrect.width), Math.round(rect.width - (hchange ? 12 : 0)),
currentResize + " width has decreased");
is(Math.round(newrect.height), Math.round(rect.height - (vchange ? 12 : 0)),
currentResize + " height has decreased");
synthesizeMouse(textarea, rect.width - 220, rect.height - 220, { type:"mousemove" });
newrect = textarea.getBoundingClientRect();
ok(hchange ? newrect.width >= 15 : Math.round(newrect.width) == Math.round(rect.width),
currentResize + " width decreased below minimum");
ok(vchange ? newrect.height >= 15 : Math.round(newrect.height) == Math.round(rect.height),
currentResize + " height decreased below minimum");
synthesizeMouse(textarea, rect.width - 8, rect.height - 8, { type:"mouseup" });
currentResize = resizeTypes.shift();
if (currentResize) {
textarea.style.width = "auto";
textarea.style.height = "auto";
textarea.style.MozResize = currentResize;
SimpleTest.executeSoon(doTheTest);
}
else {
SimpleTest.finish();
}
}
</script>
</pre>
</body>
</html>

View File

@ -1815,7 +1815,7 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
{
nsresult rv = mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
NS_ENSURE_SUCCESS(rv, rv);
if (aBuilder->GetIgnoreScrollFrame() == mOuter) {
// Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
// The scrolled frame shouldn't have its own background/border, so we
@ -1828,13 +1828,16 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
// in the border-background layer, on top of our own background and
// borders and underneath borders and backgrounds of later elements
// in the tree.
nsIFrame* kid = mOuter->GetFirstChild(nsnull);
while (kid) {
PRBool hasResizer = HasResizer();
for (nsIFrame* kid = mOuter->GetFirstChild(nsnull); kid; kid = kid->GetNextSibling()) {
if (kid != mScrolledFrame) {
if (kid == mScrollCornerBox && hasResizer) {
// skip the resizer as this will be drawn later on top of the scrolled content
continue;
}
rv = mOuter->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
NS_ENSURE_SUCCESS(rv, rv);
}
kid = kid->GetNextSibling();
}
// Overflow clipping can never clip frames outside our subtree, so there
@ -1860,6 +1863,15 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
rv = mOuter->OverflowClip(aBuilder, set, aLists, clip, PR_TRUE, mIsRoot);
NS_ENSURE_SUCCESS(rv, rv);
// Place the resizer in the display list above the overflow clip. This
// ensures that the resizer appears above the content and the mouse can
// still target the resizer even when scrollbars are hidden.
if (hasResizer && mScrollCornerBox) {
rv = mOuter->BuildDisplayListForChild(aBuilder, mScrollCornerBox, aDirtyRect, aLists,
nsIFrame::DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
@ -2176,6 +2188,19 @@ nsGfxScrollFrameInner::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
}
}
// Check if the frame is resizable.
nsIFrame* resizableFrame = mOuter;
if (parent) {
// For textarea, mOuter is the frame for the anonymous div element,
// so get the resizability from the parent textarea instead.
nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement(do_QueryInterface(parent->GetContent()));
if (textAreaElement) {
resizableFrame = parent;
}
}
PRBool isResizable = resizableFrame->GetStyleDisplay()->mResize != NS_STYLE_RESIZE_NONE;
nsIScrollableFrame *scrollable = do_QueryFrame(mOuter);
// At this stage in frame construction, the document element and/or
@ -2195,7 +2220,7 @@ nsGfxScrollFrameInner::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
ScrollbarStyles styles = scrollable->GetScrollbarStyles();
PRBool canHaveHorizontal = styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN;
PRBool canHaveVertical = styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN;
if (!canHaveHorizontal && !canHaveVertical) {
if (!canHaveHorizontal && !canHaveVertical && !isResizable) {
// Nothing to do.
return NS_OK;
}
@ -2240,7 +2265,42 @@ nsGfxScrollFrameInner::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
return NS_ERROR_OUT_OF_MEMORY;
}
if (canHaveHorizontal && canHaveVertical) {
if (isResizable) {
nsCOMPtr<nsINodeInfo> nodeInfo;
nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::resizer, nsnull,
kNameSpaceID_XUL);
NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
rv = NS_NewXULElement(getter_AddRefs(mScrollCornerContent), nodeInfo);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString dir;
switch (resizableFrame->GetStyleDisplay()->mResize) {
case NS_STYLE_RESIZE_HORIZONTAL:
if (IsScrollbarOnRight()) {
dir.AssignLiteral("right");
}
else {
dir.AssignLiteral("left");
}
break;
case NS_STYLE_RESIZE_VERTICAL:
dir.AssignLiteral("bottom");
break;
case NS_STYLE_RESIZE_BOTH:
dir.AssignLiteral("bottomend");
break;
default:
NS_WARNING("only resizable types should have resizers");
}
mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, PR_FALSE);
mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::element,
NS_LITERAL_STRING("_parent"), PR_FALSE);
if (!aElements.AppendElement(mScrollCornerContent))
return NS_ERROR_OUT_OF_MEMORY;
}
else if (canHaveHorizontal && canHaveVertical) {
nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nsnull,
kNameSpaceID_XUL);
rv = NS_NewElement(getter_AddRefs(mScrollCornerContent),
@ -2972,26 +3032,36 @@ static void LayoutAndInvalidate(nsBoxLayoutState& aState,
}
}
static void AdjustScrollbarRect(nsIFrame* aFrame, nsPresContext* aPresContext,
nsRect& aRect, PRBool aVertical)
void
nsGfxScrollFrameInner::AdjustScrollbarRectForResizer(
nsIFrame* aFrame, nsPresContext* aPresContext,
nsRect& aRect, PRBool aHasResizer, PRBool aVertical)
{
if ((aVertical ? aRect.width : aRect.height) == 0)
return;
nsPoint offsetToView;
nsPoint offsetToWidget;
nsIWidget* widget =
aFrame->GetClosestView(&offsetToView)->GetNearestWidget(&offsetToWidget);
nsPoint offset = offsetToView + offsetToWidget;
nsIntRect widgetRect;
if (!widget || !widget->ShowsResizeIndicator(&widgetRect))
return;
// if a content resizer is present, use its size. Otherwise, check if the
// widget has a resizer.
nsRect resizerRect;
if (aHasResizer && mScrollCornerBox) {
resizerRect = mScrollCornerBox->GetRect();
}
else {
nsPoint offsetToView;
nsPoint offsetToWidget;
nsIWidget* widget =
aFrame->GetClosestView(&offsetToView)->GetNearestWidget(&offsetToWidget);
nsPoint offset = offsetToView + offsetToWidget;
nsIntRect widgetRect;
if (!widget || !widget->ShowsResizeIndicator(&widgetRect))
return;
nsRect resizerRect =
nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
aPresContext->DevPixelsToAppUnits(widgetRect.width),
aPresContext->DevPixelsToAppUnits(widgetRect.height));
nsRect resizerRect =
nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
aPresContext->DevPixelsToAppUnits(widgetRect.width),
aPresContext->DevPixelsToAppUnits(widgetRect.height));
}
if (!resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1)))
return;
@ -3010,18 +3080,62 @@ nsGfxScrollFrameInner::LayoutScrollbars(nsBoxLayoutState& aState,
NS_ASSERTION(!mSupppressScrollbarUpdate,
"This should have been suppressed");
PRBool hasResizer = HasResizer();
PRBool scrollbarOnLeft = !IsScrollbarOnRight();
// place the scrollcorner
if (mScrollCornerBox) {
NS_PRECONDITION(mScrollCornerBox->IsBoxFrame(), "Must be a box frame!");
// if a resizer is present, get its size
nsSize resizerSize;
if (HasResizer()) {
// just assume a default size of 15 pixels
nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15);
resizerSize.width =
mVScrollbarBox ? mVScrollbarBox->GetMinSize(aState).width : defaultSize;
resizerSize.height =
mHScrollbarBox ? mHScrollbarBox->GetMinSize(aState).height : defaultSize;
}
else {
resizerSize = nsSize(0, 0);
}
nsRect r(0, 0, 0, 0);
if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) {
// scrollbar (if any) on left
r.x = aContentArea.x;
r.width = PR_MAX(resizerSize.width, mScrollPort.x - aContentArea.x);
NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
} else {
// scrollbar (if any) on right
r.width = PR_MAX(resizerSize.width, aContentArea.XMost() - mScrollPort.XMost());
r.x = aContentArea.XMost() - r.width;
NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
}
if (aContentArea.y != mScrollPort.y) {
NS_ERROR("top scrollbars not supported");
} else {
// scrollbar (if any) on bottom
r.height = PR_MAX(resizerSize.height, aContentArea.YMost() - mScrollPort.YMost());
r.y = aContentArea.YMost() - r.height;
NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
}
LayoutAndInvalidate(aState, mScrollCornerBox, r);
}
nsPresContext* presContext = mScrolledFrame->PresContext();
if (mVScrollbarBox) {
NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!");
nsRect vRect(mScrollPort);
vRect.width = aContentArea.width - mScrollPort.width;
vRect.x = IsScrollbarOnRight() ? mScrollPort.XMost() : aContentArea.x;
vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.XMost();
#ifdef DEBUG
nsMargin margin;
mVScrollbarBox->GetMargin(margin);
NS_ASSERTION(margin == nsMargin(0,0,0,0), "Scrollbar margin not supported");
#endif
AdjustScrollbarRect(mOuter, presContext, vRect, PR_TRUE);
AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, PR_TRUE);
LayoutAndInvalidate(aState, mVScrollbarBox, vRect);
}
@ -3035,39 +3149,10 @@ nsGfxScrollFrameInner::LayoutScrollbars(nsBoxLayoutState& aState,
mHScrollbarBox->GetMargin(margin);
NS_ASSERTION(margin == nsMargin(0,0,0,0), "Scrollbar margin not supported");
#endif
AdjustScrollbarRect(mOuter, presContext, hRect, PR_FALSE);
AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, PR_FALSE);
LayoutAndInvalidate(aState, mHScrollbarBox, hRect);
}
// place the scrollcorner
if (mScrollCornerBox) {
NS_PRECONDITION(mScrollCornerBox->IsBoxFrame(), "Must be a box frame!");
nsRect r(0, 0, 0, 0);
if (aContentArea.x != mScrollPort.x) {
// scrollbar (if any) on left
r.x = aContentArea.x;
r.width = mScrollPort.x - aContentArea.x;
NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
} else {
// scrollbar (if any) on right
r.x = mScrollPort.XMost();
r.width = aContentArea.XMost() - mScrollPort.XMost();
NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
}
if (aContentArea.y != mScrollPort.y) {
// scrollbar (if any) on top
r.y = aContentArea.y;
r.height = mScrollPort.y - aContentArea.y;
NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
} else {
// scrollbar (if any) on bottom
r.y = mScrollPort.YMost();
r.height = aContentArea.YMost() - mScrollPort.YMost();
NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
}
LayoutAndInvalidate(aState, mScrollCornerBox, r);
}
// may need to update fixed position children of the viewport,
// if the client area changed size because of an incremental
// reflow of a descendant. (If the outer frame is dirty, the fixed

View File

@ -215,6 +215,15 @@ public:
nsMargin GetDesiredScrollbarSizes(nsBoxLayoutState* aState);
PRBool IsLTR() const;
PRBool IsScrollbarOnRight() const;
// adjust the scrollbar rectangle aRect to account for any visible resizer.
// aHasResizer specifies if there is a content resizer, however this method
// will also check if a widget resizer is present as well.
void AdjustScrollbarRectForResizer(nsIFrame* aFrame, nsPresContext* aPresContext,
nsRect& aRect, PRBool aHasResizer, PRBool aVertical);
// returns true if a resizer should be visible
PRBool HasResizer() {
return mScrollCornerContent && mScrollCornerContent->Tag() == nsGkAtoms::resizer;
}
void LayoutScrollbars(nsBoxLayoutState& aState,
const nsRect& aContentArea,
const nsRect& aOldScrollArea);

View File

@ -1,6 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green"></textarea>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green; -moz-resize: none;"></textarea>
</body>
</html>

View File

@ -1,6 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green; max-width: 200px;"></textarea>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green; max-width: 200px; -moz-resize: none;"></textarea>
</body>
</html>

View File

@ -1,6 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green; max-height: 200px;"></textarea>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green; max-height: 200px; -moz-resize: none;"></textarea>
</body>
</html>

View File

@ -8,6 +8,6 @@
<script type="text/javascript" src="platform.js"/>
<html:textarea rows="10"/>
<html:textarea rows="10" style="-moz-resize: none;"/>
</window>

View File

@ -3,6 +3,6 @@
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<html:textarea xmlns:html="http://www.w3.org/1999/xhtml" style="width: 200px; height: 200px; margin: 0;"/>
<html:textarea xmlns:html="http://www.w3.org/1999/xhtml" style="width: 200px; height: 200px; margin: 0; -moz-resize: none;"/>
</window>

View File

@ -0,0 +1,22 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/licenses/publicdomain/
-->
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<title>Testcase for clipPath with animateTransform</title>
<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=553053 -->
<defs>
<clipPath id="clip">
<rect width="100%" height="100%" fill="lime"/>
<animateTransform attributeName="transform" type="scale" values="1"/>
</clipPath>
</defs>
<rect width="100%" height="100%" fill="red"/>
<rect width="100%" height="100%" fill="lime" clip-path="url(#clip)"/>
</svg>

After

Width:  |  Height:  |  Size: 626 B

View File

@ -24,6 +24,7 @@ include svg-integration/reftest.list
== clipPath-basic-01.svg pass.svg
== clipPath-basic-02.svg pass.svg
== clipPath-basic-03.svg pass.svg
== clipPath-basic-04.svg pass.svg
== clipPath-winding-01.svg pass.svg
== clip-surface-clone-01.svg clip-surface-clone-01-ref.svg
== conditions-01.svg pass.svg

View File

@ -127,6 +127,7 @@ textarea {
letter-spacing: normal;
vertical-align: text-bottom;
cursor: text;
-moz-resize: both;
-moz-binding: url("chrome://global/content/platformHTMLBindings.xml#textAreas");
-moz-appearance: textfield-multiline;
text-indent: 0;

View File

@ -6019,6 +6019,9 @@ CSSParserImpl::ParseSingleValueProperty(nsCSSValue& aValue,
nsCSSProps::kPointerEventsKTable);
case eCSSProperty_position:
return ParseVariant(aValue, VARIANT_HK, nsCSSProps::kPositionKTable);
case eCSSProperty_resize:
return ParseVariant(aValue, VARIANT_HK,
nsCSSProps::kResizeKTable);
case eCSSProperty_richness:
return ParseVariant(aValue, VARIANT_HN, nsnull);
#ifdef MOZ_MATHML

View File

@ -2185,6 +2185,17 @@ CSS_PROP_QUOTES(
nsnull,
CSS_PROP_NO_OFFSET,
eStyleAnimType_None)
CSS_PROP_DISPLAY(
-moz-resize,
resize,
MozResize,
0,
Display,
mResize,
eCSSType_Value,
kResizeKTable,
CSS_PROP_NO_OFFSET,
eStyleAnimType_None)
CSS_PROP_BACKENDONLY(
richness,
richness,

View File

@ -1106,6 +1106,14 @@ const PRInt32 nsCSSProps::kRadialGradientSizeKTable[] = {
eCSSKeyword_UNKNOWN,-1
};
const PRInt32 nsCSSProps::kResizeKTable[] = {
eCSSKeyword_none, NS_STYLE_RESIZE_NONE,
eCSSKeyword_both, NS_STYLE_RESIZE_BOTH,
eCSSKeyword_horizontal, NS_STYLE_RESIZE_HORIZONTAL,
eCSSKeyword_vertical, NS_STYLE_RESIZE_VERTICAL,
eCSSKeyword_UNKNOWN,-1
};
const PRInt32 nsCSSProps::kSpeakKTable[] = {
eCSSKeyword_none, NS_STYLE_SPEAK_NONE,
eCSSKeyword_normal, NS_STYLE_SPEAK_NORMAL,

View File

@ -306,6 +306,7 @@ public:
static const PRInt32 kPositionKTable[];
static const PRInt32 kRadialGradientShapeKTable[];
static const PRInt32 kRadialGradientSizeKTable[];
static const PRInt32 kResizeKTable[];
static const PRInt32 kSpeakKTable[];
static const PRInt32 kSpeakHeaderKTable[];
static const PRInt32 kSpeakNumeralKTable[];

View File

@ -372,6 +372,7 @@ struct nsCSSDisplay : public nsCSSStruct {
nsCSSRect mClip;
nsCSSValue mOverflowX;
nsCSSValue mOverflowY;
nsCSSValue mResize;
nsCSSValue mPointerEvents;
nsCSSValue mVisibility;
nsCSSValue mOpacity;

View File

@ -3074,6 +3074,19 @@ nsComputedDOMStyle::GetOverflowY(nsIDOMCSSValue** aValue)
return CallQueryInterface(val, aValue);
}
nsresult
nsComputedDOMStyle::GetResize(nsIDOMCSSValue** aValue)
{
nsROCSSPrimitiveValue *val = GetROCSSPrimitiveValue();
NS_ENSURE_TRUE(val, NS_ERROR_OUT_OF_MEMORY);
val->SetIdent(nsCSSProps::ValueToKeywordEnum(GetStyleDisplay()->mResize,
nsCSSProps::kResizeKTable));
return CallQueryInterface(val, aValue);
}
nsresult
nsComputedDOMStyle::GetPageBreakAfter(nsIDOMCSSValue** aValue)
{
@ -4532,6 +4545,7 @@ nsComputedDOMStyle::GetQueryablePropertyMap(PRUint32* aLength)
COMPUTED_STYLE_MAP_ENTRY_LAYOUT(_moz_outline_radius_bottomRight,OutlineRadiusBottomRight),
COMPUTED_STYLE_MAP_ENTRY_LAYOUT(_moz_outline_radius_topLeft, OutlineRadiusTopLeft),
COMPUTED_STYLE_MAP_ENTRY_LAYOUT(_moz_outline_radius_topRight, OutlineRadiusTopRight),
COMPUTED_STYLE_MAP_ENTRY(resize, Resize),
COMPUTED_STYLE_MAP_ENTRY(stack_sizing, StackSizing),
COMPUTED_STYLE_MAP_ENTRY(_moz_tab_size, MozTabSize),
COMPUTED_STYLE_MAP_ENTRY_LAYOUT(_moz_transform, MozTransform),

View File

@ -308,6 +308,7 @@ private:
nsresult GetOverflow(nsIDOMCSSValue** aValue);
nsresult GetOverflowX(nsIDOMCSSValue** aValue);
nsresult GetOverflowY(nsIDOMCSSValue** aValue);
nsresult GetResize(nsIDOMCSSValue** aValue);
nsresult GetPageBreakAfter(nsIDOMCSSValue** aValue);
nsresult GetPageBreakBefore(nsIDOMCSSValue** aValue);
nsresult GetMozTransform(nsIDOMCSSValue** aValue);

View File

@ -3984,6 +3984,10 @@ nsRuleNode::ComputeDisplayData(void* aStartStruct,
display->mOverflowY = NS_STYLE_OVERFLOW_AUTO;
}
SetDiscrete(displayData.mResize, display->mResize, canStoreInRuleTree,
SETDSC_ENUMERATED, parentDisplay->mResize,
NS_STYLE_RESIZE_NONE, 0, 0, 0, 0);
// clip property: length, auto, inherit
if (eCSSUnit_Inherit == displayData.mClip.mTop.GetUnit()) { // if one is inherit, they all are
canStoreInRuleTree = PR_FALSE;

View File

@ -1806,6 +1806,7 @@ nsStyleDisplay::nsStyleDisplay()
mBreakAfter = PR_FALSE;
mOverflowX = NS_STYLE_OVERFLOW_VISIBLE;
mOverflowY = NS_STYLE_OVERFLOW_VISIBLE;
mResize = NS_STYLE_RESIZE_NONE;
mClipFlags = NS_STYLE_CLIP_AUTO;
mClip.SetRect(0,0,0,0);
mOpacity = 1.0f;
@ -1841,6 +1842,7 @@ nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource)
mBreakAfter = aSource.mBreakAfter;
mOverflowX = aSource.mOverflowX;
mOverflowY = aSource.mOverflowY;
mResize = aSource.mResize;
mClipFlags = aSource.mClipFlags;
mClip = aSource.mClip;
mOpacity = aSource.mOpacity;
@ -1864,7 +1866,8 @@ nsChangeHint nsStyleDisplay::CalcDifference(const nsStyleDisplay& aOther) const
|| mDisplay != aOther.mDisplay
|| (mFloats == NS_STYLE_FLOAT_NONE) != (aOther.mFloats == NS_STYLE_FLOAT_NONE)
|| mOverflowX != aOther.mOverflowX
|| mOverflowY != aOther.mOverflowY)
|| mOverflowY != aOther.mOverflowY
|| mResize != aOther.mResize)
NS_UpdateHint(hint, nsChangeHint_ReconstructFrame);
if (mFloats != aOther.mFloats) {

View File

@ -1314,6 +1314,7 @@ struct nsStyleDisplay {
PRPackedBool mBreakAfter; // [reset]
PRUint8 mOverflowX; // [reset] see nsStyleConsts.h
PRUint8 mOverflowY; // [reset] see nsStyleConsts.h
PRUint8 mResize; // [reset] see nsStyleConsts.h
PRUint8 mClipFlags; // [reset] see nsStyleConsts.h
PRPackedBool mTransformPresent; // [reset] Whether there is a -moz-transform.
nsStyleTransformMatrix mTransform; // [reset] The stored transform matrix

View File

@ -535,6 +535,15 @@ var gCSSProperties = {
other_values: [ "1px", "3em" ],
invalid_values: []
},
"-moz-resize": {
domProp: "MozResize",
inherited: false,
type: CSS_TYPE_LONGHAND,
prerequisites: { "display": "block", "overflow": "auto" },
initial_values: [ "none" ],
other_values: [ "both", "horizontal", "vertical" ],
invalid_values: []
},
"-moz-tab-size": {
domProp: "MozTabSize",
inherited: true,

View File

@ -0,0 +1,2 @@
== textbox-multiline-noresize.xul textbox-multiline-ref.xul
!= textbox-multiline-resize.xul textbox-multiline-ref.xul

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<textbox style="margin: 0;" multiline="true" width="100" height="100"/>
</window>

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<vbox style="-moz-appearance: textfield;" width="100" height="100"/>
</window>

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<textbox style="margin: 0;" resizable="true" multiline="true" width="100" height="100"/>
</window>

View File

@ -179,23 +179,17 @@ nsResizerFrame::HandleEvent(nsPresContext* aPresContext,
nsIntPoint screenPoint(aEvent->refPoint + aEvent->widget->WidgetToScreenOffset());
nsIntPoint mouseMove(screenPoint - mMouseDownPoint);
// what direction should we go in? For content resizing, always use
// 'bottomend'. For other windows, check the dir attribute.
Direction direction;
// Determine which direction to resize by checking the dir attribute.
// For windows and menus, ensure that it can be resized in that direction.
Direction direction = GetDirection();
if (window || menuPopupFrame) {
direction = GetDirection();
if (menuPopupFrame) {
menuPopupFrame->CanAdjustEdges(
(direction.mHorizontal == -1) ? NS_SIDE_LEFT : NS_SIDE_RIGHT,
(direction.mVertical == -1) ? NS_SIDE_TOP : NS_SIDE_BOTTOM, mouseMove);
}
}
else if (contentToResize) {
direction.mHorizontal =
GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL ? -1 : 1;
direction.mVertical = 1;
}
else {
else if (!contentToResize) {
break; // don't do anything if there's nothing to resize
}
@ -233,9 +227,16 @@ nsResizerFrame::HandleEvent(nsPresContext* aPresContext,
}
if (contentToResize) {
nsIntRect cssRect =
rect.ToAppUnits(aPresContext->AppUnitsPerDevPixel())
.ToInsidePixels(nsPresContext::AppUnitsPerCSSPixel());
// convert the rectangle into css pixels. When changing the size in a
// direction, don't allow the new size to be less that the resizer's
// size. This ensures that content isn't resized too small as to make
// the resizer invisible.
nsRect appUnitsRect = rect.ToAppUnits(aPresContext->AppUnitsPerDevPixel());
if (appUnitsRect.width < mRect.width && mouseMove.x)
appUnitsRect.width = mRect.width;
if (appUnitsRect.height < mRect.height && mouseMove.y)
appUnitsRect.height = mRect.height;
nsIntRect cssRect = appUnitsRect.ToInsidePixels(nsPresContext::AppUnitsPerCSSPixel());
nsAutoString widthstr, heightstr;
widthstr.AppendInt(cssRect.width);
@ -353,7 +354,9 @@ nsResizerFrame::GetContentToResize(nsIPresShell* aPresShell, nsIBaseWindow** aWi
}
if (elementid.EqualsLiteral("_parent")) {
return mContent->GetParent();
// return the parent, but skip over native anonymous content
nsIContent* parent = mContent->GetParent();
return parent ? parent->FindFirstNonNativeAnonymous() : nsnull;
}
nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(aPresShell->GetDocument());

View File

@ -151,26 +151,26 @@ window.opener.SimpleTest.waitForFocus(doTest, window);
<resizer id="html" dir="bottomend" element="_parent"/>
</html:div>
<hbox id="anchor" align="start" style="margin-left: 100px;">
<hbox id="inside-container">
<hbox id="inside-container" align="start">
<hbox minwidth="45" minheight="41"/>
<resizer id="inside" dir="bottomend" element="_parent"/>
</hbox>
<hbox id="inside-large-container" width="70" height="70">
<hbox id="inside-large-container" width="70" height="70" align="start">
<resizer id="inside-large" dir="bottomend" element="_parent"/>
</hbox>
<hbox id="inside-with-border-container" style="border: 5px solid red; padding: 2px; margin: 2px;">
<hbox id="inside-with-border-container" style="border: 5px solid red; padding: 2px; margin: 2px;" align="start">
<hbox minwidth="35" minheight="30"/>
<resizer id="inside-with-border" dir="bottomend" element="_parent"/>
</hbox>
</hbox>
<panel id="inside-popup-container" onpopupshown="popupShown(event)" onpopuphidden="popupHidden()">
<panel id="inside-popup-container" align="start" onpopupshown="popupShown(event)" onpopuphidden="popupHidden()">
<resizer id="inside-popup" dir="bottomend"/>
<hbox width="50" height="50" flex="1"/>
</panel>
<resizer id="outside-popup" dir="bottomend" element="outside-popup-container"/>
<panel id="anchored-panel-container" onpopupshown="anchoredPopupShown(event)"
<panel id="anchored-panel-container" align="start" onpopupshown="anchoredPopupShown(event)"
onpopuphidden="popupHidden()">
<hbox width="50" height="50" flex="1"/>
<resizer id="anchored-panel" width="20" height="20"/>

View File

@ -546,3 +546,261 @@ function disableNonTestMouseEvents(aDisable)
if (utils)
utils.disableNonTestMouseEvents(aDisable);
}
function _getDOMWindowUtils(aWindow)
{
if (!aWindow) {
aWindow = window;
}
return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
}
/**
* Synthesize a composition event.
*
* @param aIsCompositionStart If true, this synthesize compositionstart event.
* Otherwise, compositionend event.
* @param aWindow Optional (If null, current |window| will be used)
*/
function synthesizeComposition(aIsCompositionStart, aWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return;
}
utils.sendCompositionEvent(aIsCompositionStart ?
"compositionstart" : "compositionend");
}
/**
* Synthesize a text event.
*
* @param aEvent The text event's information, this has |composition|
* and |caret| members. |composition| has |string| and
* |clauses| members. |clauses| must be array object. Each
* object has |length| and |attr|. And |caret| has |start| and
* |length|. See the following tree image.
*
* aEvent
* +-- composition
* | +-- string
* | +-- clauses[]
* | +-- length
* | +-- attr
* +-- caret
* +-- start
* +-- length
*
* Set the composition string to |composition.string|. Set its
* clauses information to the |clauses| array.
*
* When it's composing, set the each clauses' length to the
* |composition.clauses[n].length|. The sum of the all length
* values must be same as the length of |composition.string|.
* Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
* |composition.clauses[n].attr|.
*
* When it's not composing, set 0 to the
* |composition.clauses[0].length| and
* |composition.clauses[0].attr|.
*
* Set caret position to the |caret.start|. It's offset from
* the start of the composition string. Set caret length to
* |caret.length|. If it's larger than 0, it should be wide
* caret. However, current nsEditor doesn't support wide
* caret, therefore, you should always set 0 now.
*
* @param aWindow Optional (If null, current |window| will be used)
*/
function synthesizeText(aEvent, aWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return;
}
if (!aEvent.composition || !aEvent.composition.clauses ||
!aEvent.composition.clauses[0]) {
return;
}
var firstClauseLength = aEvent.composition.clauses[0].length;
var firstClauseAttr = aEvent.composition.clauses[0].attr;
var secondClauseLength = 0;
var secondClauseAttr = 0;
var thirdClauseLength = 0;
var thirdClauseAttr = 0;
if (aEvent.composition.clauses[1]) {
secondClauseLength = aEvent.composition.clauses[1].length;
secondClauseAttr = aEvent.composition.clauses[1].attr;
if (aEvent.composition.clauses[2]) {
thirdClauseLength = aEvent.composition.clauses[2].length;
thirdClauseAttr = aEvent.composition.clauses[2].attr;
}
}
var caretStart = -1;
var caretLength = 0;
if (aEvent.caret) {
caretStart = aEvent.caret.start;
caretLength = aEvent.caret.length;
}
utils.sendTextEvent(aEvent.composition.string,
firstClauseLength, firstClauseAttr,
secondClauseLength, secondClauseAttr,
thirdClauseLength, thirdClauseAttr,
caretStart, caretLength);
}
/**
* Synthesize a query selected text event.
*
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeQuerySelectedText(aWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
}
/**
* Synthesize a query text content event.
*
* @param aOffset The character offset. 0 means the first character in the
* selection root.
* @param aLength The length of getting text. If the length is too long,
* the extra length is ignored.
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeQueryTextContent(aOffset, aLength, aWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_TEXT_CONTENT,
aOffset, aLength, 0, 0);
}
/**
* Synthesize a query caret rect event.
*
* @param aOffset The caret offset. 0 means left side of the first character
* in the selection root.
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeQueryCaretRect(aOffset, aWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_CARET_RECT,
aOffset, 0, 0, 0);
}
/**
* Synthesize a query text rect event.
*
* @param aOffset The character offset. 0 means the first character in the
* selection root.
* @param aLength The length of the text. If the length is too long,
* the extra length is ignored.
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeQueryTextRect(aOffset, aLength, aWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_TEXT_RECT,
aOffset, aLength, 0, 0);
}
/**
* Synthesize a query editor rect event.
*
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeQueryEditorRect(aWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_EDITOR_RECT, 0, 0, 0, 0);
}
/**
* Synthesize a character at point event.
*
* @param aX, aY The offset in the client area of the DOM window.
* @param aWindow Optional (If null, current |window| will be used)
* @return An nsIQueryContentEventResult object. If this failed,
* the result might be null.
*/
function synthesizeCharAtPoint(aX, aY, aWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return nsnull;
}
return utils.sendQueryContentEvent(utils.QUERY_CHARACTER_AT_POINT,
0, 0, aX, aY);
}
/**
* Synthesize a selection set event.
*
* @param aOffset The character offset. 0 means the first character in the
* selection root.
* @param aLength The length of the text. If the length is too long,
* the extra length is ignored.
* @param aReverse If true, the selection is from |aOffset + aLength| to
* |aOffset|. Otherwise, from |aOffset| to |aOffset + aLength|.
* @param aWindow Optional (If null, current |window| will be used)
* @return True, if succeeded. Otherwise false.
*/
function synthesizeSelectionSet(aOffset, aLength, aReverse, aWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return false;
}
return utils.sendSelectionSetEvent(aOffset, aLength, aReverse);
}

View File

@ -88,6 +88,9 @@ interface nsILoginManager : nsISupports {
* If newLoginData is a nsIPropertyBag, only the specified properties
* will be changed. The nsILoginMetaInfo properties of oldLogin can be
* changed in this manner.
*
* If the propertybag contains an item named "timesUsedIncrement", the
* login's timesUsed property will be incremented by the item's value.
*/
void modifyLogin(in nsILoginInfo oldLogin, in nsISupports newLoginData);

View File

@ -111,6 +111,9 @@ interface nsILoginManagerStorage : nsISupports {
* If newLoginData is a nsIPropertyBag, only the specified properties
* will be changed. The nsILoginMetaInfo properties of oldLogin can be
* changed in this manner.
*
* If the propertybag contains an item named "timesUsedIncrement", the
* login's timesUsed property will be incremented by the item's value.
*/
void modifyLogin(in nsILoginInfo oldLogin, in nsISupports newLoginData);

View File

@ -37,7 +37,7 @@
#include "nsISupports.idl"
[scriptable, uuid(867407d5-10e0-43a0-bc81-a324740534ca)]
[scriptable, uuid(20d8eb40-c494-497f-b2a6-aaa32f807ebd)]
/**
* An object containing metainfo for a login stored by the login manager.
@ -59,4 +59,27 @@ interface nsILoginMetaInfo : nsISupports {
* addLogin and modifyLogin will throw if the GUID already exists.
*/
attribute AString guid;
/**
* The time, in Unix Epoch milliseconds, when the login was first created.
*/
attribute unsigned long long timeCreated;
/**
* The time, in Unix Epoch milliseconds, when the login was last submitted
* in a form or used to begin an HTTP auth session.
*/
attribute unsigned long long timeLastUsed;
/**
* The time, in Unix Epoch milliseconds, when the login's password was
* last modified.
*/
attribute unsigned long long timePasswordChanged;
/**
* The number of times the login was submitted in a form or used to begin
* an HTTP auth session.
*/
attribute unsigned long timesUsed;
};

View File

@ -121,6 +121,10 @@ nsLoginInfo.prototype = {
// Copy nsILoginMetaInfo props
clone.QueryInterface(Ci.nsILoginMetaInfo);
clone.guid = this.guid;
clone.timeCreated = this.timeCreated;
clone.timeLastUsed = this.timeLastUsed;
clone.timePasswordChanged = this.timePasswordChanged;
clone.timesUsed = this.timesUsed;
return clone;
},
@ -129,7 +133,11 @@ nsLoginInfo.prototype = {
// nsILoginMetaInfo interfaces...
//
guid : null
guid : null,
timeCreated : null,
timeLastUsed : null,
timePasswordChanged : null,
timesUsed : null
}; // end of nsLoginInfo implementation

View File

@ -828,6 +828,7 @@ LoginManager.prototype = {
// Check for autocomplete=off attribute. We don't use it to prevent
// autofilling (for existing logins), but won't save logins when it's
// present.
// XXX spin out a bug that we don't update timeLastUsed in this case?
if (this._isAutocompleteDisabled(form) ||
this._isAutocompleteDisabled(usernameField) ||
this._isAutocompleteDisabled(newPasswordField) ||
@ -913,6 +914,13 @@ LoginManager.prototype = {
this.log("...passwords differ, prompting to change.");
prompter = getPrompter(win);
prompter.promptToChangePassword(existingLogin, formLogin);
} else {
// Update the lastUsed timestamp.
var propBag = Cc["@mozilla.org/hash-property-bag;1"].
createInstance(Ci.nsIWritablePropertyBag);
propBag.setProperty("timeLastUsed", Date.now());
propBag.setProperty("timesUsedIncrement", 1);
this.modifyLogin(existingLogin, propBag);
}
return;

View File

@ -380,11 +380,6 @@ LoginManagerPrompter.prototype = {
return ok;
}
var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(Ci.nsILoginInfo);
newLogin.init(hostname, null, realm, aUsername.value, aPassword.value,
"", "");
// XXX We can't prompt with multiple logins yet (bug 227632), so
// the entered login might correspond to an existing login
// other than the one we originally selected.
@ -395,13 +390,18 @@ LoginManagerPrompter.prototype = {
if (!selectedLogin) {
// add as new
this.log("New login seen for " + realm);
var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(Ci.nsILoginInfo);
newLogin.init(hostname, null, realm,
aUsername.value, aPassword.value, "", "");
this._pwmgr.addLogin(newLogin);
} else if (aPassword.value != selectedLogin.password) {
// update password
this.log("Updating password for " + realm);
this._pwmgr.modifyLogin(selectedLogin, newLogin);
this._updateLogin(selectedLogin, aPassword.value);
} else {
this.log("Login unchanged, no further action needed.");
this._updateLogin(selectedLogin);
}
return ok;
@ -589,11 +589,6 @@ LoginManagerPrompter.prototype = {
return ok;
}
var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(Ci.nsILoginInfo);
newLogin.init(hostname, null, httpRealm,
username, password, "", "");
// XXX We can't prompt with multiple logins yet (bug 227632), so
// the entered login might correspond to an existing login
// other than the one we originally selected.
@ -602,9 +597,13 @@ LoginManagerPrompter.prototype = {
// If we didn't find an existing login, or if the username
// changed, save as a new login.
if (!selectedLogin) {
// add as new
this.log("New login seen for " + username +
" @ " + hostname + " (" + httpRealm + ")");
var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(Ci.nsILoginInfo);
newLogin.init(hostname, null, httpRealm,
username, password, "", "");
if (notifyBox)
this._showSaveLoginNotification(notifyBox, newLogin);
else
@ -616,12 +615,13 @@ LoginManagerPrompter.prototype = {
" @ " + hostname + " (" + httpRealm + ")");
if (notifyBox)
this._showChangeLoginNotification(notifyBox,
selectedLogin, newLogin);
selectedLogin, password);
else
this._pwmgr.modifyLogin(selectedLogin, newLogin);
this._updateLogin(selectedLogin, password);
} else {
this.log("Login unchanged, no further action needed.");
this._updateLogin(selectedLogin);
}
} catch (e) {
Components.utils.reportError("LoginManagerPrompter: " +
@ -923,9 +923,9 @@ LoginManagerPrompter.prototype = {
var notifyBox = this._getNotifyBox();
if (notifyBox)
this._showChangeLoginNotification(notifyBox, aOldLogin, aNewLogin);
this._showChangeLoginNotification(notifyBox, aOldLogin, aNewLogin.password);
else
this._showChangeLoginDialog(aOldLogin, aNewLogin);
this._showChangeLoginDialog(aOldLogin, aNewLogin.password);
},
@ -935,7 +935,7 @@ LoginManagerPrompter.prototype = {
* Shows the Change Password notification bar.
*
*/
_showChangeLoginNotification : function (aNotifyBox, aOldLogin, aNewLogin) {
_showChangeLoginNotification : function (aNotifyBox, aOldLogin, aNewPassword) {
var notificationText;
if (aOldLogin.username)
notificationText = this._getLocalizedString(
@ -957,7 +957,7 @@ LoginManagerPrompter.prototype = {
// The callbacks in |buttons| have a closure to access the variables
// in scope here; set one to |this._pwmgr| so we can get back to pwmgr
// without a getService() call.
var pwmgr = this._pwmgr;
var self = this;
var buttons = [
// "Yes" button
@ -966,7 +966,7 @@ LoginManagerPrompter.prototype = {
accessKey: changeButtonAccessKey,
popup: null,
callback: function(aNotificationBar, aButton) {
pwmgr.modifyLogin(aOldLogin, aNewLogin);
self._updateLogin(aOldLogin, aNewPassword);
}
},
@ -992,7 +992,7 @@ LoginManagerPrompter.prototype = {
* Shows the Change Password dialog.
*
*/
_showChangeLoginDialog : function (aOldLogin, aNewLogin) {
_showChangeLoginDialog : function (aOldLogin, aNewPassword) {
const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
var dialogText;
@ -1014,7 +1014,7 @@ LoginManagerPrompter.prototype = {
null, {});
if (ok) {
this.log("Updating password for user " + aOldLogin.username);
this._pwmgr.modifyLogin(aOldLogin, aNewLogin);
this._updateLogin(aOldLogin, aNewPassword);
}
},
@ -1047,17 +1047,10 @@ LoginManagerPrompter.prototype = {
usernames.length, usernames,
selectedIndex);
if (ok) {
// Now that we know which login to change the password for,
// update the missing username info in the aNewLogin.
// Now that we know which login to use, modify its password.
var selectedLogin = logins[selectedIndex.value];
this.log("Updating password for user " + selectedLogin.username);
aNewLogin.username = selectedLogin.username;
aNewLogin.usernameField = selectedLogin.usernameField;
this._pwmgr.modifyLogin(selectedLogin, aNewLogin);
this._updateLogin(selectedLogin, aNewLogin.password);
}
},
@ -1069,6 +1062,25 @@ LoginManagerPrompter.prototype = {
/*
* _updateLogin
*/
_updateLogin : function (login, newPassword) {
var now = Date.now();
var propBag = Cc["@mozilla.org/hash-property-bag;1"].
createInstance(Ci.nsIWritablePropertyBag);
if (newPassword) {
propBag.setProperty("password", newPassword);
// Explicitly set the password change time here (even though it would
// be changed automatically), to ensure that it's exactly the same
// value as timeLastUsed.
propBag.setProperty("timePasswordChanged", now);
}
propBag.setProperty("timeLastUsed", now);
propBag.setProperty("timesUsedIncrement", 1);
this._pwmgr.modifyLogin(login, propBag);
},
/*
* _getNotifyBox
*

View File

@ -42,7 +42,7 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const DB_VERSION = 3; // The database schema version
const DB_VERSION = 4; // The database schema version
const ENCTYPE_BASE64 = 0;
const ENCTYPE_SDR = 1;
@ -94,18 +94,23 @@ LoginManagerStorage_mozStorage.prototype = {
// The current database schema.
_dbSchema: {
tables: {
moz_logins: "id INTEGER PRIMARY KEY," +
"hostname TEXT NOT NULL," +
"httpRealm TEXT," +
"formSubmitURL TEXT," +
"usernameField TEXT NOT NULL," +
"passwordField TEXT NOT NULL," +
"encryptedUsername TEXT NOT NULL," +
"encryptedPassword TEXT NOT NULL," +
"guid TEXT," +
"encType INTEGER",
// Changes must be reflected in this._dbAreExpectedColumnsPresent
// and this._searchLogins
moz_logins: "id INTEGER PRIMARY KEY," +
"hostname TEXT NOT NULL," +
"httpRealm TEXT," +
"formSubmitURL TEXT," +
"usernameField TEXT NOT NULL," +
"passwordField TEXT NOT NULL," +
"encryptedUsername TEXT NOT NULL," +
"encryptedPassword TEXT NOT NULL," +
"guid TEXT," +
"encType INTEGER," +
"timeCreated INTEGER," +
"timeLastUsed INTEGER," +
"timePasswordChanged INTEGER," +
"timesUsed INTEGER",
// Changes must be reflected in this._dbAreExpectedColumnsPresent(),
// this._searchLogins(), and this.modifyLogin().
moz_disabledHosts: "id INTEGER PRIMARY KEY," +
"hostname TEXT UNIQUE ON CONFLICT REPLACE",
},
@ -259,25 +264,42 @@ LoginManagerStorage_mozStorage.prototype = {
(encUsername.charAt(0) == '~' || encPassword.charAt(0) == '~'))
encType = ENCTYPE_BASE64;
// Set timestamps
let currentTime = Date.now();
if (!loginClone.timeCreated)
loginClone.timeCreated = currentTime;
if (!loginClone.timeLastUsed)
loginClone.timeLastUsed = currentTime;
if (!loginClone.timePasswordChanged)
loginClone.timePasswordChanged = currentTime;
if (!loginClone.timesUsed)
loginClone.timesUsed = 1;
let query =
"INSERT INTO moz_logins " +
"(hostname, httpRealm, formSubmitURL, usernameField, " +
"passwordField, encryptedUsername, encryptedPassword, " +
"guid, encType) " +
"guid, encType, timeCreated, timeLastUsed, timePasswordChanged, " +
"timesUsed) " +
"VALUES (:hostname, :httpRealm, :formSubmitURL, :usernameField, " +
":passwordField, :encryptedUsername, :encryptedPassword, " +
":guid, :encType)";
":guid, :encType, :timeCreated, :timeLastUsed, " +
":timePasswordChanged, :timesUsed)";
let params = {
hostname: loginClone.hostname,
httpRealm: loginClone.httpRealm,
formSubmitURL: loginClone.formSubmitURL,
usernameField: loginClone.usernameField,
passwordField: loginClone.passwordField,
encryptedUsername: encUsername,
encryptedPassword: encPassword,
guid: loginClone.guid,
encType: encType
hostname: loginClone.hostname,
httpRealm: loginClone.httpRealm,
formSubmitURL: loginClone.formSubmitURL,
usernameField: loginClone.usernameField,
passwordField: loginClone.passwordField,
encryptedUsername: encUsername,
encryptedPassword: encPassword,
guid: loginClone.guid,
encType: encType,
timeCreated: loginClone.timeCreated,
timeLastUsed: loginClone.timeLastUsed,
timePasswordChanged: loginClone.timePasswordChanged,
timesUsed: loginClone.timesUsed
};
let stmt;
@ -344,11 +366,33 @@ LoginManagerStorage_mozStorage.prototype = {
newLoginData.username, newLoginData.password,
newLoginData.usernameField, newLoginData.passwordField);
newLogin.QueryInterface(Ci.nsILoginMetaInfo);
// Automatically update metainfo when password is changed.
if (newLogin.password != oldLogin.password)
newLogin.timePasswordChanged = Date.now();
} else if (newLoginData instanceof Ci.nsIPropertyBag) {
function _bagHasProperty(aPropName) {
try {
newLoginData.getProperty(aPropName);
return true;
} catch (e) {
return false;
}
}
// Clone the existing login, along with all its properties.
newLogin = oldStoredLogin.clone();
newLogin.QueryInterface(Ci.nsILoginMetaInfo);
// Automatically update metainfo when password is changed.
// (Done before the main property updates, lest the caller be
// explicitly updating both .password and .timePasswordChanged)
if (_bagHasProperty("password")) {
let newPassword = newLoginData.getProperty("password");
if (newPassword != oldLogin.password)
newLogin.timePasswordChanged = Date.now();
}
let propEnum = newLoginData.enumerator;
while (propEnum.hasMoreElements()) {
let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
@ -361,16 +405,22 @@ LoginManagerStorage_mozStorage.prototype = {
case "password":
case "usernameField":
case "passwordField":
newLogin[prop.name] = prop.value;
break;
// nsILoginMetaInfo properties...
case "guid":
newLogin.guid = prop.value;
if (!this._isGuidUnique(newLogin.guid))
case "timeCreated":
case "timeLastUsed":
case "timePasswordChanged":
case "timesUsed":
newLogin[prop.name] = prop.value;
if (prop.name == "guid" && !this._isGuidUnique(newLogin.guid))
throw "specified GUID already exists";
break;
// Fake property, allows easy incrementing.
case "timesUsedIncrement":
newLogin.timesUsed += prop.value;
break;
// Fail if caller requests setting an unknown property.
default:
throw "Unexpected propertybag item: " + prop.name;
@ -396,20 +446,28 @@ LoginManagerStorage_mozStorage.prototype = {
"encryptedUsername = :encryptedUsername, " +
"encryptedPassword = :encryptedPassword, " +
"guid = :guid, " +
"encType = :encType " +
"encType = :encType, " +
"timeCreated = :timeCreated, " +
"timeLastUsed = :timeLastUsed, " +
"timePasswordChanged = :timePasswordChanged, " +
"timesUsed = :timesUsed " +
"WHERE id = :id";
let params = {
id: idToModify,
hostname: newLogin.hostname,
httpRealm: newLogin.httpRealm,
formSubmitURL: newLogin.formSubmitURL,
usernameField: newLogin.usernameField,
passwordField: newLogin.passwordField,
encryptedUsername: encUsername,
encryptedPassword: encPassword,
guid: newLogin.guid,
encType: ENCTYPE_SDR
id: idToModify,
hostname: newLogin.hostname,
httpRealm: newLogin.httpRealm,
formSubmitURL: newLogin.formSubmitURL,
usernameField: newLogin.usernameField,
passwordField: newLogin.passwordField,
encryptedUsername: encUsername,
encryptedPassword: encPassword,
guid: newLogin.guid,
encType: ENCTYPE_SDR,
timeCreated: newLogin.timeCreated,
timeLastUsed: newLogin.timeLastUsed,
timePasswordChanged: newLogin.timePasswordChanged,
timesUsed: newLogin.timesUsed
};
let stmt;
@ -518,6 +576,10 @@ LoginManagerStorage_mozStorage.prototype = {
case "encryptedPassword":
case "guid":
case "encType":
case "timeCreated":
case "timeLastUsed":
case "timePasswordChanged":
case "timesUsed":
if (value == null) {
conditions.push(field + " isnull");
} else {
@ -554,6 +616,10 @@ LoginManagerStorage_mozStorage.prototype = {
// set nsILoginMetaInfo values
login.QueryInterface(Ci.nsILoginMetaInfo);
login.guid = stmt.row.guid;
login.timeCreated = stmt.row.timeCreated;
login.timeLastUsed = stmt.row.timeLastUsed;
login.timePasswordChanged = stmt.row.timePasswordChanged;
login.timesUsed = stmt.row.timesUsed;
logins.push(login);
ids.push(stmt.row.id);
}
@ -1247,25 +1313,14 @@ LoginManagerStorage_mozStorage.prototype = {
* Version 2 adds a GUID column. Existing logins are assigned a random GUID.
*/
_dbMigrateToVersion2 : function () {
// Check to see if GUID column already exists.
let exists = true;
try {
let stmt = this._dbConnection.createStatement(
"SELECT guid FROM moz_logins");
// (no need to execute statement, if it compiled we're good)
stmt.finalize();
} catch (e) {
exists = false;
}
// Check to see if GUID column already exists, add if needed
let query;
if (!this._dbColumnExists("guid")) {
query = "ALTER TABLE moz_logins ADD COLUMN guid TEXT";
this._dbConnection.executeSimpleSQL(query);
// Add the new column and index only if needed.
if (!exists) {
this._dbConnection.executeSimpleSQL(
"ALTER TABLE moz_logins ADD COLUMN guid TEXT");
this._dbConnection.executeSimpleSQL(
"CREATE INDEX IF NOT EXISTS " +
"moz_logins_guid_index ON moz_logins (guid)");
query = "CREATE INDEX IF NOT EXISTS moz_logins_guid_index ON moz_logins (guid)";
this._dbConnection.executeSimpleSQL(query);
}
// Get a list of IDs for existing logins
@ -1310,20 +1365,9 @@ LoginManagerStorage_mozStorage.prototype = {
* Version 3 adds a encType column.
*/
_dbMigrateToVersion3 : function () {
// Check to see if encType column already exists.
let exists = true;
let query = "SELECT encType FROM moz_logins";
let stmt;
try {
stmt = this._dbConnection.createStatement(query);
// (no need to execute statement, if it compiled we're good)
stmt.finalize();
} catch (e) {
exists = false;
}
// Add the new column and index only if needed.
if (!exists) {
// Check to see if encType column already exists, add if needed
let query;
if (!this._dbColumnExists("encType")) {
query = "ALTER TABLE moz_logins ADD COLUMN encType INTEGER";
this._dbConnection.executeSimpleSQL(query);
@ -1371,6 +1415,59 @@ LoginManagerStorage_mozStorage.prototype = {
},
/*
* _dbMigrateToVersion4
*
* Version 4 adds timeCreated, timeLastUsed, timePasswordChanged,
* and timesUsed columns
*/
_dbMigrateToVersion4 : function () {
let query;
// Add the new columns, if needed.
for each (let column in ["timeCreated", "timeLastUsed", "timePasswordChanged", "timesUsed"]) {
if (!this._dbColumnExists(column)) {
query = "ALTER TABLE moz_logins ADD COLUMN " + column + " INTEGER";
this._dbConnection.executeSimpleSQL(query);
}
}
// Get a list of IDs for existing logins.
let ids = [];
query = "SELECT id FROM moz_logins WHERE timeCreated isnull OR " +
"timeLastUsed isnull OR timePasswordChanged isnull OR timesUsed isnull";
try {
stmt = this._dbCreateStatement(query);
while (stmt.executeStep())
ids.push(stmt.row.id);
} catch (e) {
this.log("Failed getting IDs: " + e);
throw e;
} finally {
stmt.reset();
}
// Initialize logins with current time.
query = "UPDATE moz_logins SET timeCreated = :initTime, timeLastUsed = :initTime, " +
"timePasswordChanged = :initTime, timesUsed = 1 WHERE id = :id";
let params = {
id: null,
initTime: Date.now()
};
for each (let id in ids) {
params.id = id;
try {
stmt = this._dbCreateStatement(query, params);
stmt.execute();
} catch (e) {
this.log("Failed setting timestamps: " + e);
throw e;
} finally {
stmt.reset();
}
}
},
/*
* _dbAreExpectedColumnsPresent
*
@ -1388,7 +1485,11 @@ LoginManagerStorage_mozStorage.prototype = {
"encryptedUsername, " +
"encryptedPassword, " +
"guid, " +
"encType " +
"encType, " +
"timeCreated, " +
"timeLastUsed, " +
"timePasswordChanged, " +
"timesUsed " +
"FROM moz_logins";
try {
let stmt = this._dbConnection.createStatement(query);
@ -1415,6 +1516,24 @@ LoginManagerStorage_mozStorage.prototype = {
},
/*
* _dbColumnExists
*
* Checks to see if the named column already exists.
*/
_dbColumnExists : function (columnName) {
let query = "SELECT " + columnName + " FROM moz_logins";
try {
let stmt = this._dbConnection.createStatement(query);
// (no need to execute statement, if it compiled we're good)
stmt.finalize();
return true;
} catch (e) {
return false;
}
},
/*
* _dbCleanup
*

View File

@ -135,6 +135,11 @@ function checkTest() {
is(gotPass, "notifyp1", "Checking submitted password");
bar = getNotificationBar(notifyBox, "password-save");
ok(bar, "got notification bar");
// Sanity check, no logins should exist yet.
var logins = pwmgr.getAllLogins();
is(logins.length, 0, "Should not have any logins yet");
clickNotificationButton(bar, kRememberButton);
break;
@ -144,6 +149,16 @@ function checkTest() {
is(gotPass, "notifyp1", "Checking submitted password");
bar = getNotificationBar(notifyBox, "password-save");
ok(!bar, "checking for no notification bar");
// Check to make sure we updated the timestamps and use count on the
// existing loging that was submitted for this form.
var logins = pwmgr.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
ok(logins[0] instanceof Ci.nsILoginMetaInfo, "metainfo QI");
is(logins[0].timesUsed, 2, "check .timesUsed for existing login submission");
ok(logins[0].timeLastUsed > logins[0].timeCreated, "timeLastUsed bumped");
ok(logins[0].timeCreated == logins[0].timePasswordChanged, "timeChanged not updated");
// remove that login
pwmgr.removeLogin(login1);
break;
@ -286,6 +301,15 @@ function checkTest() {
ok(bar, "got notification bar");
clickNotificationButton(bar, kChangeButton);
// Check to make sure we updated the timestamps and use count for
// the login being changed with this form.
var logins = pwmgr.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
ok(logins[0] instanceof Ci.nsILoginMetaInfo, "metainfo QI");
is(logins[0].timesUsed, 2, "check .timesUsed incremented on change");
ok(logins[0].timeCreated < logins[0].timeLastUsed, "timeLastUsed bumped");
ok(logins[0].timeLastUsed == logins[0].timePasswordChanged, "timeUsed == timeChanged");
// cleanup
login1.password = "pass2";
pwmgr.removeLogin(login1);
@ -379,6 +403,8 @@ var pwmgr = Cc["@mozilla.org/login-manager;1"].
getService(Ci.nsILoginManager);
ok(pwmgr != null, "Access pwmgr");
pwmgr.removeAllLogins();
var prefs = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService);
ok(prefs != null, "Access prefs");

View File

@ -237,6 +237,13 @@ const LoginTest = {
profileFile.remove(false);
file.copyTo(PROFDIR, filename);
},
// Returns true if the timestamp is within 30 seconds of now.
is_about_now : function (timestamp) {
var delta = Math.abs(timestamp - Date.now());
var seconds = 30 * 1000;
return delta < seconds;
}
};

View File

@ -254,6 +254,157 @@ storage.removeLogin(wonkyLogin);
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]);
/* ========== 12 ========== */
testnum++;
testdesc = "check values for v4 DB addLogin";
var timeuser1 = new nsLoginInfo();
timeuser1.init("http://time1", "", null, "timeuser1", "origpass1", "foo", "bar");
storage.addLogin(timeuser1);
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3, timeuser1]);
let matchData = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
matchData.setProperty("hostname", "http://time1");
logins = storage.searchLogins({}, matchData);
do_check_eq(1, logins.length);
let tu1 = logins[0];
tu1.QueryInterface(Ci.nsILoginMetaInfo);
let time1 = tu1.timeCreated;
// Check that times and usage count were initialized properly.
do_check_true(LoginTest.is_about_now(time1));
do_check_eq(time1, tu1.timeLastUsed);
do_check_eq(time1, tu1.timePasswordChanged);
do_check_eq(1, tu1.timesUsed);
/* ========== 13 ========== */
testnum++;
testdesc = "check values for v4 DB addLogin part 2";
var timeuser2 = new nsLoginInfo();
timeuser2.init("http://time2", "", null, "timeuser2", "origpass2", "foo", "bar");
timeuser2.QueryInterface(Ci.nsILoginMetaInfo);
// This time pre-init the times and usage count.
timeuser2.timeCreated = 123;
timeuser2.timeLastUsed = 456;
timeuser2.timePasswordChanged = 789;
timeuser2.timesUsed = 42;
storage.addLogin(timeuser2);
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3, timeuser1, timeuser2]);
matchData = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
matchData.setProperty("hostname", "http://time2");
logins = storage.searchLogins({}, matchData);
do_check_eq(1, logins.length);
let tu2 = logins[0];
tu2.QueryInterface(Ci.nsILoginMetaInfo);
// Check that values we specified were set, instead of defaults.
do_check_eq(123, tu2.timeCreated);
do_check_eq(456, tu2.timeLastUsed);
do_check_eq(789, tu2.timePasswordChanged);
do_check_eq(42, tu2.timesUsed);
/* ========== 14 ========== */
testnum++;
testdesc = "check values for v4 DB modifyLogin";
let modData = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
modData.setProperty("timesUsed", 8);
storage.modifyLogin(timeuser2, modData);
modData = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
modData.setProperty("password", "newpass2");
storage.modifyLogin(timeuser2, modData);
timeuser2.password = "newpass2";
matchData = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
matchData.setProperty("hostname", "http://time2");
logins = storage.searchLogins({}, matchData);
do_check_eq(1, logins.length);
tu2 = logins[0];
tu2.QueryInterface(Ci.nsILoginMetaInfo);
// Check that values we specified were set, instead of defaults.
do_check_eq("newpass2", tu2.password);
do_check_eq(123, tu2.timeCreated);
do_check_eq(456, tu2.timeLastUsed);
do_check_true(LoginTest.is_about_now(tu2.timePasswordChanged));
do_check_eq(8, tu2.timesUsed);
/* ========== 15 ========== */
testnum++;
testdesc = "check values for v4 DB modifyLogin part 2";
modData = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
modData.setProperty("timesUsedIncrement", 2);
storage.modifyLogin(timeuser2, modData);
modData = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
modData.setProperty("password", "newpass2again\u0394\u00e8");
modData.setProperty("timePasswordChanged", 888); // pw change with specific time
storage.modifyLogin(timeuser2, modData);
timeuser2.password = "newpass2again\u0394\u00e8";
matchData = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
matchData.setProperty("hostname", "http://time2");
logins = storage.searchLogins({}, matchData);
do_check_eq(1, logins.length);
tu2 = logins[0];
tu2.QueryInterface(Ci.nsILoginMetaInfo);
// Check that values we specified were set, instead of defaults.
do_check_eq("newpass2again\u0394\u00e8", tu2.password);
do_check_eq(123, tu2.timeCreated);
do_check_eq(456, tu2.timeLastUsed);
do_check_eq(888, tu2.timePasswordChanged);
do_check_eq(10, tu2.timesUsed);
/* ========== 16 ========== */
testnum++;
testdesc = "check values for v4 DB modifyLogin part 3";
modData = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
modData.setProperty("timeCreated", 5444333222111);
modData.setProperty("timeLastUsed", 22222);
storage.modifyLogin(timeuser2, modData);
matchData = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
matchData.setProperty("hostname", "http://time2");
logins = storage.searchLogins({}, matchData);
do_check_eq(1, logins.length);
tu2 = logins[0];
tu2.QueryInterface(Ci.nsILoginMetaInfo);
// Check that values we specified were set, instead of defaults.
do_check_eq(5444333222111, tu2.timeCreated);
do_check_eq(22222, tu2.timeLastUsed);
do_check_eq(888, tu2.timePasswordChanged);
do_check_eq(10, tu2.timesUsed);
// some cleanup
storage.removeLogin(timeuser1);
storage.removeLogin(timeuser2);
LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]);
LoginTest.deleteFile(OUTDIR, "signons-unittest6.sqlite");
} catch (e) {

View File

@ -14,7 +14,7 @@ const ENCTYPE_SDR = 1;
// Current schema version used by storage-mozStorage.js. This will need to be
// kept in sync with the version there (or else the tests fail).
const CURRENT_SCHEMA = 3;
const CURRENT_SCHEMA = 4;
function run_test() {
@ -231,6 +231,69 @@ dbConnection.close();
LoginTest.deleteFile(OUTDIR, "signons-v2v3.sqlite");
/* ========== 7 ========== */
testnum++;
testdesc = "Test upgrade from v3->v4 storage"
LoginTest.copyFile("signons-v3.sqlite");
// Sanity check the test file.
dbConnection = LoginTest.openDB("signons-v3.sqlite");
do_check_eq(3, dbConnection.schemaVersion);
storage = LoginTest.reloadStorage(OUTDIR, "signons-v3.sqlite");
do_check_eq(CURRENT_SCHEMA, dbConnection.schemaVersion);
// Check that timestamps and counts were initialized correctly
LoginTest.checkStorageData(storage, [], [testuser1, testuser2]);
var logins = storage.getAllLogins();
for (var i = 0; i < 2; i++) {
do_check_true(logins[i] instanceof Ci.nsILoginMetaInfo);
do_check_eq(1, logins[i].timesUsed);
do_check_true(LoginTest.is_about_now(logins[i].timeCreated));
do_check_true(LoginTest.is_about_now(logins[i].timeLastUsed));
do_check_true(LoginTest.is_about_now(logins[i].timePasswordChanged));
}
/* ========== 8 ========== */
testnum++;
testdesc = "Test upgrade from v3->v4->v3 storage"
LoginTest.copyFile("signons-v3v4.sqlite");
// Sanity check the test file.
dbConnection = LoginTest.openDB("signons-v3v4.sqlite");
do_check_eq(3, dbConnection.schemaVersion);
storage = LoginTest.reloadStorage(OUTDIR, "signons-v3v4.sqlite");
do_check_eq(CURRENT_SCHEMA, dbConnection.schemaVersion);
// testuser1 already has timestamps, testuser2 does not.
LoginTest.checkStorageData(storage, [], [testuser1, testuser2]);
var logins = storage.getAllLogins();
var t1, t2;
if (logins[0].username == "testuser1") {
t1 = logins[0];
t2 = logins[1];
} else {
t1 = logins[1];
t2 = logins[0];
}
do_check_true(t1 instanceof Ci.nsILoginMetaInfo);
do_check_true(t2 instanceof Ci.nsILoginMetaInfo);
do_check_eq(9, t1.timesUsed);
do_check_eq(1262049951275, t1.timeCreated);
do_check_eq(1262049951275, t1.timeLastUsed);
do_check_eq(1262049951275, t1.timePasswordChanged);
do_check_eq(1, t2.timesUsed);
do_check_true(LoginTest.is_about_now(t2.timeCreated));
do_check_true(LoginTest.is_about_now(t2.timeLastUsed));
do_check_true(LoginTest.is_about_now(t2.timePasswordChanged));
} catch (e) {
throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e;
}

View File

@ -292,6 +292,12 @@ function() {
* Test adaptive autocomplete
*/
function run_test() {
// always search in history + bookmarks, no matter what the default is
var prefs = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
prefs.setIntPref("browser.urlbar.search.sources", 3);
prefs.setIntPref("browser.urlbar.default.behavior", 0);
do_test_pending();
next_test();
}

View File

@ -740,14 +740,14 @@ function onToolbarDragOver(aEvent)
toolbar = toolbar.parentNode;
}
var previousDragItem = gCurrentDragOverItem;
// Make sure we are dragging over a customizable toolbar.
if (!isCustomizableToolbar(toolbar)) {
if (!toolbar || !isCustomizableToolbar(toolbar)) {
gCurrentDragOverItem = null;
return;
}
var previousDragItem = gCurrentDragOverItem;
if (dropTarget.localName == "toolbar") {
gCurrentDragOverItem = dropTarget;
} else {

View File

@ -694,6 +694,14 @@ textbox[multiline="true"] {
-moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box");
}
html|textarea.textbox-textarea {
-moz-resize: none;
}
textbox[resizable="true"] > .textbox-input-box > html|textarea.textbox-textarea {
-moz-resize: both;
}
.textbox-input-box[spellcheck="true"] {
-moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box-spell");
}

View File

@ -70,6 +70,8 @@ _CHROME_FILES = test_bug343416.xul \
window_wheeltransaction.xul \
test_imestate.html \
test_plugin_scroll_consistency.html \
test_composition_text_querycontent.xul \
window_composition_text_querycontent.xul \
$(NULL)
ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)

View File

@ -143,7 +143,6 @@ protected:
PRBool TestExtents(void);
PRBool TestComposition(void);
PRBool TestNotification(void);
PRBool TestContentEvents(void);
PRBool TestEditMessages(void);
PRBool TestScrollMessages(void);
@ -1157,7 +1156,7 @@ public:
PRInt32 mFocusCount;
TSFMgrImpl(TestApp* test) : mTestApp(test), mTest(nsnull), mRefCnt(0),
mDeactivated(PR_FALSE), mFocusCount(0)
mDeactivated(PR_FALSE), mFocusedDocument(nsnull), mFocusCount(0)
{
}
@ -1661,9 +1660,9 @@ TestApp::OnStateChange(nsIWebProgress *aWebProgress,
NS_ASSERTION(aStateFlags & nsIWebProgressListener::STATE_IS_WINDOW &&
aStateFlags & nsIWebProgressListener::STATE_STOP, "wrong state");
if (NS_SUCCEEDED(Init())) {
printf("Testing content events...\n");
if (TestContentEvents())
passed("TestContentEvents");
mCurrentNode = mTextArea;
mTextArea->Focus();
if (RunTest(&TestApp::TestEditMessages))
passed("TestEditMessages");
if (RunTest(&TestApp::TestScrollMessages))
@ -2719,143 +2718,6 @@ TestApp::TestNotification(void)
return PR_TRUE;
}
PRBool
TestApp::TestContentEvents(void)
{
mTestString = NS_LITERAL_STRING(
"This is a test of the\r\nContent Events");
// 0123456789012345678901 2 34567890123456
// 0 1 2 3
mTextArea->SetValue(mTestString);
mTextArea->Focus();
nsCOMPtr<nsIWidget> widget;
if (!GetWidget(getter_AddRefs(widget))) {
fail("TestContentEvents: get nsIWidget");
return PR_FALSE;
}
nsCOMPtr<nsIWidget> topLevel = widget->GetTopLevelWidget();
if (!topLevel) {
fail("TestContentEvents: get top level widget");
return PR_FALSE;
}
nsIntRect widgetRect, topLevelRect;
nsresult nsr = widget->GetScreenBounds(widgetRect);
if (NS_FAILED(nsr)) {
fail("TestContentEvents: get widget rect");
return PR_FALSE;
}
nsr = topLevel->GetScreenBounds(topLevelRect);
if (NS_FAILED(nsr)) {
fail("TestContentEvents: get top level widget rect");
return PR_FALSE;
}
nsIntPoint widgetOffset = widgetRect.TopLeft() - topLevelRect.TopLeft();
nsEventStatus eventStatus;
PRBool result = PR_TRUE;
const PRUint32 kNone = nsQueryContentEvent::NOT_FOUND;
PRUint32 testingOffset[] = { 0, 10, 20, 23, 36 };
PRUint32 leftSideOffset[] = { kNone, 9, 19, kNone, 35 };
PRUint32 rightSideOffset[] = { 1, 11, kNone, 24, kNone };
for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(testingOffset); i++) {
nsQueryContentEvent textRect(PR_TRUE, NS_QUERY_TEXT_RECT, widget);
textRect.InitForQueryTextRect(testingOffset[i], 1);
nsr = widget->DispatchEvent(&textRect, eventStatus);
if (NS_FAILED(nsr) || !textRect.mSucceeded ||
textRect.mReply.mRect.IsEmpty()) {
fail("TestContentEvents: get text rect");
return PR_FALSE;
}
nsIntRect &charRect = textRect.mReply.mRect;
charRect.MoveBy(widgetOffset);
// Note that charRect might be inflated at rounding to pixels!
printf("TestContentEvents: testing... i=%lu, pt={ %ld, %ld }, size={ %ld, %ld }\n",
i, charRect.x, charRect.y, charRect.width, charRect.height);
nsQueryContentEvent charAtPt1(PR_TRUE, NS_QUERY_CHARACTER_AT_POINT, widget);
charAtPt1.refPoint.x = charRect.x + 1;
charAtPt1.refPoint.y = charRect.y + 1;
nsr = widget->DispatchEvent(&charAtPt1, eventStatus);
if (NS_FAILED(nsr) || !charAtPt1.mSucceeded) {
fail(" TestContentEvents: get char at point1");
return PR_FALSE;
}
printf(" NS_QUERY_CHARACTER_AT_POINT: pt={ %ld, %ld }, offset=%lu, rect={ %ld, %ld, %ld, %ld }\n",
charAtPt1.refPoint.x, charAtPt1.refPoint.y,
charAtPt1.mReply.mOffset, charAtPt1.mReply.mRect.x,
charAtPt1.mReply.mRect.y, charAtPt1.mReply.mRect.width,
charAtPt1.mReply.mRect.height);
if (charAtPt1.mReply.mOffset != testingOffset[i]) {
fail(" TestContentEvents: get char at point1 (wrong offset)");
result = PR_FALSE;
} else if (charAtPt1.mReply.mRect != textRect.mReply.mRect) {
fail(" TestContentEvents: get char at point1 (rect mismatch)");
result = PR_FALSE;
}
nsQueryContentEvent charAtPt2(PR_TRUE, NS_QUERY_CHARACTER_AT_POINT, widget);
charAtPt2.refPoint.x = charRect.XMost() - 2;
charAtPt2.refPoint.y = charRect.YMost() - 2;
nsr = widget->DispatchEvent(&charAtPt2, eventStatus);
if (NS_FAILED(nsr) || !charAtPt2.mSucceeded) {
fail(" TestContentEvents: get char at point2");
return PR_FALSE;
}
printf(" NS_QUERY_CHARACTER_AT_POINT: pt={ %ld, %ld }, offset=%lu, rect={ %ld, %ld, %ld, %ld }\n",
charAtPt2.refPoint.x, charAtPt2.refPoint.y,
charAtPt2.mReply.mOffset, charAtPt2.mReply.mRect.x,
charAtPt2.mReply.mRect.y, charAtPt2.mReply.mRect.width,
charAtPt2.mReply.mRect.height);
if (charAtPt2.mReply.mOffset != testingOffset[i]) {
fail(" TestContentEvents: get char at point2 (wrong offset)");
result = PR_FALSE;
} else if (charAtPt2.mReply.mRect != textRect.mReply.mRect) {
fail(" TestContentEvents: get char at point2 (rect mismatch)");
result = PR_FALSE;
}
nsQueryContentEvent charAtPt3(PR_TRUE, NS_QUERY_CHARACTER_AT_POINT, widget);
charAtPt3.refPoint.x = charRect.x - 2;
charAtPt3.refPoint.y = charRect.y + 1;
nsr = widget->DispatchEvent(&charAtPt3, eventStatus);
if (NS_FAILED(nsr) || !charAtPt3.mSucceeded) {
fail(" TestContentEvents: get char at point3");
return PR_FALSE;
}
printf(" NS_QUERY_CHARACTER_AT_POINT: pt={ %ld, %ld }, offset=%lu, rect={ %ld, %ld, %ld, %ld }\n",
charAtPt3.refPoint.x, charAtPt3.refPoint.y,
charAtPt3.mReply.mOffset, charAtPt3.mReply.mRect.x,
charAtPt3.mReply.mRect.y, charAtPt3.mReply.mRect.width,
charAtPt3.mReply.mRect.height);
if (charAtPt3.mReply.mOffset != leftSideOffset[i]) {
fail(" TestContentEvents: get left side char at point (wrong offset)");
result = PR_FALSE;
}
nsQueryContentEvent charAtPt4(PR_TRUE, NS_QUERY_CHARACTER_AT_POINT, widget);
charAtPt4.refPoint.x = charRect.XMost() + 1;
charAtPt4.refPoint.y = charRect.YMost() - 2;
nsr = widget->DispatchEvent(&charAtPt4, eventStatus);
if (NS_FAILED(nsr) || !charAtPt4.mSucceeded) {
fail(" TestContentEvents: get char at point4");
return PR_FALSE;
}
printf(" NS_QUERY_CHARACTER_AT_POINT: pt={ %ld, %ld }, offset=%lu, rect={ %ld, %ld, %ld, %ld }\n",
charAtPt4.refPoint.x, charAtPt4.refPoint.y,
charAtPt4.mReply.mOffset, charAtPt4.mReply.mRect.x,
charAtPt4.mReply.mRect.y, charAtPt4.mReply.mRect.width,
charAtPt4.mReply.mRect.height);
if (charAtPt4.mReply.mOffset != rightSideOffset[i]) {
fail(" TestContentEvents: get right side char at point4 (wrong offset)");
result = PR_FALSE;
}
}
return result;
}
PRBool
TestApp::TestEditMessages(void)
{

View File

@ -0,0 +1,30 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="Testing composition, text and query content events"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<body xmlns="http://www.w3.org/1999/xhtml">
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
SimpleTest.waitForExplicitFinish();
window.open("window_composition_text_querycontent.xul", "_blank",
"chrome,width=600,height=600");
]]>
</script>
</window>

View File

@ -0,0 +1,986 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="Testing composition, text and query content events"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onunload="onunload();">
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
<panel id="panel" hidden="true"
orient="vertical"
onpopupshown="onPanelShown(event);"
onpopuphidden="onPanelHidden(event);">
<vbox id="vbox">
<textbox id="textbox" onfocus="onFocusPanelTextbox(event);"
multiline="true" cols="20" rows="4"/>
</vbox>
</panel>
<body xmlns="http://www.w3.org/1999/xhtml">
<p id="display">
<textarea id="textarea" cols="20" rows="4"></textarea>
<iframe id="iframe" width="300" height="150"
src="data:text/html,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></iframe>
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTest, window);
function ok(aCondition, aMessage)
{
window.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
}
function is(aLeft, aRight, aMessage)
{
window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
}
function isnot(aLeft, aRight, aMessage)
{
window.opener.wrappedJSObject.SimpleTest.isnot(aLeft, aRight, aMessage);
}
function finish()
{
window.close();
}
function onunload()
{
window.opener.wrappedJSObject.SimpleTest.finish();
}
var textarea = document.getElementById("textarea");
var panel = document.getElementById("panel");
var textbox = document.getElementById("textbox");
var iframe = document.getElementById("iframe");
var textareaInFrame;
const nsIDOMWindowUtils = Components.interfaces.nsIDOMWindowUtils;
const kIsWin = (navigator.platform.indexOf("Win") == 0);
const kIsMac = (navigator.platform.indexOf("Mac") == 0);
function checkQueryContentResult(aResult, aMessage, aID)
{
ok(aResult, aMessage + ": the result is null");
if (!aResult) {
return false;
}
ok(aResult.succeeded, aMessage + ": the query content failed");
return aResult.succeeded;
}
function checkContent(aExpectedText, aMessage, aID)
{
var textContent = synthesizeQueryTextContent(0, 100);
if (!checkQueryContentResult(textContent, aMessage +
": synthesizeQueryTextContent " + aID)) {
return false;
}
is(textContent.text, aExpectedText,
aMessage + ": composition string is wrong" + aID);
return textContent.text == aExpectedText;
}
function checkSelection(aExpectedOffset, aExpectedText, aMessage, aID)
{
var selectedText = synthesizeQuerySelectedText();
if (!checkQueryContentResult(selectedText, aMessage +
": synthesizeQuerySelectedText " + aID)) {
return false;
}
is(selectedText.offset, aExpectedOffset,
aMessage + ": selection offset is wrong" + aID);
is(selectedText.text, aExpectedText,
aMessage + ": selected text is wrong" + aID);
return selectedText.offset == aExpectedOffset &&
selectedText.text == aExpectedText;
}
function checkRect(aRect, aExpectedRect, aMessage)
{
is(aRect.left, aExpectedRect.left, aMessage + ": left is wrong");
is(aRect.top, aExpectedRect.top, aMessage + " top is wrong");
is(aRect.width, aExpectedRect.width, aMessage + ": width is wrong");
is(aRect.height, aExpectedRect.height, aMessage + ": height is wrong");
return aRect.left == aExpectedRect.left &&
aRect.top == aExpectedRect.top &&
aRect.width == aExpectedRect.width &&
aRect.height == aExpectedRect.height;
}
function checkRectContainsRect(aRect, aContainer, aMessage)
{
var container = { left: Math.ceil(aContainer.left),
top: Math.ceil(aContainer.top),
width: Math.floor(aContainer.width),
height: Math.floor(aContainer.height) };
var ret = container.left <= aRect.left &&
container.top <= aRect.top &&
container.left + container.width >= aRect.left + aRect.width &&
container.top + container.height >= aRect.top + aRect.height;
ret = ret && aMessage;
ok(ret, aMessage + " container={ left=" + container.left + ", top=" +
container.top + ", width=" + container.width + ", height=" +
container.height + " } rect={ left=" + aRect.left + ", top=" + aRect.top +
", width=" + aRect.width + ", height=" + aRect.height + " }");
return ret;
}
function runCompositionTest()
{
textarea.value = "";
textarea.focus();
var caretRects = [];
var caretRect = synthesizeQueryCaretRect(0);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #0")) {
return false;
}
caretRects[0] = caretRect;
// start composition
synthesizeComposition(true);
// input first character
synthesizeText(
{ "composition":
{ "string": "\u3089",
"clauses":
[
{ "length": 1, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 1, "length": 0 }
});
if (!checkContent("\u3089", "runCompositionTest", "#1-1") ||
!checkSelection(1, "", "runCompositionTest", "#1-1")) {
return;
}
caretRect = synthesizeQueryCaretRect(1);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #1-1")) {
return false;
}
caretRects[1] = caretRect;
// input second character
synthesizeText(
{ "composition":
{ "string": "\u3089\u30FC",
"clauses":
[
{ "length": 2, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 2, "length": 0 }
});
if (!checkContent("\u3089\u30FC", "runCompositionTest", "#1-2") ||
!checkSelection(2, "", "runCompositionTest", "#1-2")) {
return;
}
caretRect = synthesizeQueryCaretRect(2);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #1-2")) {
return false;
}
caretRects[2] = caretRect;
isnot(caretRects[2].left, caretRects[1].left,
"runCompositionTest: caret isn't moved (#1-2)");
is(caretRects[2].top, caretRects[1].top,
"runCompositionTest: caret is moved to another line (#1-2)");
is(caretRects[2].width, caretRects[1].width,
"runCompositionTest: caret width is wrong (#1-2)");
is(caretRects[2].height, caretRects[1].height,
"runCompositionTest: caret width is wrong (#1-2)");
// input third character
synthesizeText(
{ "composition":
{ "string": "\u3089\u30FC\u3081",
"clauses":
[
{ "length": 3, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 3, "length": 0 }
});
if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3") ||
!checkSelection(3, "", "runCompositionTest", "#1-3")) {
return;
}
caretRect = synthesizeQueryCaretRect(3);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #1-3")) {
return false;
}
caretRects[3] = caretRect;
isnot(caretRects[3].left, caretRects[2].left,
"runCompositionTest: caret isn't moved (#1-3)");
is(caretRects[3].top, caretRects[2].top,
"runCompositionTest: caret is moved to another line (#1-3)");
is(caretRects[3].width, caretRects[2].width,
"runCompositionTest: caret width is wrong (#1-3)");
is(caretRects[3].height, caretRects[2].height,
"runCompositionTest: caret height is wrong (#1-3)");
// moves the caret left
synthesizeText(
{ "composition":
{ "string": "\u3089\u30FC\u3081",
"clauses":
[
{ "length": 3, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 2, "length": 0 }
});
if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-1") ||
!checkSelection(2, "", "runCompositionTest", "#1-3-1")) {
return;
}
caretRect = synthesizeQueryCaretRect(2);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #1-3-1")) {
return false;
}
is(caretRect.left, caretRects[2].left,
"runCompositionTest: caret rects are different (#1-3-1, left)");
is(caretRect.top, caretRects[2].top,
"runCompositionTest: caret rects are different (#1-3-1, top)");
// by bug 335359, the caret width depends on the right side's character.
is(caretRect.width, caretRects[2].width + 1,
"runCompositionTest: caret rects are different (#1-3-1, width)");
is(caretRect.height, caretRects[2].height,
"runCompositionTest: caret rects are different (#1-3-1, height)");
// moves the caret left
synthesizeText(
{ "composition":
{ "string": "\u3089\u30FC\u3081",
"clauses":
[
{ "length": 3, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 1, "length": 0 }
});
if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-2") ||
!checkSelection(1, "", "runCompositionTest", "#1-3-2")) {
return;
}
caretRect = synthesizeQueryCaretRect(1);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #1-3-2")) {
return false;
}
is(caretRect.left, caretRects[1].left,
"runCompositionTest: caret rects are different (#1-3-2, left)");
is(caretRect.top, caretRects[1].top,
"runCompositionTest: caret rects are different (#1-3-2, top)");
// by bug 335359, the caret width depends on the right side's character.
is(caretRect.width, caretRects[1].width + 1,
"runCompositionTest: caret rects are different (#1-3-2, width)");
is(caretRect.height, caretRects[1].height,
"runCompositionTest: caret rects are different (#1-3-2, height)");
synthesizeText(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093",
"clauses":
[
{ "length": 4, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 4, "length": 0 }
});
if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-4") ||
!checkSelection(4, "", "runCompositionTest", "#1-4")) {
return;
}
// backspace
synthesizeText(
{ "composition":
{ "string": "\u3089\u30FC\u3081",
"clauses":
[
{ "length": 3, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 3, "length": 0 }
});
if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-5") ||
!checkSelection(3, "", "runCompositionTest", "#1-5")) {
return;
}
// re-input
synthesizeText(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093",
"clauses":
[
{ "length": 4, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 4, "length": 0 }
});
if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-6") ||
!checkSelection(4, "", "runCompositionTest", "#1-6")) {
return;
}
synthesizeText(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093\u3055",
"clauses":
[
{ "length": 5, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 5, "length": 0 }
});
if (!checkContent("\u3089\u30FC\u3081\u3093\u3055", "runCompositionTest", "#1-7") ||
!checkSelection(5, "", "runCompositionTest", "#1-7")) {
return;
}
synthesizeText(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093\u3055\u3044",
"clauses":
[
{ "length": 6, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 6, "length": 0 }
});
if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044", "runCompositionTest", "#1-8") ||
!checkSelection(6, "", "runCompositionTest", "#1-8")) {
return;
}
synthesizeText(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
"clauses":
[
{ "length": 7, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 7, "length": 0 }
});
if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053", "runCompositionTest", "#1-8") ||
!checkSelection(7, "", "runCompositionTest", "#1-8")) {
return;
}
synthesizeText(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
"clauses":
[
{ "length": 8, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 8, "length": 0 }
});
if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
"runCompositionTest", "#1-9") ||
!checkSelection(8, "", "runCompositionTest", "#1-9")) {
return;
}
// convert
synthesizeText(
{ "composition":
{ "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
"clauses":
[
{ "length": 4,
"attr": nsIDOMWindowUtils.COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT },
{ "length": 2,
"attr": nsIDOMWindowUtils.COMPOSITION_ATTR_CONVERTEDTEXT }
]
},
"caret": { "start": 4, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
"runCompositionTest", "#1-10") ||
!checkSelection(6, "", "runCompositionTest", "#1-10")) {
return;
}
// change the selected clause
synthesizeText(
{ "composition":
{ "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
"clauses":
[
{ "length": 4,
"attr": nsIDOMWindowUtils.COMPOSITION_ATTR_CONVERTEDTEXT },
{ "length": 2,
"attr": nsIDOMWindowUtils.COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT }
]
},
"caret": { "start": 6, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
"runCompositionTest", "#1-11") ||
!checkSelection(6, "", "runCompositionTest", "#1-11")) {
return;
}
// reset clauses
synthesizeText(
{ "composition":
{ "string": "\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
"clauses":
[
{ "length": 5,
"attr": nsIDOMWindowUtils.COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT },
{ "length": 3,
"attr": nsIDOMWindowUtils.COMPOSITION_ATTR_CONVERTEDTEXT }
]
},
"caret": { "start": 5, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
"runCompositionTest", "#1-12") ||
!checkSelection(8, "", "runCompositionTest", "#1-12")) {
return;
}
var textRect1 = synthesizeQueryTextRect(0, 1);
var textRect2 = synthesizeQueryTextRect(1, 1);
if (!checkQueryContentResult(textRect1,
"runCompositionTest: synthesizeQueryTextRect #1-12-1") ||
!checkQueryContentResult(textRect2,
"runCompositionTest: synthesizeQueryTextRect #1-12-2")) {
return false;
}
// commit the composition string
synthesizeText(
{ "composition":
{ "string": "\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 8, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
"runCompositionTest", "#1-13") ||
!checkSelection(8, "", "runCompositionTest", "#1-13")) {
return;
}
synthesizeComposition(false);
var textRect3 = synthesizeQueryTextRect(0, 1);
var textRect4 = synthesizeQueryTextRect(1, 1);
if (!checkQueryContentResult(textRect3,
"runCompositionTest: synthesizeQueryTextRect #1-13-1") ||
!checkQueryContentResult(textRect4,
"runCompositionTest: synthesizeQueryTextRect #1-13-2")) {
return false;
}
checkRect(textRect3, textRect1, "runCompositionTest: textRect #1-13-1");
checkRect(textRect4, textRect2, "runCompositionTest: textRect #1-13-2");
// restart composition
synthesizeComposition(true);
// input characters
synthesizeText(
{ "composition":
{ "string": "\u3057",
"clauses":
[
{ "length": 1, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 1, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3057",
"runCompositionTest", "#2-1") ||
!checkSelection(8 + 1, "", "runCompositionTest", "#2-1")) {
return;
}
synthesizeText(
{ "composition":
{ "string": "\u3058",
"clauses":
[
{ "length": 1, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 1, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058",
"runCompositionTest", "#2-2") ||
!checkSelection(8 + 1, "", "runCompositionTest", "#2-2")) {
return;
}
synthesizeText(
{ "composition":
{ "string": "\u3058\u3087",
"clauses":
[
{ "length": 2, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 2, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087",
"runCompositionTest", "#2-3") ||
!checkSelection(8 + 2, "", "runCompositionTest", "#2-3")) {
return;
}
synthesizeText(
{ "composition":
{ "string": "\u3058\u3087\u3046",
"clauses":
[
{ "length": 3, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 3, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
"runCompositionTest", "#2-4") ||
!checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
return;
}
// commit the composition string
synthesizeText(
{ "composition":
{ "string": "\u3058\u3087\u3046",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 3, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
"runCompositionTest", "#2-4") ||
!checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
return;
}
synthesizeComposition(false);
// set selection
var selectionSetTest = synthesizeSelectionSet(4, 7, false);
ok(selectionSetTest, "runCompositionTest: selectionSetTest failed");
if (!checkSelection(4, "\u3055\u884C\u3053\u3046\u3058\u3087\u3046", "runCompositionTest", "#3-1")) {
return;
}
// start composition with selection
synthesizeComposition(true);
synthesizeText(
{ "composition":
{ "string": "\u304A",
"clauses":
[
{ "length": 1, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 1, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u304A",
"runCompositionTest", "#3-2") ||
!checkSelection(4 + 1, "", "runCompositionTest", "#3-2")) {
return;
}
// remove the composition string
synthesizeText(
{ "composition":
{ "string": "",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 0, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#3-3") ||
!checkSelection(4, "", "runCompositionTest", "#3-3")) {
return;
}
// re-input the composition string
synthesizeText(
{ "composition":
{ "string": "\u3046",
"clauses":
[
{ "length": 1, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 1, "length": 0 }
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3046",
"runCompositionTest", "#3-4") ||
!checkSelection(4 + 1, "", "runCompositionTest", "#3-4")) {
return;
}
// cancel the composition
synthesizeText(
{ "composition":
{ "string": "",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 0, "length": 0 }
});
synthesizeComposition(false);
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#3-5") ||
!checkSelection(4, "", "runCompositionTest", "#3-5")) {
return;
}
}
function runCharAtPointTest(aFocusedEditor, aTargetName)
{
aFocusedEditor.value = "This is a test of the\nContent Events";
// 012345678901234567890 12345678901234
// 0 1 2 3
const kLFLen = kIsWin ? 2 : 1;
const kNone = -1;
const kTestingOffset = [ 0, 10, 20, 21 + kLFLen, 34 + kLFLen];
const kLeftSideOffset = [ kNone, 9, 19, kNone, 33 + kLFLen];
const kRightSideOffset = [ 1, 11, kNone, 22 + kLFLen, kNone];
var editorRect = synthesizeQueryEditorRect();
if (!checkQueryContentResult(editorRect,
"runCharAtPointTest (" + aTargetName + "): editorRect")) {
return;
}
for (var i = 0; i < kTestingOffset.length; i++) {
var textRect = synthesizeQueryTextRect(kTestingOffset[i], 1);
if (!checkQueryContentResult(textRect,
"runCharAtPointTest (" + aTargetName + "): textRect", "i=" + i)) {
continue;
}
checkRectContainsRect(textRect, editorRect,
"runCharAtPointTest (" + aTargetName +
"): the text rect isn't in the editor");
// Test #1, getting same character rect by the point near the top-left.
var charAtPt1 = synthesizeCharAtPoint(textRect.left + 1,
textRect.top + 1);
if (checkQueryContentResult(charAtPt1,
"runCharAtPointTest (" + aTargetName + "): charAtPt1", "i=" + i)) {
ok(!charAtPt1.notFound,
"runCharAtPointTest (" + aTargetName + "): charAtPt1 isn't found: i=" + i);
if (!charAtPt1.notFound) {
is(charAtPt1.offset, kTestingOffset[i],
"runCharAtPointTest (" + aTargetName + "): charAtPt1 offset is wrong: i=" + i);
checkRect(charAtPt1, textRect, "runCharAtPointTest (" + aTargetName +
"): charAtPt1 left is wrong: i=" + i);
}
}
// Test #2, getting same character rect by the point near the bottom-right.
var charAtPt2 = synthesizeCharAtPoint(textRect.left + textRect.width - 2,
textRect.top + textRect.height - 2);
if (checkQueryContentResult(charAtPt2,
"runCharAtPointTest (" + aTargetName + "): charAtPt2", "i=" + i)) {
ok(!charAtPt2.notFound,
"runCharAtPointTest (" + aTargetName + "): charAtPt2 isn't found: i=" + i);
if (!charAtPt2.notFound) {
is(charAtPt2.offset, kTestingOffset[i],
"runCharAtPointTest (" + aTargetName + "): charAtPt2 offset is wrong: i=" + i);
checkRect(charAtPt2, textRect, "runCharAtPointTest (" + aTargetName +
"): charAtPt1 left is wrong: i=" + i);
}
}
// Test #3, getting left character offset.
var charAtPt3 = synthesizeCharAtPoint(textRect.left - 2,
textRect.top + 1);
if (checkQueryContentResult(charAtPt3,
"runCharAtPointTest (" + aTargetName + "): charAtPt3", "i=" + i)) {
is(charAtPt3.notFound, kLeftSideOffset[i] == kNone,
kLeftSideOffset[i] == kNone ?
"runCharAtPointTest (" + aTargetName + "): charAtPt3 is found: i=" + i :
"runCharAtPointTest (" + aTargetName + "): charAtPt3 isn't found: i=" + i);
if (!charAtPt3.notFound) {
is(charAtPt3.offset, kLeftSideOffset[i],
"runCharAtPointTest (" + aTargetName + "): charAtPt3 offset is wrong: i=" + i);
}
}
// Test #4, getting right character offset.
var charAtPt4 = synthesizeCharAtPoint(textRect.left + textRect.width + 1,
textRect.top + textRect.height - 2);
if (checkQueryContentResult(charAtPt4,
"runCharAtPointTest (" + aTargetName + "): charAtPt4", "i=" + i)) {
is(charAtPt4.notFound, kRightSideOffset[i] == kNone,
kRightSideOffset[i] == kNone ?
"runCharAtPointTest (" + aTargetName + "): charAtPt4 is found: i=" + i :
"runCharAtPointTest (" + aTargetName + "): charAtPt4 isn't found: i=" + i);
if (!charAtPt4.notFound) {
is(charAtPt4.offset, kRightSideOffset[i],
"runCharAtPointTest (" + aTargetName + "): charAtPt4 offset is wrong: i=" + i);
}
}
}
}
function runTestOnAnotherContext(aPanelOrFrame, aFocusedEditor, aTestName)
{
aFocusedEditor.value = "";
var editorRect = synthesizeQueryEditorRect();
if (!checkQueryContentResult(editorRect, aTestName + ": editorRect")) {
return;
}
var r = aPanelOrFrame.getBoundingClientRect();
var parentRect = { "left": r.left, "top": r.top, "width": r.right - r.left,
"height": r.bottom - r.top };
checkRectContainsRect(editorRect, parentRect, aTestName +
": the editor rect coordinates are wrong");
// start composition
synthesizeComposition(true);
// input characters
synthesizeText(
{ "composition":
{ "string": "\u3078\u3093\u3057\u3093",
"clauses":
[
{ "length": 4, "attr": nsIDOMWindowUtils.COMPOSITION_ATTR_RAWINPUT }
]
},
"caret": { "start": 4, "length": 0 }
});
if (!checkContent("\u3078\u3093\u3057\u3093", aTestName, "#1-1") ||
!checkSelection(4, "", aTestName, "#1-1")) {
return;
}
// convert them #1
synthesizeText(
{ "composition":
{ "string": "\u8FD4\u4FE1",
"clauses":
[
{ "length": 2,
"attr": nsIDOMWindowUtils.COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT }
]
},
"caret": { "start": 2, "length": 0 }
});
if (!checkContent("\u8FD4\u4FE1", aTestName, "#1-2") ||
!checkSelection(2, "", aTestName, "#1-2")) {
return;
}
// convert them #2
synthesizeText(
{ "composition":
{ "string": "\u5909\u8EAB",
"clauses":
[
{ "length": 2,
"attr": nsIDOMWindowUtils.COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT }
]
},
"caret": { "start": 2, "length": 0 }
});
if (!checkContent("\u5909\u8EAB", aTestName, "#1-3") ||
!checkSelection(2, "", aTestName, "#1-3")) {
return;
}
// commit them
synthesizeText(
{ "composition":
{ "string": "\u5909\u8EAB",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 2, "length": 0 }
});
if (!checkContent("\u5909\u8EAB", aTestName, "#1-4") ||
!checkSelection(2, "", aTestName, "#1-4")) {
return;
}
synthesizeComposition(false);
is(aFocusedEditor.value, "\u5909\u8EAB",
aTestName + ": composition isn't in the focused editor");
if (aFocusedEditor.value != "\u5909\u8EAB") {
return;
}
var textRect = synthesizeQueryTextRect(0, 1);
var caretRect = synthesizeQueryCaretRect(2);
if (!checkQueryContentResult(textRect,
aTestName + ": synthesizeQueryTextRect") ||
!checkQueryContentResult(caretRect,
aTestName + ": synthesizeQueryCaretRect")) {
return;
}
checkRectContainsRect(textRect, editorRect, aTestName + ":testRect");
checkRectContainsRect(caretRect, editorRect, aTestName + ":caretRect");
}
function runFrameTest()
{
var textareaInFrame = iframe.contentDocument.getElementById("textarea");
textareaInFrame.focus();
runTestOnAnotherContext(iframe, textareaInFrame, "runFrameTest");
runCharAtPointTest(textareaInFrame, "textarea in the iframe");
}
var gPanelShown = false;
var gPanelFocused = false;
function onPanelShown(aEvent)
{
gPanelShown = true;
textbox.focus();
setTimeout(doPanelTest, 0);
}
function onFocusPanelTextbox(aEvent)
{
gPanelFocused = true;
setTimeout(doPanelTest, 0);
}
var gIsPanelHiding = false;
var gIsRunPanelTestInternal = false;
function doPanelTest()
{
if (!gPanelFocused || !gPanelShown) {
return;
}
if (gIsRunPanelTestInternal) {
return;
}
gIsRunPanelTestInternal = true;
runTestOnAnotherContext(panel, textbox, "runPanelTest");
runCharAtPointTest(textbox, "textbox in the panel");
gIsPanelHiding = true;
panel.hidePopup();
}
function onPanelHidden(aEvent)
{
panel.hidden = true;
ok(gIsPanelHiding, "runPanelTest: the panel is hidden unexpectedly");
finish();
}
function runPanelTest()
{
panel.hidden = false;
panel.openPopupAtScreen(window.screenX + window.outerWidth, 0, false);
}
function runTest()
{
runCompositionTest();
runCharAtPointTest(textarea, "textarea in the document");
runFrameTest();
runPanelTest();
}
]]>
</script>
</window>

View File

@ -157,9 +157,10 @@ nsInputStreamTee::TeeSegment(const char *buf, PRUint32 count)
}
nsresult rv;
PRUint32 bytesWritten = 0;
PRUint32 totalBytesWritten = 0;
while (count) {
rv = mSink->Write(buf + bytesWritten, count, &bytesWritten);
PRUint32 bytesWritten = 0;
rv = mSink->Write(buf + totalBytesWritten, count, &bytesWritten);
if (NS_FAILED(rv)) {
// ok, this is not a fatal error... just drop our reference to mSink
// and continue on as if nothing happened.
@ -169,6 +170,7 @@ nsInputStreamTee::TeeSegment(const char *buf, PRUint32 count)
mSink = 0;
break;
}
totalBytesWritten += bytesWritten;
NS_ASSERTION(bytesWritten <= count, "wrote too much");
count -= bytesWritten;
}