Merge mozilla-central and inbound

This commit is contained in:
Ed Morley 2013-07-11 10:59:11 +01:00
commit 20148ab4c5
32 changed files with 608 additions and 196 deletions

View File

@ -3973,16 +3973,6 @@ var XULBrowserWindow = {
}
} else
disableFindCommands(false);
if (gFindBarInitialized) {
if (gFindBar.findMode != gFindBar.FIND_NORMAL) {
// Close the Find toolbar if we're in old-style TAF mode
gFindBar.close();
}
// fix bug 253793 - turn off highlight when page changes
gFindBar.getElement("highlight").checked = false;
}
}
UpdateBackForwardCommands(gBrowser.webNavigation);

View File

@ -687,6 +687,17 @@
// previous location.
this.mBrowser.missingPlugins = null;
if (this.mTabBrowser.isFindBarInitialized(this.mTab)) {
let findBar = this.mTabBrowser.getFindBar(this.mTab);
// Close the Find toolbar if we're in old-style TAF mode
if (findBar.findMode != findBar.FIND_NORMAL)
findBar.close();
// fix bug 253793 - turn off highlight when page changes
findBar.getElement("highlight").checked = false;
}
// Don't clear the favicon if this onLocationChange was
// triggered by a pushState or a replaceState. See bug 550565.
if (!gMultiProcessBrowser) {

View File

@ -39,6 +39,8 @@ function test() {
gBrowser.selectedTab = tabs[0];
setFindString(texts[0]);
// Turn on highlight for testing bug 891638
gFindBar.getElement("highlight").checked = true;
// Make sure the second tab is correct, then set it up
gBrowser.selectedTab = tabs[1];
@ -52,6 +54,17 @@ function test() {
gBrowser.selectedTab = tabs[0];
ok(!gFindBar.hidden, "First tab shows find bar!");
is(gFindBar._findField.value, texts[0], "First tab persists find value!");
ok(gFindBar.getElement("highlight").checked,
"Highlight button state persists!");
// While we're here, let's test bug 253793
gBrowser.reload();
gBrowser.addEventListener("DOMContentLoaded", continueTests, true);
}
function continueTests() {
gBrowser.removeEventListener("DOMContentLoaded", continueTests, true);
ok(!gFindBar.getElement("highlight").checked, "Highlight button reset!");
gFindBar.close();
ok(gFindBar.hidden, "First tab doesn't show find bar!");
gBrowser.selectedTab = tabs[1];

View File

@ -610,6 +610,16 @@ var SelectionHelperUI = {
this.startMark.position(targetMark.xPos, targetMark.yPos);
this.endMark.position(targetMark.xPos, targetMark.yPos);
// We delay transitioning until we know which direction the user is dragging
// based on a hysteresis value in the drag marker code. Down in our caller, we
// cache the first drag position in _cachedCaretPos so we can select from the
// initial caret drag position. Use those values if we have them. (Note
// _cachedCaretPos has already been translated in _getMarkerBaseMessage.)
let xpos = this._cachedCaretPos ? this._cachedCaretPos.xPos :
this._msgTarget.ctobx(targetMark.xPos, true);
let ypos = this._cachedCaretPos ? this._cachedCaretPos.yPos :
this._msgTarget.ctoby(targetMark.yPos, true);
// Start the selection monocle drag. SelectionHandler relies on this
// for getting initialized. This will also trigger a message back for
// monocle positioning. Note, markerDragMove is still on the stack in
@ -617,8 +627,8 @@ var SelectionHelperUI = {
this._sendAsyncMessage("Browser:SelectionSwitchMode", {
newMode: "selection",
change: targetMark.tag,
xPos: this._msgTarget.ctobx(targetMark.xPos, true),
yPos: this._msgTarget.ctoby(targetMark.yPos, true),
xPos: xpos,
yPos: ypos,
});
},
@ -1097,6 +1107,7 @@ var SelectionHelperUI = {
markerDragStart: function markerDragStart(aMarker) {
let json = this._getMarkerBaseMessage(aMarker.tag);
if (aMarker.tag == "caret") {
this._cachedCaretPos = null;
this._sendAsyncMessage("Browser:CaretMove", json);
return;
}
@ -1123,8 +1134,13 @@ var SelectionHelperUI = {
this._transitionFromCaretToSelection(aDirection);
return false;
}
// Cache for when we start the drag in _transitionFromCaretToSelection.
if (!this._cachedCaretPos) {
this._cachedCaretPos = this._getMarkerBaseMessage(aMarker.tag).caret;
}
return true;
}
this._cachedCaretPos = null;
// We'll re-display these after the drag is complete.
this._hideMonocles();

View File

@ -38,7 +38,7 @@ gTests.push({
let div = gWindow.document.getElementById("testdiv");
ok(div, "have the div");
sendElementTap(gWindow, div, 295); // end of 'outlook.com'
sendElementTap(gWindow, div, 287); // end of 'outlook.com'
yield waitForCondition(function () {
return SelectionHelperUI.isCaretUIVisible;

View File

@ -372,8 +372,13 @@ nsContentEventHandler::SetRangeFromFlatTextOffset(
nsRange* aRange,
uint32_t aNativeOffset,
uint32_t aNativeLength,
bool aExpandToClusterBoundaries)
bool aExpandToClusterBoundaries,
uint32_t* aNewNativeOffset)
{
if (aNewNativeOffset) {
*aNewNativeOffset = aNativeOffset;
}
nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
nsresult rv = iter->Init(mRootContent);
NS_ENSURE_SUCCESS(rv, rv);
@ -404,8 +409,12 @@ nsContentEventHandler::SetRangeFromFlatTextOffset(
ConvertToXPOffset(content, aNativeOffset - nativeOffset) : 0;
if (aExpandToClusterBoundaries) {
uint32_t oldXPOffset = xpOffset;
rv = ExpandToClusterBoundary(content, false, &xpOffset);
NS_ENSURE_SUCCESS(rv, rv);
if (aNewNativeOffset) {
*aNewNativeOffset -= (oldXPOffset - xpOffset);
}
}
rv = aRange->SetStart(domNode, int32_t(xpOffset));
@ -456,6 +465,9 @@ nsContentEventHandler::SetRangeFromFlatTextOffset(
MOZ_ASSERT(!mRootContent->IsNodeOfType(nsINode::eTEXT));
rv = aRange->SetStart(domNode, int32_t(mRootContent->GetChildCount()));
NS_ENSURE_SUCCESS(rv, rv);
if (aNewNativeOffset) {
*aNewNativeOffset = nativeOffset;
}
}
rv = aRange->SetEnd(domNode, int32_t(mRootContent->GetChildCount()));
NS_ASSERTION(NS_SUCCEEDED(rv), "nsIDOMRange::SetEnd failed");
@ -517,7 +529,8 @@ nsContentEventHandler::OnQueryTextContent(nsQueryContentEvent* aEvent)
nsRefPtr<nsRange> range = new nsRange(mRootContent);
rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset,
aEvent->mInput.mLength, false);
aEvent->mInput.mLength, false,
&aEvent->mReply.mOffset);
NS_ENSURE_SUCCESS(rv, rv);
rv = GenerateFlatTextContent(range, aEvent->mReply.mString);
@ -574,7 +587,10 @@ nsContentEventHandler::OnQueryTextRect(nsQueryContentEvent* aEvent)
nsRefPtr<nsRange> range = new nsRange(mRootContent);
rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset,
aEvent->mInput.mLength, true);
aEvent->mInput.mLength, true,
&aEvent->mReply.mOffset);
NS_ENSURE_SUCCESS(rv, rv);
rv = GenerateFlatTextContent(range, aEvent->mReply.mString);
NS_ENSURE_SUCCESS(rv, rv);
// used to iterate over all contents and their frames
@ -717,6 +733,7 @@ nsContentEventHandler::OnQueryCaretRect(nsQueryContentEvent* aEvent)
NS_ENSURE_SUCCESS(rv, rv);
aEvent->mReply.mRect =
rect.ToOutsidePixels(caretFrame->PresContext()->AppUnitsPerDevPixel());
aEvent->mReply.mOffset = aEvent->mInput.mOffset;
aEvent->mSucceeded = true;
return NS_OK;
}
@ -724,7 +741,8 @@ nsContentEventHandler::OnQueryCaretRect(nsQueryContentEvent* aEvent)
// Otherwise, we should set the guessed caret rect.
nsRefPtr<nsRange> range = new nsRange(mRootContent);
rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, 0, true);
rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset, 0, true,
&aEvent->mReply.mOffset);
NS_ENSURE_SUCCESS(rv, rv);
int32_t offsetInFrame;

View File

@ -91,7 +91,8 @@ protected:
nsresult SetRangeFromFlatTextOffset(nsRange* aRange,
uint32_t aNativeOffset,
uint32_t aNativeLength,
bool aExpandToClusterBoundaries);
bool aExpandToClusterBoundaries,
uint32_t* aNewNativeOffset = nullptr);
// Find the first textframe for the range, and get the start offset in
// the frame.
nsresult GetStartFrameAndOffset(nsRange* aRange,

View File

@ -51,7 +51,7 @@ class nsTextStateManager MOZ_FINAL : public nsISelectionListener,
{
public:
nsTextStateManager()
: mObserving(false)
: mObserving(nsIMEUpdatePreference::NOTIFY_NOTHING)
{
}
@ -79,7 +79,7 @@ private:
void NotifyContentAdded(nsINode* aContainer, int32_t aStart, int32_t aEnd);
void ObserveEditableNode();
bool mObserving;
nsIMEUpdatePreference::Notifications mObserving;
uint32_t mPreAttrChangeLength;
};
@ -736,9 +736,7 @@ nsTextStateManager::Init(nsIWidget* aWidget,
return;
}
if (mWidget->GetIMEUpdatePreference().mWantUpdates) {
ObserveEditableNode();
}
ObserveEditableNode();
}
void
@ -747,18 +745,19 @@ nsTextStateManager::ObserveEditableNode()
MOZ_ASSERT(mSel);
MOZ_ASSERT(mRootContent);
// add selection change listener
nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSel));
NS_ENSURE_TRUE_VOID(selPrivate);
nsresult rv = selPrivate->AddSelectionListener(this);
NS_ENSURE_SUCCESS_VOID(rv);
rv = selPrivate->AddSelectionListener(this);
NS_ENSURE_SUCCESS_VOID(rv);
mObserving = mWidget->GetIMEUpdatePreference().mWantUpdates;
if (mObserving & nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE) {
// add selection change listener
nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSel));
NS_ENSURE_TRUE_VOID(selPrivate);
nsresult rv = selPrivate->AddSelectionListener(this);
NS_ENSURE_SUCCESS_VOID(rv);
}
// add text change observer
mRootContent->AddMutationObserver(this);
mObserving = true;
if (mObserving & nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE) {
// add text change observer
mRootContent->AddMutationObserver(this);
}
}
void
@ -776,13 +775,13 @@ nsTextStateManager::Destroy(void)
}
// Even if there are some pending notification, it'll never notify the widget.
mWidget = nullptr;
if (mObserving && mSel) {
if ((mObserving & nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE) && mSel) {
nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSel));
if (selPrivate)
selPrivate->RemoveSelectionListener(this);
}
mSel = nullptr;
if (mObserving && mRootContent) {
if ((mObserving & nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE) && mRootContent) {
mRootContent->RemoveMutationObserver(this);
}
mRootContent = nullptr;

View File

@ -748,7 +748,7 @@ TabParent::RecvNotifyIMEFocus(const bool& aFocus,
{
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget) {
aPreference->mWantUpdates = false;
aPreference->mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING;
aPreference->mWantHints = false;
return true;
}

View File

@ -23,7 +23,6 @@
#define BOOKMARKS_FILE_50_NAME NS_LITERAL_CSTRING("bookmarks.html")
#define DOWNLOADS_FILE_50_NAME NS_LITERAL_CSTRING("downloads.rdf")
#define SEARCH_FILE_50_NAME NS_LITERAL_CSTRING("search.rdf" )
#define STORAGE_FILE_50_NAME NS_LITERAL_CSTRING("storage.sdb")
//*****************************************************************************
// nsProfileDirServiceProvider::nsProfileDirServiceProvider
@ -226,11 +225,6 @@ nsProfileDirServiceProvider::GetFile(const char *prop, bool *persistant, nsIFile
rv = EnsureProfileFileExists(localFile, domainDir);
}
}
else if (strcmp(prop, NS_APP_STORAGE_50_FILE) == 0) {
rv = domainDir->Clone(getter_AddRefs(localFile));
if (NS_SUCCEEDED(rv))
rv = localFile->AppendNative(STORAGE_FILE_50_NAME);
}
if (localFile && NS_SUCCEEDED(rv))

View File

@ -76,7 +76,7 @@ interface mozIStorageService : nsISupports {
* Get a connection to a named special database storage.
*
* @param aStorageKey a string key identifying the type of storage
* requested. Valid values include: "profile", "memory".
* requested. Valid values include: "memory".
*
* @see openDatabase for restrictions on how database connections may be
* used. For the profile database, you should only access it from the main
@ -189,6 +189,5 @@ interface mozIStorageService : nsISupports {
%{C++
#define MOZ_STORAGE_MEMORY_STORAGE_KEY "memory"
#define MOZ_STORAGE_PROFILE_STORAGE_KEY "profile"
%}

View File

@ -619,9 +619,6 @@ Service::getLocaleCollation()
////////////////////////////////////////////////////////////////////////////////
//// mozIStorageService
#ifndef NS_APP_STORAGE_50_FILE
#define NS_APP_STORAGE_50_FILE "UStor"
#endif
NS_IMETHODIMP
Service::OpenSpecialDatabase(const char *aStorageKey,
@ -634,13 +631,6 @@ Service::OpenSpecialDatabase(const char *aStorageKey,
// just fall through with NULL storageFile, this will cause the storage
// connection to use a memory DB.
}
else if (::strcmp(aStorageKey, "profile") == 0) {
rv = NS_GetSpecialDirectory(NS_APP_STORAGE_50_FILE,
getter_AddRefs(storageFile));
NS_ENSURE_SUCCESS(rv, rv);
// fall through to DB initialization
}
else {
return NS_ERROR_INVALID_ARG;
}

View File

@ -31,6 +31,9 @@ var _nextPortId = 1;
// new ClientPort.
this.getFrameWorkerHandle =
function getFrameWorkerHandle(url, clientWindow, name, origin, exposeLocalStorage = false) {
// prevent data/about urls - see bug 891516
if (['http', 'https'].indexOf(Services.io.newURI(url, null, null).scheme) < 0)
throw new Error("getFrameWorkerHandle requires http/https urls");
// first create the client port we are going to use. Later we will
// message the worker to create the worker port.
let portid = _nextPortId++;

View File

@ -16,6 +16,7 @@ ifdef MOZ_SOCIAL
MOCHITEST_BROWSER_FILES = \
head.js \
data.json \
echo.sjs \
worker_xhr.js \
browser_frameworker.js \
worker_relative.js \

View File

@ -3,7 +3,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function makeWorkerUrl(runner) {
return "data:application/javascript;charset=utf-8," + encodeURI("let run=" + runner.toSource()) + ";run();"
let prefix = "http://example.com/browser/toolkit/components/social/test/browser/echo.sjs?";
if (typeof runner == "function") {
runner = "let run=" + runner.toSource() + ";run();";
}
return prefix + encodeURI(runner);
}
var getFrameWorkerHandle;
@ -124,7 +128,8 @@ let tests = {
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), fakeWindow, "testPrototypes");
worker.port.onmessage = function(e) {
if (e.data.topic == "hello" && e.data.data.somextrafunction) {
if (e.data.topic == "hello") {
ok(e.data.data.somextrafunction, "have someextrafunction")
worker.terminate();
cbnext();
}
@ -455,7 +460,7 @@ let tests = {
let ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService2);
let oldManage = ioService.manageOfflineStatus;
let oldOffline = ioService.offline;
ioService.manageOfflineStatus = false;
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testNavigator");
let expected_topic = "onoffline";
@ -521,7 +526,7 @@ let tests = {
},
testEmptyWorker: function(cbnext) {
let worker = getFrameWorkerHandle("data:application/javascript;charset=utf-8,",
let worker = getFrameWorkerHandle(makeWorkerUrl(''),
undefined, "testEmptyWorker");
Services.obs.addObserver(function handleError(subj, topic, data) {
Services.obs.removeObserver(handleError, "social:frameworker-error");

View File

@ -10,7 +10,8 @@ function ensureProvider(workerFunction, cb) {
let manifest = {
origin: TEST_PROVIDER_ORIGIN,
name: "Example Provider",
workerURL: "data:application/javascript;charset=utf-8," + encodeURI("let run=" + workerFunction.toSource()) + ";run();"
workerURL: "http://example.com/browser/toolkit/components/social/test/browser/echo.sjs?"
+ encodeURI("let run=" + workerFunction.toSource()) + ";run();"
};
ensureSocialEnabled();

View File

@ -0,0 +1,9 @@
// A server-side JS test file for frameworker testing. It exists only to
// work-around a lack of data: URL support in the frameworker.
function handleRequest(request, response)
{
// The query string is the javascript - we just write it back.
response.setHeader("Content-Type", "application/javascript; charset=utf-8", false);
response.write(unescape(request.queryString));
}

View File

@ -49,9 +49,20 @@ const BackgroundPageThumbs = {
Services.tm.mainThread.dispatch(options.onDone.bind(options, url), 0);
return;
}
let cap = new Capture(url, this._onCaptureOrTimeout.bind(this), options);
this._captureQueue = this._captureQueue || [];
this._capturesByURL = this._capturesByURL || new Map();
// We want to avoid duplicate captures for the same URL. If there is an
// existing one, we just add the callback to that one and we are done.
let existing = this._capturesByURL.get(url);
if (existing) {
if (options.onDone)
existing.doneCallbacks.push(options.onDone);
// The queue is already being processed, so nothing else to do...
return;
}
let cap = new Capture(url, this._onCaptureOrTimeout.bind(this), options);
this._captureQueue.push(cap);
this._capturesByURL.set(url, cap);
this._processCaptureQueue();
},
@ -185,6 +196,7 @@ const BackgroundPageThumbs = {
if (idx < 0)
throw new Error("The capture should be in the queue.");
this._captureQueue.splice(idx, 1);
this._capturesByURL.delete(capture.url);
// Start the destroy-browser timer *before* processing the capture queue.
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
@ -212,6 +224,9 @@ function Capture(url, captureCallback, options) {
this.captureCallback = captureCallback;
this.options = options;
this.id = Capture.nextID++;
this.doneCallbacks = [];
if (options.onDone)
this.doneCallbacks.push(options.onDone);
// The timeout starts when the consumer requests the capture, not when the
// capture is dequeued and started.
@ -280,22 +295,22 @@ Capture.prototype = {
this.captureCallback(this);
this.destroy();
let callOnDone = function callOnDoneFn() {
if (!("onDone" in this.options))
return;
try {
this.options.onDone(this.url);
}
catch (err) {
Cu.reportError(err);
let callOnDones = function callOnDonesFn() {
for (let callback of this.doneCallbacks) {
try {
callback.call(this.options, this.url);
}
catch (err) {
Cu.reportError(err);
}
}
}.bind(this);
if (!data) {
callOnDone();
callOnDones();
return;
}
PageThumbs._store(this.url, data.finalURL, data.imageData).then(callOnDone);
PageThumbs._store(this.url, data.finalURL, data.imageData).then(callOnDones);
},
};

View File

@ -168,6 +168,7 @@ let tests = [
yield capture(url1);
ok(file1.exists(), "First file should exist after capture.");
file1.remove(false);
yield wait(2000);
is(imports.BackgroundPageThumbs._thumbBrowser, undefined,
@ -175,6 +176,7 @@ let tests = [
yield capture(url2);
ok(file2.exists(), "Second file should exist after capture.");
file2.remove(false);
imports.BackgroundPageThumbs._destroyBrowserTimeout = defaultTimeout;
isnot(imports.BackgroundPageThumbs._thumbBrowser, undefined,
@ -264,6 +266,37 @@ let tests = [
"Thumbnail file should exist even though it alerted.");
file.remove(false);
},
function noDuplicates() {
let deferred = imports.Promise.defer();
let url = "http://example.com/1";
let file = fileForURL(url);
ok(!file.exists(), "Thumbnail file should not already exist.");
let numCallbacks = 0;
let doneCallback = function(doneUrl) {
is(doneUrl, url, "called back with correct url");
numCallbacks += 1;
// We will delete the file after the first callback, then check it
// still doesn't exist on the second callback, which should give us
// confidence that we didn't end up with 2 different captures happening
// for the same url...
if (numCallbacks == 1) {
ok(file.exists(), "Thumbnail file should now exist.");
file.remove(false);
return;
}
if (numCallbacks == 2) {
ok(!file.exists(), "Thumbnail file should still be deleted.");
// and that's all we expect, so we are done...
deferred.resolve();
return;
}
ok(false, "only expecting 2 callbacks");
}
imports.BackgroundPageThumbs.capture(url, {onDone: doneCallback});
imports.BackgroundPageThumbs.capture(url, {onDone: doneCallback});
yield deferred.promise;
}
];
function capture(url, options) {

View File

@ -419,9 +419,6 @@ nsXREDirProvider::GetFile(const char* aProperty, bool* aPersistent,
EnsureProfileFileExists(file);
ensureFilePermissions = true;
}
else if (!strcmp(aProperty, NS_APP_STORAGE_50_FILE)) {
rv = file->AppendNative(NS_LITERAL_CSTRING("storage.sdb"));
}
else if (!strcmp(aProperty, NS_APP_DOWNLOADS_50_FILE)) {
rv = file->AppendNative(NS_LITERAL_CSTRING("downloads.rdf"));
}

View File

@ -2367,7 +2367,9 @@ nsWindow::NotifyIMEOfTextChange(uint32_t aStart,
nsIMEUpdatePreference
nsWindow::GetIMEUpdatePreference()
{
return nsIMEUpdatePreference(true, true);
int8_t notifications = (nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE |
nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE);
return nsIMEUpdatePreference(notifications, true);
}
void

View File

@ -340,6 +340,16 @@ public:
*/
bool DispatchEvent(nsGUIEvent& aEvent);
/**
* SetSelection() dispatches NS_SELECTION_SET event for the aRange.
*
* @param aRange The range which will be selected.
* @return TRUE if setting selection is succeeded and
* the widget hasn't been destroyed.
* Otherwise, FALSE.
*/
bool SetSelection(NSRange& aRange);
/**
* InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent.
*
@ -368,6 +378,13 @@ public:
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters);
/**
* GetWindowLevel() returns the window level of current focused (in Gecko)
* window. E.g., if an <input> element in XUL panel has focus, this returns
* the XUL panel's window level.
*/
NSInteger GetWindowLevel();
/**
* IsSpecialGeckoKey() checks whether aNativeKeyCode is mapped to a special
* Gecko keyCode. A key is "special" if it isn't used for text input.
@ -798,6 +815,8 @@ public:
virtual void OnFocusChangeInGecko(bool aFocus);
void OnSelectionChange() { mSelectedRange.location = NSNotFound; }
/**
* DispatchTextEvent() dispatches a text event on mWidget.
*
@ -823,9 +842,12 @@ public:
* create an NSAttributedString from it and pass
* that instead.
* @param aSelectedRange Current selected range (or caret position).
* @param aReplacementRange The range which will be replaced with the
* aAttrString instead of current marked range.
*/
void SetMarkedText(NSAttributedString* aAttrString,
NSRange& aSelectedRange);
NSRange& aSelectedRange,
NSRange* aReplacementRange = nullptr);
/**
* ConversationIdentifier() returns an ID for the current editor. The ID is
@ -841,12 +863,15 @@ public:
* which is allocated as autorelease for aRange.
*
* @param aRange The range of string which you want.
* @param aActualRange The actual range of the result.
* @return The string in aRange. If the string is empty,
* this returns nil. If succeeded, this returns
* an instance which is allocated as autorelease.
* If this has some troubles, returns nil.
*/
NSAttributedString* GetAttributedSubstringFromRange(NSRange& aRange);
NSAttributedString* GetAttributedSubstringFromRange(
NSRange& aRange,
NSRange* aActualRange = nullptr);
/**
* SelectedRange() returns current selected range.
@ -865,12 +890,15 @@ public:
* @param aRange A range of text to examine. Its position is
* an offset from the beginning of the focused
* editor or document.
* @param aActualRange If this is not null, this returns the actual
* range used for computing the result.
* @return An NSRect containing the first character in
* aRange, in screen coordinates.
* If the length of aRange is 0, the width will
* be 0.
*/
NSRect FirstRectForCharacterRange(NSRange& aRange);
NSRect FirstRectForCharacterRange(NSRange& aRange,
NSRange* aActualRange = nullptr);
/**
* CharacterIndexForPoint() returns an offset of a character at aPoint.
@ -927,9 +955,9 @@ protected:
// See the comment in nsCocoaTextInputHandler.mm.
nsCOMPtr<nsITimer> mTimer;
enum {
kResetIMEWindowLevel = 1,
kDiscardIMEComposition = 2,
kSyncASCIICapableOnly = 4
kNotifyIMEOfFocusChangeInGecko = 1,
kDiscardIMEComposition = 2,
kSyncASCIICapableOnly = 4
};
uint32_t mPendingMethods;
@ -946,8 +974,11 @@ protected:
* is no composition, this starts a composition and commits it immediately.
*
* @param aAttrString A string which is committed.
* @param aReplacementRange The range which will be replaced with the
* aAttrString instead of current selection.
*/
void InsertTextAsCommittingComposition(NSAttributedString* aAttrString);
void InsertTextAsCommittingComposition(NSAttributedString* aAttrString,
NSRange* aReplacementRange);
private:
// If mIsIMEComposing is true, the composition string is stored here.
@ -957,6 +988,7 @@ private:
nsString mLastDispatchedCompositionString;
NSRange mMarkedRange;
NSRange mSelectedRange;
bool mIsIMEComposing;
bool mIsIMEEnabled;
@ -967,13 +999,14 @@ private:
// that time, the focus processing in Gecko might not be finished yet. So,
// you cannot use nsQueryContentEvent or something.
bool mIsInFocusProcessing;
bool mIMEHasFocus;
void KillIMEComposition();
void SendCommittedText(NSString *aString);
void OpenSystemPreferredLanguageIME();
// Pending methods
void ResetIMEWindowLevel();
void NotifyIMEOfFocusChangeInGecko();
void DiscardIMEComposition();
void SyncASCIICapableOnly();
@ -1103,8 +1136,11 @@ public:
* the composition by the aAttrString.
*
* @param aAttrString An inserted string.
* @param aReplacementRange The range which will be replaced with the
* aAttrString instead of current selection.
*/
void InsertText(NSAttributedString *aAttrString);
void InsertText(NSAttributedString *aAttrString,
NSRange* aReplacementRange = nullptr);
/**
* doCommandBySelector event handler.

View File

@ -1929,7 +1929,8 @@ TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
}
void
TextInputHandler::InsertText(NSAttributedString *aAttrString)
TextInputHandler::InsertText(NSAttributedString* aAttrString,
NSRange* aReplacementRange)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
@ -1941,10 +1942,13 @@ TextInputHandler::InsertText(NSAttributedString *aAttrString)
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
"aReplacementRange=%p { location=%llu, length=%llu }, "
"IsIMEComposing()=%s, IgnoreIMEComposition()=%s, "
"keyevent=%p, keypressDispatched=%s",
this, GetCharacters([aAttrString string]), TrueOrFalse(IsIMEComposing()),
TrueOrFalse(IgnoreIMEComposition()),
this, GetCharacters([aAttrString string]), aReplacementRange,
aReplacementRange ? aReplacementRange->location : 0,
aReplacementRange ? aReplacementRange->length : 0,
TrueOrFalse(IsIMEComposing()), TrueOrFalse(IgnoreIMEComposition()),
currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
currentKeyEvent ?
TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A"));
@ -1953,14 +1957,54 @@ TextInputHandler::InsertText(NSAttributedString *aAttrString)
return;
}
InputContext context = mWidget->GetInputContext();
bool isEditable = (context.mIMEState.mEnabled == IMEState::ENABLED ||
context.mIMEState.mEnabled == IMEState::PASSWORD);
NSRange selectedRange = SelectedRange();
nsAutoString str;
nsCocoaUtils::GetStringForNSString([aAttrString string], str);
if (!IsIMEComposing() && str.IsEmpty()) {
return; // nothing to do
// nothing to do if there is no content which can be removed.
if (!isEditable) {
return;
}
// If replacement range is specified, we need to remove the range.
// Otherwise, we need to remove the selected range if it's not collapsed.
if (aReplacementRange && aReplacementRange->location != NSNotFound) {
// nothing to do since the range is collapsed.
if (aReplacementRange->length == 0) {
return;
}
// If the replacement range is different from current selected range,
// select the range.
if (!NSEqualRanges(selectedRange, *aReplacementRange)) {
NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
}
selectedRange = SelectedRange();
}
NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound);
if (selectedRange.length == 0) {
return; // nothing to do
}
// If this is caused by a key input, the keypress event which will be
// dispatched later should cause the delete. Therefore, nothing to do here.
// Although, we're not sure if such case is actually possible.
if (!currentKeyEvent) {
return;
}
// Delete the selected range.
nsRefPtr<TextInputHandler> kungFuDeathGrip(this);
nsContentCommandEvent deleteCommandEvent(true, NS_CONTENT_COMMAND_DELETE,
mWidget);
DispatchEvent(deleteCommandEvent);
NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
// Be aware! The widget might be destroyed here.
return;
}
if (str.Length() != 1 || IsIMEComposing()) {
InsertTextAsCommittingComposition(aAttrString);
InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
return;
}
@ -1972,6 +2016,14 @@ TextInputHandler::InsertText(NSAttributedString *aAttrString)
nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
// If the replacement range is specified, select the range. Then, the
// selection will be replaced by the later keypress event.
if (isEditable &&
aReplacementRange && aReplacementRange->location != NSNotFound &&
!NSEqualRanges(selectedRange, *aReplacementRange)) {
NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
}
// Dispatch keypress event with char instead of textEvent
nsKeyEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
keypressEvent.isChar = IsPrintableChar(str.CharAt(0));
@ -2237,15 +2289,15 @@ IMEInputHandler::GetCurrentTSMDocumentID()
******************************************************************************/
void
IMEInputHandler::ResetIMEWindowLevel()
IMEInputHandler::NotifyIMEOfFocusChangeInGecko()
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::ResetIMEWindowLevel, "
"Destroyed()=%s, IsFocused()=%s, GetCurrentTSMDocumentID()=%p",
("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, "
"Destroyed()=%s, IsFocused()=%s, inputContext=%p",
this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
GetCurrentTSMDocumentID()));
mView ? [mView inputContext] : nullptr));
if (Destroyed()) {
return;
@ -2253,45 +2305,24 @@ IMEInputHandler::ResetIMEWindowLevel()
if (!IsFocused()) {
// retry at next focus event
mPendingMethods |= kResetIMEWindowLevel;
mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
return;
}
TSMDocumentID doc = GetCurrentTSMDocumentID();
if (!doc) {
// retry
mPendingMethods |= kResetIMEWindowLevel;
NS_WARNING("Application is active but there is no active document");
ResetTimer();
return;
}
MOZ_ASSERT(mView);
NSTextInputContext* inputContext = [mView inputContext];
NS_ENSURE_TRUE_VOID(inputContext);
// When the focus of Gecko is on a text box of a popup panel, the actual
// focused view is the panel's parent view (mView). But the editor is
// displayed on the popuped widget's view (editorView). So, their window
// level may be different.
NSView<mozView>* editorView = mWidget->GetEditorView();
if (!editorView) {
NS_ERROR("editorView is null");
return;
}
// We need to set the focused window level to TSMDocument. Then, the popup
// windows of IME (E.g., a candidate list window) will be over the focused
// view. See http://developer.apple.com/technotes/tn2005/tn2128.html#TNTAG1
NSInteger windowLevel = [[editorView window] level];
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::ResetIMEWindowLevel, windowLevel=%s (%X)",
this, GetWindowLevelName(windowLevel), windowLevel));
// Chinese IMEs on 10.5 don't work fine if the level is NSNormalWindowLevel,
// then, we need to increment the value.
if (windowLevel == NSNormalWindowLevel)
windowLevel++;
::TSMSetDocumentProperty(GetCurrentTSMDocumentID(),
kTSMDocumentWindowLevelPropertyTag,
sizeof(windowLevel), &windowLevel);
// When an <input> element on a XUL <panel> element gets focus from an <input>
// element on the opener window of the <panel> element, the owner window
// still has native focus. Therefore, IMEs may store the opener window's
// level at this time because they don't know the actual focus is moved to
// different window. If IMEs try to get the newest window level after the
// focus change, we return the window level of the XUL <panel>'s widget.
// Therefore, let's emulate the native focus change. Then, IMEs can refresh
// the stored window level.
[inputContext deactivate];
[inputContext activate];
NS_OBJC_END_TRY_ABORT_BLOCK;
}
@ -2419,8 +2450,9 @@ IMEInputHandler::ExecutePendingMethods()
DiscardIMEComposition();
if (pendingMethods & kSyncASCIICapableOnly)
SyncASCIICapableOnly();
if (pendingMethods & kResetIMEWindowLevel)
ResetIMEWindowLevel();
if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) {
NotifyIMEOfFocusChangeInGecko();
}
mIsInFocusProcessing = false;
@ -2615,28 +2647,63 @@ IMEInputHandler::InitCompositionEvent(nsCompositionEvent& aCompositionEvent)
void
IMEInputHandler::InsertTextAsCommittingComposition(
NSAttributedString* aAttrString)
NSAttributedString* aAttrString,
NSRange* aReplacementRange)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::InsertTextAsCommittingComposition, "
"aAttrString=\"%s\", Destroyed()=%s, IsIMEComposing()=%s, "
"aAttrString=\"%s\", aReplacementRange=%p { location=%llu, length=%llu }, "
"Destroyed()=%s, IsIMEComposing()=%s, "
"mMarkedRange={ location=%llu, length=%llu }",
this, GetCharacters([aAttrString string]), TrueOrFalse(Destroyed()),
TrueOrFalse(IsIMEComposing()),
this, GetCharacters([aAttrString string]), aReplacementRange,
aReplacementRange ? aReplacementRange->location : 0,
aReplacementRange ? aReplacementRange->length : 0,
TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
mMarkedRange.location, mMarkedRange.length));
if (IgnoreIMECommit()) {
MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
"be called while canceling the composition");
}
if (Destroyed()) {
return;
}
// First, commit current composition with the latest composition string if the
// replacement range is different from marked range.
if (IsIMEComposing() && aReplacementRange &&
aReplacementRange->location != NSNotFound &&
!NSEqualRanges(MarkedRange(), *aReplacementRange)) {
NSString* latestStr =
nsCocoaUtils::ToNSString(mLastDispatchedCompositionString);
NSAttributedString* attrLatestStr =
[[[NSAttributedString alloc] initWithString:latestStr] autorelease];
InsertTextAsCommittingComposition(attrLatestStr, nullptr);
if (Destroyed()) {
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::InsertTextAsCommittingComposition, "
"destroyed by commiting composition for setting replacement range",
this));
return;
}
}
nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
nsString str;
nsCocoaUtils::GetStringForNSString([aAttrString string], str);
if (!IsIMEComposing()) {
// If there is no selection and replacement range is specified, set the
// range as selection.
if (aReplacementRange && aReplacementRange->location != NSNotFound &&
!NSEqualRanges(SelectedRange(), *aReplacementRange)) {
NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
}
// XXXmnakano Probably, we shouldn't emulate composition in this case.
// I think that we should just fire DOM3 textInput event if we implement it.
nsCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
@ -2653,13 +2720,6 @@ IMEInputHandler::InsertTextAsCommittingComposition(
OnStartIMEComposition();
}
if (IgnoreIMECommit()) {
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::InsertTextAsCommittingComposition, "
"IgnoreIMECommit()=%s", this, TrueOrFalse(IgnoreIMECommit())));
str.Truncate();
}
NSRange range = NSMakeRange(0, str.Length());
DispatchTextEvent(str, aAttrString, range, true);
if (Destroyed()) {
@ -2691,18 +2751,23 @@ IMEInputHandler::InsertTextAsCommittingComposition(
void
IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
NSRange& aSelectedRange)
NSRange& aSelectedRange,
NSRange* aReplacementRange)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::SetMarkedText, "
"aAttrString=\"%s\", aSelectedRange={ location=%llu, length=%llu }, "
"aReplacementRange=%p { location=%llu, length=%llu }, "
"Destroyed()=%s, IgnoreIMEComposition()=%s, IsIMEComposing()=%s, "
"mMarkedRange={ location=%llu, length=%llu }",
this, GetCharacters([aAttrString string]),
aSelectedRange.location, aSelectedRange.length, TrueOrFalse(Destroyed()),
TrueOrFalse(IgnoreIMEComposition()), TrueOrFalse(IsIMEComposing()),
aSelectedRange.location, aSelectedRange.length, aReplacementRange,
aReplacementRange ? aReplacementRange->location : 0,
aReplacementRange ? aReplacementRange->length : 0,
TrueOrFalse(Destroyed()), TrueOrFalse(IgnoreIMEComposition()),
TrueOrFalse(IsIMEComposing()),
mMarkedRange.location, mMarkedRange.length));
if (Destroyed() || IgnoreIMEComposition()) {
@ -2711,16 +2776,42 @@ IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
// First, commit current composition with the latest composition string if the
// replacement range is different from marked range.
if (IsIMEComposing() && aReplacementRange &&
aReplacementRange->location != NSNotFound &&
!NSEqualRanges(MarkedRange(), *aReplacementRange)) {
NSString* latestStr =
nsCocoaUtils::ToNSString(mLastDispatchedCompositionString);
NSAttributedString* attrLatestStr =
[[[NSAttributedString alloc] initWithString:latestStr] autorelease];
bool ignoreIMECommit = mIgnoreIMECommit;
mIgnoreIMECommit = false;
InsertTextAsCommittingComposition(attrLatestStr, nullptr);
mIgnoreIMECommit = ignoreIMECommit;
if (Destroyed()) {
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::SetMarkedText, "
"destroyed by commiting composition for setting replacement range",
this));
return;
}
}
nsString str;
nsCocoaUtils::GetStringForNSString([aAttrString string], str);
mMarkedRange.length = str.Length();
if (!IsIMEComposing() && !str.IsEmpty()) {
nsQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT,
mWidget);
DispatchEvent(selection);
mMarkedRange.location = selection.mSucceeded ? selection.mReply.mOffset : 0;
// If there is no selection and replacement range is specified, set the
// range as selection.
if (aReplacementRange && aReplacementRange->location != NSNotFound &&
!NSEqualRanges(SelectedRange(), *aReplacementRange)) {
NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
}
mMarkedRange.location = SelectedRange().location;
nsCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
InitCompositionEvent(compStart);
@ -2794,14 +2885,20 @@ IMEInputHandler::ConversationIdentifier()
}
NSAttributedString*
IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange)
IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange,
NSRange* aActualRange)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::GetAttributedSubstringFromRange, "
"aRange={ location=%llu, length=%llu }, Destroyed()=%s",
this, aRange.location, aRange.length, TrueOrFalse(Destroyed())));
"aRange={ location=%llu, length=%llu }, aActualRange=%p, Destroyed()=%s",
this, aRange.location, aRange.length, aActualRange,
TrueOrFalse(Destroyed())));
if (aActualRange) {
*aActualRange = NSMakeRange(NSNotFound, 0);
}
if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
return nil;
@ -2816,11 +2913,12 @@ IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange)
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::GetAttributedSubstringFromRange, "
"textContent={ mSucceeded=%s, mReply.mString=\"%s\"",
"textContent={ mSucceeded=%s, mReply={ mString=\"%s\", mOffset=%llu } }",
this, TrueOrFalse(textContent.mSucceeded),
NS_ConvertUTF16toUTF8(textContent.mReply.mString).get()));
NS_ConvertUTF16toUTF8(textContent.mReply.mString).get(),
textContent.mReply.mOffset));
if (!textContent.mSucceeded || textContent.mReply.mString.IsEmpty()) {
if (!textContent.mSucceeded) {
return nil;
}
@ -2828,6 +2926,10 @@ IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange)
NSAttributedString* result =
[[[NSAttributedString alloc] initWithString:nsstr
attributes:nil] autorelease];
if (aActualRange) {
aActualRange->location = textContent.mReply.mOffset;
aActualRange->length = textContent.mReply.mString.Length();
}
return result;
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
@ -2864,12 +2966,18 @@ IMEInputHandler::SelectedRange()
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::SelectedRange, Destroyed()=%s",
this, TrueOrFalse(Destroyed())));
("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ "
"location=%llu, length=%llu }",
this, TrueOrFalse(Destroyed()), mSelectedRange.location,
mSelectedRange.length));
NSRange range = NSMakeRange(NSNotFound, 0);
if (Destroyed()) {
return range;
return mSelectedRange;
}
if (mSelectedRange.location != NSNotFound) {
MOZ_ASSERT(mIMEHasFocus);
return mSelectedRange;
}
nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
@ -2884,30 +2992,42 @@ IMEInputHandler::SelectedRange()
selection.mReply.mString.Length()));
if (!selection.mSucceeded) {
return range;
return mSelectedRange;
}
if (mIMEHasFocus) {
mSelectedRange.location = selection.mReply.mOffset;
mSelectedRange.length = selection.mReply.mString.Length();
return mSelectedRange;
}
return NSMakeRange(selection.mReply.mOffset,
selection.mReply.mString.Length());
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0));
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(mSelectedRange);
}
NSRect
IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange)
IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange,
NSRange* aActualRange)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s"
"aRange={ location=%llu, length=%llu }",
this, TrueOrFalse(Destroyed()), aRange.location, aRange.length));
("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, "
"aRange={ location=%llu, length=%llu }, aActualRange=%p }",
this, TrueOrFalse(Destroyed()), aRange.location, aRange.length,
aActualRange));
// XXX this returns first character rect or caret rect, it is limitation of
// now. We need more work for returns first line rect. But current
// implementation is enough for IMEs.
NSRect rect;
NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
NSRange actualRange = NSMakeRange(NSNotFound, 0);
if (aActualRange) {
*aActualRange = actualRange;
}
if (Destroyed() || aRange.location == NSNotFound) {
return rect;
}
@ -2922,6 +3042,8 @@ IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange)
DispatchEvent(charRect);
if (charRect.mSucceeded) {
r = charRect.mReply.mRect;
actualRange.location = charRect.mReply.mOffset;
actualRange.length = charRect.mReply.mString.Length();
} else {
useCaretRect = true;
}
@ -2936,6 +3058,8 @@ IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange)
}
r = caretRect.mReply.mRect;
r.width = 0;
actualRange.location = caretRect.mReply.mOffset;
actualRange.length = 0;
}
nsIWidget* rootWidget = mWidget->GetTopLevelWidget();
@ -2950,11 +3074,17 @@ IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange)
rect = [rootView convertRect:rect toView:nil];
rect.origin = [rootWindow convertBaseToScreen:rect.origin];
if (aActualRange) {
*aActualRange = actualRange;
}
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p IMEInputHandler::FirstRectForCharacterRange, "
"useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }",
"useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, "
"actualRange={ location=%llu, length=%llu }",
this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y,
rect.size.width, rect.size.height));
rect.size.width, rect.size.height, actualRange.location,
actualRange.length));
return rect;
@ -3011,12 +3141,14 @@ IMEInputHandler::IMEInputHandler(nsChildView* aWidget,
mPendingMethods(0), mIMECompositionString(nullptr),
mIsIMEComposing(false), mIsIMEEnabled(true),
mIsASCIICapableOnly(false), mIgnoreIMECommit(false),
mIsInFocusProcessing(false)
mIsInFocusProcessing(false), mIMEHasFocus(false)
{
InitStaticMembers();
mMarkedRange.location = NSNotFound;
mMarkedRange.length = 0;
mSelectedRange.location = NSNotFound;
mSelectedRange.length = 0;
}
IMEInputHandler::~IMEInputHandler()
@ -3038,6 +3170,9 @@ IMEInputHandler::OnFocusChangeInGecko(bool aFocus)
"sFocusedIMEHandler=%p",
this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
mSelectedRange.location = NSNotFound; // Marking dirty
mIMEHasFocus = aFocus;
// This is called when the native focus is changed and when the native focus
// isn't changed but the focus is changed in Gecko.
if (!aFocus) {
@ -3049,11 +3184,9 @@ IMEInputHandler::OnFocusChangeInGecko(bool aFocus)
sFocusedIMEHandler = this;
mIsInFocusProcessing = true;
// We need to reset the IME's window level by the current focused view of
// Gecko. It may be different from mView. However, we cannot get the
// new focused view here because the focus change process in Gecko hasn't
// been finished yet. So, we should post the job to the todo list.
mPendingMethods |= kResetIMEWindowLevel;
// We need to notify IME of focus change in Gecko as native focus change
// because the window level of the focused element in Gecko may be changed.
mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
ResetTimer();
}
@ -3082,6 +3215,9 @@ IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget)
OnEndIMEComposition();
}
mSelectedRange.location = NSNotFound; // Marking dirty
mIMEHasFocus = false;
return true;
}
@ -4113,6 +4249,51 @@ TextInputHandlerBase::SynthesizeNativeKeyEvent(
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NSInteger
TextInputHandlerBase::GetWindowLevel()
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s",
this, TrueOrFalse(Destroyed())));
if (Destroyed()) {
return NSNormalWindowLevel;
}
// When an <input> element on a XUL <panel> is focused, the actual focused view
// is the panel's parent view (mView). But the editor is displayed on the
// popped-up widget's view (editorView). We want the latter's window level.
NSView<mozView>* editorView = mWidget->GetEditorView();
NS_ENSURE_TRUE(editorView, NSNormalWindowLevel);
NSInteger windowLevel = [[editorView window] level];
PR_LOG(gLog, PR_LOG_ALWAYS,
("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%X)",
this, GetWindowLevelName(windowLevel), windowLevel));
return windowLevel;
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
}
bool
TextInputHandlerBase::SetSelection(NSRange& aRange)
{
MOZ_ASSERT(!Destroyed());
nsRefPtr<TextInputHandlerBase> kungFuDeathGrip(this);
nsSelectionEvent selectionEvent(true, NS_SELECTION_SET, mWidget);
selectionEvent.mOffset = aRange.location;
selectionEvent.mLength = aRange.length;
selectionEvent.mReversed = false;
selectionEvent.mExpandToClusterBoundary = false;
DispatchEvent(selectionEvent);
NS_ENSURE_TRUE(selectionEvent.mSucceeded, false);
return !Destroyed();
}
/* static */ bool
TextInputHandlerBase::IsPrintableChar(PRUnichar aChar)
{

View File

@ -215,7 +215,7 @@ typedef NSInteger NSEventGestureAxis;
#ifdef ACCESSIBILITY
mozAccessible,
#endif
mozView, NSTextInput>
mozView, NSTextInput, NSTextInputClient>
{
@private
// the nsChildView that created the view. It retains this NSView, so
@ -480,6 +480,7 @@ public:
NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
const InputContextAction& aAction);
NS_IMETHOD_(InputContext) GetInputContext();
virtual nsIMEUpdatePreference GetIMEUpdatePreference() MOZ_OVERRIDE;
NS_IMETHOD GetToggledKeyState(uint32_t aKeyCode,
bool* aLEDState);

View File

@ -1811,6 +1811,9 @@ nsChildView::NotifyIME(NotificationToIME aNotification)
NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
mTextInputHandler->OnFocusChangeInGecko(false);
return NS_OK;
case NOTIFY_IME_OF_SELECTION_CHANGE:
NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
mTextInputHandler->OnSelectionChange();
default:
return NS_ERROR_NOT_IMPLEMENTED;
}
@ -1880,6 +1883,13 @@ nsChildView::GetInputContext()
return mInputContext;
}
nsIMEUpdatePreference
nsChildView::GetIMEUpdatePreference()
{
return nsIMEUpdatePreference(nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE,
false);
}
NS_IMETHODIMP nsChildView::GetToggledKeyState(uint32_t aKeyCode,
bool* aLEDState)
{
@ -4946,7 +4956,7 @@ static int32_t RoundUp(double aDouble)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
NS_ENSURE_TRUE(mGeckoChild, );
NS_ENSURE_TRUE_VOID(mGeckoChild);
nsAutoRetainCocoaObject kungFuDeathGrip(self);
@ -4988,7 +4998,7 @@ static int32_t RoundUp(double aDouble)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
NS_ENSURE_TRUE(mTextInputHandler, );
NS_ENSURE_TRUE_VOID(mTextInputHandler);
nsAutoRetainCocoaObject kungFuDeathGrip(self);
@ -5082,6 +5092,75 @@ static int32_t RoundUp(double aDouble)
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
}
#pragma mark -
// NSTextInputClient implementation
- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
NS_ENSURE_TRUE_VOID(mGeckoChild);
nsAutoRetainCocoaObject kungFuDeathGrip(self);
NSAttributedString* attrStr;
if ([aString isKindOfClass:[NSAttributedString class]]) {
attrStr = static_cast<NSAttributedString*>(aString);
} else {
attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
}
mTextInputHandler->InsertText(attrStr, &replacementRange);
NS_OBJC_END_TRY_ABORT_BLOCK;
}
- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange
replacementRange:(NSRange)replacementRange
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
NS_ENSURE_TRUE_VOID(mTextInputHandler);
nsAutoRetainCocoaObject kungFuDeathGrip(self);
NSAttributedString* attrStr;
if ([aString isKindOfClass:[NSAttributedString class]]) {
attrStr = static_cast<NSAttributedString*>(aString);
} else {
attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
}
mTextInputHandler->SetMarkedText(attrStr, selectedRange, &replacementRange);
NS_OBJC_END_TRY_ABORT_BLOCK;
}
- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)aRange
actualRange:(NSRangePointer)actualRange
{
NS_ENSURE_TRUE(mTextInputHandler, nil);
return mTextInputHandler->GetAttributedSubstringFromRange(aRange,
actualRange);
}
- (NSRect)firstRectForCharacterRange:(NSRange)aRange
actualRange:(NSRangePointer)actualRange
{
NS_ENSURE_TRUE(mTextInputHandler, NSMakeRect(0.0, 0.0, 0.0, 0.0));
return mTextInputHandler->FirstRectForCharacterRange(aRange, actualRange);
}
- (NSInteger)windowLevel
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
NS_ENSURE_TRUE(mTextInputHandler, [[self window] level]);
return mTextInputHandler->GetWindowLevel();
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
}
#pragma mark -
#ifdef __LP64__

View File

@ -359,6 +359,9 @@ nsCocoaUtils::GetStringForNSString(const NSString *aSrc, nsAString& aDist)
NSString*
nsCocoaUtils::ToNSString(const nsAString& aString)
{
if (aString.IsEmpty()) {
return [NSString string];
}
return [NSString stringWithCharacters:aString.BeginReading()
length:aString.Length()];
}

View File

@ -191,13 +191,14 @@ enum nsTopLevelWidgetZPlacement { // for PlaceBehind()
/**
* Preference for receiving IME updates
*
* If mWantUpdates is true, nsTextStateManager will observe text change and
* selection change and call nsIWidget::NotifyIMEOfTextChange() and
* nsIWidget::NotifyIME(NOTIFY_IME_OF_SELECTION_CHANGE). The observing cost is
* very expensive.
* If mWantUpdates is not NOTIFY_NOTHING, nsTextStateManager will observe text
* change and/or selection change and call nsIWidget::NotifyIMEOfTextChange()
* and/or nsIWidget::NotifyIME(NOTIFY_IME_OF_SELECTION_CHANGE).
* Please note that the text change observing cost is very expensive especially
* on an HTML editor has focus.
* If the IME implementation on a particular platform doesn't care about
* NotifyIMEOfTextChange and NotifyIME(NOTIFY_IME_OF_SELECTION_CHANGE), they
* should set mWantUpdates to false to avoid the cost.
* NotifyIMEOfTextChange() and/or NotifyIME(NOTIFY_IME_OF_SELECTION_CHANGE),
* they should set mWantUpdates to NOTIFY_NOTHING to avoid the cost.
*
* If mWantHints is true, PuppetWidget will forward the content of text fields
* to the chrome process to be cached. This way we return the cached content
@ -208,15 +209,25 @@ enum nsTopLevelWidgetZPlacement { // for PlaceBehind()
*/
struct nsIMEUpdatePreference {
typedef int8_t Notifications;
enum
{
NOTIFY_NOTHING = 0x0000,
NOTIFY_SELECTION_CHANGE = 0x0001,
NOTIFY_TEXT_CHANGE = 0x0002
};
nsIMEUpdatePreference()
: mWantUpdates(false), mWantHints(false)
: mWantUpdates(NOTIFY_NOTHING), mWantHints(false)
{
}
nsIMEUpdatePreference(bool aWantUpdates, bool aWantHints)
nsIMEUpdatePreference(Notifications aWantUpdates, bool aWantHints)
: mWantUpdates(aWantUpdates), mWantHints(aWantHints)
{
}
bool mWantUpdates;
Notifications mWantUpdates;
bool mWantHints;
};

View File

@ -221,7 +221,7 @@ IMEHandler::GetUpdatePreference()
}
#endif //NS_ENABLE_TSF
return nsIMEUpdatePreference(false, false);
return nsIMEUpdatePreference();
}
// static

View File

@ -2920,13 +2920,16 @@ nsTextStore::OnFocusChange(bool aGotFocus,
nsIMEUpdatePreference
nsTextStore::GetIMEUpdatePreference()
{
bool hasFocus = false;
int8_t notifications = nsIMEUpdatePreference::NOTIFY_NOTHING;
if (sTsfThreadMgr && sTsfTextStore && sTsfTextStore->mDocumentMgr) {
nsRefPtr<ITfDocumentMgr> docMgr;
sTsfThreadMgr->GetFocus(getter_AddRefs(docMgr));
hasFocus = (docMgr == sTsfTextStore->mDocumentMgr);
if (docMgr == sTsfTextStore->mDocumentMgr) {
notifications = (nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE |
nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE);
}
}
return nsIMEUpdatePreference(hasFocus, false);
return nsIMEUpdatePreference(notifications, false);
}
nsresult

View File

@ -456,13 +456,15 @@ PuppetWidget::NotifyIMEOfFocusChange(bool aFocus)
}
uint32_t chromeSeqno;
mIMEPreference.mWantUpdates = false;
mIMEPreference.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING;
mIMEPreference.mWantHints = false;
if (!mTabChild->SendNotifyIMEFocus(aFocus, &mIMEPreference, &chromeSeqno))
return NS_ERROR_FAILURE;
if (aFocus) {
if (mIMEPreference.mWantUpdates && mIMEPreference.mWantHints) {
if ((mIMEPreference.mWantUpdates &
nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE) &&
mIMEPreference.mWantHints) {
NotifyIMEOfSelectionChange(); // Update selection
}
} else {
@ -500,7 +502,7 @@ PuppetWidget::NotifyIMEOfTextChange(uint32_t aStart,
mTabChild->SendNotifyIMETextHint(queryEvent.mReply.mString);
}
}
if (mIMEPreference.mWantUpdates) {
if (mIMEPreference.mWantUpdates & nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE) {
mTabChild->SendNotifyIMETextChange(aStart, aEnd, aNewEnd);
}
return NS_OK;
@ -516,7 +518,8 @@ PuppetWidget::NotifyIMEOfSelectionChange()
if (!mTabChild)
return NS_ERROR_FAILURE;
if (mIMEPreference.mWantUpdates) {
if (mIMEPreference.mWantUpdates &
nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE) {
nsEventStatus status;
nsQueryContentEvent queryEvent(true, NS_QUERY_SELECTED_TEXT, this);
InitEvent(queryEvent, nullptr);

View File

@ -181,7 +181,7 @@ public:
virtual bool ComputeShouldAccelerate(bool aDefault);
NS_IMETHOD GetToggledKeyState(uint32_t aKeyCode, bool* aLEDState) { return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHOD NotifyIMEOfTextChange(uint32_t aStart, uint32_t aOldEnd, uint32_t aNewEnd) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; }
virtual nsIMEUpdatePreference GetIMEUpdatePreference() { return nsIMEUpdatePreference(false, false); }
virtual nsIMEUpdatePreference GetIMEUpdatePreference() MOZ_OVERRIDE { return nsIMEUpdatePreference(); }
NS_IMETHOD OnDefaultButtonLoaded(const nsIntRect &aButtonRect) { return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHOD OverrideSystemMouseScrollSpeed(double aOriginalDeltaX,
double aOriginalDeltaY,

View File

@ -85,8 +85,6 @@
#define NS_APP_INSTALL_CLEANUP_DIR "XPIClnupD" //location of xpicleanup.dat xpicleanup.exe
#define NS_APP_STORAGE_50_FILE "UStor" // sqlite database used as mozStorage profile db
#define NS_APP_INDEXEDDB_PARENT_DIR "indexedDBPDir"
#define NS_APP_PERMISSION_PARENT_DIR "permissionDBPDir"