Bug 987040 - Part 1: Implement mozbrowserselectionchange. r=vingtetun,ehsan,bugs. sr=bz

This commit is contained in:
Morris Tseng 2014-07-28 01:21:00 +02:00
parent 48b52013e5
commit bdc2515786
12 changed files with 220 additions and 27 deletions

View File

@ -346,6 +346,7 @@ var shell = {
window.addEventListener('sizemodechange', this);
window.addEventListener('unload', this);
this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
this.contentBrowser.addEventListener('mozbrowserselectionchange', this, true);
CustomEventManager.init();
WebappsHelper.init();
@ -372,6 +373,7 @@ var shell = {
window.removeEventListener('mozfullscreenchange', this);
window.removeEventListener('sizemodechange', this);
this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
this.contentBrowser.removeEventListener('mozbrowserselectionchange', this, true);
ppmm.removeMessageListener("content-handler", this);
UserAgentOverrides.uninit();
@ -514,6 +516,30 @@ var shell = {
this.notifyContentStart();
break;
case 'mozbrowserselectionchange':
// The mozbrowserselectionchange event, may have crossed the chrome-content boundary.
// This event always dispatch to shell.js. But the offset we got from this event is
// based on tab's coordinate. So get the actual offsets between shell and evt.target.
let elt = evt.target;
let win = elt.ownerDocument.defaultView;
let offsetX = win.mozInnerScreenX;
let offsetY = win.mozInnerScreenY;
let rect = elt.getBoundingClientRect();
offsetX += rect.left;
offsetY += rect.top;
let data = evt.detail;
data.offsetX = offsetX;
data.offsetY = offsetY;
DoCommandHelper.setEvent(evt);
shell.sendChromeEvent({
type: 'selectionchange',
detail: data,
});
break;
case 'MozApplicationManifest':
try {
if (!Services.prefs.getBoolPref('browser.cache.offline.enable'))
@ -713,6 +739,23 @@ var CustomEventManager = {
case 'inputmethod-update-layouts':
KeyboardHelper.handleEvent(detail);
break;
case 'do-command':
DoCommandHelper.handleEvent(detail.cmd);
break;
}
}
}
let DoCommandHelper = {
_event: null,
setEvent: function docommand_setEvent(evt) {
this._event = evt;
},
handleEvent: function docommand_handleEvent(cmd) {
if (this._event) {
shell.sendEvent(this._event.target, 'mozdocommand', { cmd: cmd });
this._event = null;
}
}
}

View File

@ -715,7 +715,7 @@ nsCopySupport::FireClipboardEvent(int32_t aType, int32_t aClipboardType, nsIPres
// Now that we have copied, update the clipboard commands. This should have
// the effect of updating the enabled state of the paste menu item.
if (doDefault || count) {
piWindow->UpdateCommands(NS_LITERAL_STRING("clipboard"));
piWindow->UpdateCommands(NS_LITERAL_STRING("clipboard"), nullptr, 0);
}
return doDefault;

View File

@ -676,7 +676,9 @@ protected:
*/
virtual ~nsTextInputListener();
nsresult UpdateTextInputCommands(const nsAString& commandsToUpdate);
nsresult UpdateTextInputCommands(const nsAString& commandsToUpdate,
nsISelection* sel = nullptr,
int16_t reason = 0);
protected:
@ -780,16 +782,18 @@ nsTextInputListener::NotifySelectionChanged(nsIDOMDocument* aDoc, nsISelection*
}
}
UpdateTextInputCommands(NS_LITERAL_STRING("selectionchange"), aSel, aReason);
// if the collapsed state did not change, don't fire notifications
if (collapsed == mSelectionWasCollapsed)
return NS_OK;
mSelectionWasCollapsed = collapsed;
if (!weakFrame.IsAlive() || !nsContentUtils::IsFocusedContent(mFrame->GetContent()))
return NS_OK;
return UpdateTextInputCommands(NS_LITERAL_STRING("select"));
return UpdateTextInputCommands(NS_LITERAL_STRING("select"), aSel, aReason);
}
// END nsIDOMSelectionListener
@ -930,7 +934,9 @@ nsTextInputListener::EditAction()
nsresult
nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate)
nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate,
nsISelection* sel,
int16_t reason)
{
nsIContent* content = mFrame->GetContent();
NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
@ -941,7 +947,7 @@ nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate)
nsPIDOMWindow *domWindow = doc->GetWindow();
NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
return domWindow->UpdateCommands(commandsToUpdate);
return domWindow->UpdateCommands(commandsToUpdate, sel, reason);
}
// END nsTextInputListener

View File

@ -923,7 +923,7 @@ nsFocusManager::WindowHidden(nsIDOMWindow* aWindow)
NotifyFocusStateChange(oldFocusedContent,
mFocusedWindow->ShouldShowFocusRing(),
false);
window->UpdateCommands(NS_LITERAL_STRING("focus"));
window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
if (presShell) {
SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell,
@ -1266,7 +1266,7 @@ nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags,
// update the commands even when inactive so that the attributes for that
// window are up to date.
if (allowFrameSwitch)
newWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
newWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
if (aFlags & FLAG_RAISE)
RaiseWindow(newRootWindow);
@ -1586,7 +1586,7 @@ nsFocusManager::Blur(nsPIDOMWindow* aWindowToClear,
// window, then this was a blur caused by the active window being lowered,
// so there is no need to update the commands
if (mActiveWindow)
window->UpdateCommands(NS_LITERAL_STRING("focus"));
window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
SendFocusOrBlurEvent(NS_BLUR_CONTENT, presShell,
content->GetCurrentDoc(), content, 1, false);
@ -1807,7 +1807,7 @@ nsFocusManager::Focus(nsPIDOMWindow* aWindow,
// commands
// XXXndeakin P2 someone could adjust the focus during the update
if (!aWindowRaised)
aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
SendFocusOrBlurEvent(NS_FOCUS_CONTENT, presShell,
aContent->GetCurrentDoc(),
@ -1817,7 +1817,7 @@ nsFocusManager::Focus(nsPIDOMWindow* aWindow,
IMEStateManager::OnChangeFocus(presContext, nullptr,
GetFocusMoveActionCause(aFlags));
if (!aWindowRaised) {
aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
}
}
}
@ -1842,7 +1842,7 @@ nsFocusManager::Focus(nsPIDOMWindow* aWindow,
GetFocusMoveActionCause(aFlags));
if (!aWindowRaised)
aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
}
// update the caret visibility and position to match the newly focused

View File

@ -199,6 +199,8 @@
#include "nsRefreshDriver.h"
#include "mozilla/dom/SelectionChangeEvent.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "nsLocation.h"
@ -9248,7 +9250,7 @@ public:
};
NS_IMETHODIMP
nsGlobalWindow::UpdateCommands(const nsAString& anAction)
nsGlobalWindow::UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason)
{
nsPIDOMWindow *rootWindow = nsGlobalWindow::GetPrivateRoot();
if (!rootWindow)
@ -9257,7 +9259,9 @@ nsGlobalWindow::UpdateCommands(const nsAString& anAction)
nsCOMPtr<nsIDOMXULDocument> xulDoc =
do_QueryInterface(rootWindow->GetExtantDoc());
// See if we contain a XUL document.
if (xulDoc) {
// selectionchange action is only used for mozbrowser, not for XUL. So we bypass
// XUL command dispatch if anAction is "selectionchange".
if (xulDoc && !anAction.EqualsLiteral("selectionchange")) {
// Retrieve the command dispatcher and call updateCommands on it.
nsCOMPtr<nsIDOMXULCommandDispatcher> xulCommandDispatcher;
xulDoc->GetCommandDispatcher(getter_AddRefs(xulCommandDispatcher));
@ -9267,6 +9271,29 @@ nsGlobalWindow::UpdateCommands(const nsAString& anAction)
}
}
if (mDoc && anAction.EqualsLiteral("selectionchange")) {
SelectionChangeEventInit init;
init.mBubbles = true;
if (aSel) {
nsCOMPtr<nsIDOMRange> range;
nsresult rv = aSel->GetRangeAt(0, getter_AddRefs(range));
if (NS_SUCCEEDED(rv) && range) {
nsRefPtr<nsRange> nsrange = static_cast<nsRange*>(range.get());
init.mBoundingClientRect = nsrange->GetBoundingClientRect(true);
range->ToString(init.mSelectedText);
init.mReason = aReason;
}
nsRefPtr<SelectionChangeEvent> event =
SelectionChangeEvent::Constructor(mDoc, NS_LITERAL_STRING("mozselectionchange"), init);
event->SetTrusted(true);
event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
bool ret;
mDoc->DispatchEvent(event, &ret);
}
}
return NS_OK;
}

View File

@ -108,6 +108,13 @@ const OBSERVED_EVENTS = [
'activity-done'
];
const COMMAND_MAP = {
'cut': 'cmd_cut',
'copy': 'cmd_copy',
'paste': 'cmd_paste',
'selectall': 'cmd_selectAll'
};
/**
* The BrowserElementChild implements one half of <iframe mozbrowser>.
* (The other half is, unsurprisingly, BrowserElementParent.)
@ -200,6 +207,10 @@ BrowserElementChild.prototype = {
/* useCapture = */ true,
/* wantsUntrusted = */ false);
addEventListener('mozselectionchange',
this._selectionChangeHandler.bind(this),
/* useCapture = */ false,
/* wantsUntrusted = */ false);
// This listens to unload events from our message manager, but /not/ from
// the |content| window. That's because the window's unload event doesn't
@ -237,7 +248,8 @@ BrowserElementChild.prototype = {
"exit-fullscreen": this._recvExitFullscreen.bind(this),
"activate-next-paint-listener": this._activateNextPaintListener.bind(this),
"set-input-method-active": this._recvSetInputMethodActive.bind(this),
"deactivate-next-paint-listener": this._deactivateNextPaintListener.bind(this)
"deactivate-next-paint-listener": this._deactivateNextPaintListener.bind(this),
"do-command": this._recvDoCommand
}
addMessageListener("browser-element-api:call", function(aMessage) {
@ -351,6 +363,15 @@ BrowserElementChild.prototype = {
}
},
_isCommandEnabled: function(cmd) {
let command = COMMAND_MAP[cmd];
if (!command) {
return false;
}
return docShell.isCommandEnabled(command);
},
/**
* Spin in a nested event loop until we receive a unblock-modal-prompt message for
* this window.
@ -589,6 +610,53 @@ BrowserElementChild.prototype = {
sendAsyncMsg('metachange', meta);
},
_selectionChangeHandler: function(e) {
let isMouseUp = e.reason & Ci.nsISelectionListener.MOUSEUP_REASON;
let isSelectAll = e.reason & Ci.nsISelectionListener.SELECTALL_REASON;
// When selectall happened, gecko will first collapse the range then
// select all. So we will receive two selection change events with
// SELECTALL_REASON. We filter first event by check the length of
// selectedText.
if (!(isMouseUp || (isSelectAll && e.selectedText.length > 0))) {
return;
}
e.stopPropagation();
let boundingClientRect = e.boundingClientRect;
let zoomFactor = content.screen.width / content.innerWidth;
let detail = {
rect: {
width: boundingClientRect.width,
height: boundingClientRect.height,
top: boundingClientRect.top,
bottom: boundingClientRect.bottom,
left: boundingClientRect.left,
right: boundingClientRect.right,
},
commands: {
canSelectAll: this._isCommandEnabled("selectall"),
canCut: this._isCommandEnabled("cut"),
canCopy: this._isCommandEnabled("copy"),
canPaste: this._isCommandEnabled("paste"),
},
zoomFactor: zoomFactor,
};
// Get correct geometry information if we have nested <iframe mozbrowser>
let currentWindow = e.target.defaultView;
while (currentWindow.realFrameElement) {
let currentRect = currentWindow.realFrameElement.getBoundingClientRect();
detail.rect.top += currentRect.top;
detail.rect.bottom += currentRect.top;
detail.rect.left += currentRect.left;
detail.rect.right += currentRect.left;
currentWindow = currentWindow.realFrameElement.ownerDocument.defaultView;
}
sendAsyncMsg("selectionchange", detail);
},
_themeColorChangedHandler: function(eventType, target) {
let meta = {
name: 'theme-color',
@ -1031,6 +1099,12 @@ BrowserElementChild.prototype = {
webNav.stop(webNav.STOP_NETWORK);
},
_recvDoCommand: function(data) {
if (this._isCommandEnabled(data.json.command)) {
docShell.doCommand(COMMAND_MAP[data.json.command]);
}
},
_recvSetInputMethodActive: function(data) {
let msgData = { id: data.json.id };
if (!this._isContentWindowCreated) {

View File

@ -159,6 +159,11 @@ function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
/* wantsUntrusted = */ false);
}
this._frameElement.addEventListener('mozdocommand',
this._doCommandHandler.bind(this),
/* useCapture = */ false,
/* wantsUntrusted = */ false);
this._window._browserElementParents.set(this, null);
// Insert ourself into the prompt service.
@ -247,7 +252,8 @@ BrowserElementParent.prototype = {
"exit-fullscreen": this._exitFullscreen,
"got-visible": this._gotDOMRequestResult,
"visibilitychange": this._childVisibilityChange,
"got-set-input-method-active": this._gotDOMRequestResult
"got-set-input-method-active": this._gotDOMRequestResult,
"selectionchange": this._handleSelectionChange
};
this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
@ -449,6 +455,17 @@ BrowserElementParent.prototype = {
}
},
_handleSelectionChange: function(data) {
let evt = this._createEvent('selectionchange', data.json,
/* cancelable = */ false);
this._frameElement.dispatchEvent(evt);
},
_doCommandHandler: function(e) {
e.stopPropagation();
this._sendAsyncMsg('do-command', { command: e.detail.cmd });
},
_createEvent: function(evtName, detail, cancelable) {
// This will have to change if we ever want to send a CustomEvent with null
// detail. For now, it's OK.

View File

@ -23,7 +23,7 @@ interface nsIVariant;
* @see <http://www.whatwg.org/html/#window>
*/
[scriptable, uuid(c3ff0328-6c47-4e64-a22f-ac221959e258)]
[scriptable, uuid(ed7cc4e4-cf5b-42af-9c2e-8df074a01470)]
interface nsIDOMWindow : nsISupports
{
// the current browsing context
@ -426,7 +426,9 @@ interface nsIDOMWindow : nsISupports
in nsISupports aExtraArgument);
// XXX Should this be in nsIDOMChromeWindow?
void updateCommands(in DOMString action);
void updateCommands(in DOMString action,
[optional] in nsISelection sel,
[optional] in short reason);
/* Find in page.
* @param str: the search pattern

View File

@ -0,0 +1,19 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
dictionary SelectionChangeEventInit : EventInit {
DOMString selectedText = "";
DOMRectReadOnly? boundingClientRect = null;
short reason = 0;
};
[Constructor(DOMString type, optional SelectionChangeEventInit eventInit),
ChromeOnly]
interface SelectionChangeEvent : Event {
readonly attribute DOMString selectedText;
readonly attribute DOMRectReadOnly? boundingClientRect;
readonly attribute short reason;
};

View File

@ -303,7 +303,9 @@ partial interface Window {
[Throws, ChromeOnly] void home();
// XXX Should this be in nsIDOMChromeWindow?
void updateCommands(DOMString action);
void updateCommands(DOMString action,
optional Selection? sel = null,
optional short reason = 0);
/* Find in page.
* @param str: the search pattern

View File

@ -668,6 +668,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
'RTCPeerConnectionIceEvent.webidl',
'RTCPeerConnectionIdentityErrorEvent.webidl',
'RTCPeerConnectionIdentityEvent.webidl',
'SelectionChangeEvent.webidl',
'SmartCardEvent.webidl',
'StyleRuleChangeEvent.webidl',
'StyleSheetApplicableStateChangeEvent.webidl',

View File

@ -3529,7 +3529,7 @@ NS_IMETHODIMP nsDocumentViewer::GetInImage(bool* aInImage)
return NS_OK;
}
NS_IMETHODIMP nsDocViewerSelectionListener::NotifySelectionChanged(nsIDOMDocument *, nsISelection *, int16_t)
NS_IMETHODIMP nsDocViewerSelectionListener::NotifySelectionChanged(nsIDOMDocument *, nsISelection *, int16_t aReason)
{
NS_ASSERTION(mDocViewer, "Should have doc viewer!");
@ -3538,6 +3538,12 @@ NS_IMETHODIMP nsDocViewerSelectionListener::NotifySelectionChanged(nsIDOMDocumen
nsresult rv = mDocViewer->GetDocumentSelection(getter_AddRefs(selection));
if (NS_FAILED(rv)) return rv;
nsIDocument* theDoc = mDocViewer->GetDocument();
if (!theDoc) return NS_ERROR_FAILURE;
nsCOMPtr<nsPIDOMWindow> domWindow = theDoc->GetWindow();
if (!domWindow) return NS_ERROR_FAILURE;
bool selectionCollapsed;
selection->GetIsCollapsed(&selectionCollapsed);
// we only call UpdateCommands when the selection changes from collapsed
@ -3545,17 +3551,13 @@ NS_IMETHODIMP nsDocViewerSelectionListener::NotifySelectionChanged(nsIDOMDocumen
// for simple selection changes, but that would be expenseive.
if (!mGotSelectionState || mSelectionWasCollapsed != selectionCollapsed)
{
nsIDocument* theDoc = mDocViewer->GetDocument();
if (!theDoc) return NS_ERROR_FAILURE;
nsPIDOMWindow *domWindow = theDoc->GetWindow();
if (!domWindow) return NS_ERROR_FAILURE;
domWindow->UpdateCommands(NS_LITERAL_STRING("select"));
domWindow->UpdateCommands(NS_LITERAL_STRING("select"), selection, aReason);
mGotSelectionState = true;
mSelectionWasCollapsed = selectionCollapsed;
}
domWindow->UpdateCommands(NS_LITERAL_STRING("selectionchange"), selection, aReason);
return NS_OK;
}