diff --git a/mozilla/extensions/spatialnavigation/Makefile.in b/mozilla/extensions/spatialnavigation/Makefile.in new file mode 100755 index 00000000000..aef7f24be5b --- /dev/null +++ b/mozilla/extensions/spatialnavigation/Makefile.in @@ -0,0 +1,13 @@ +DEPTH=../.. +topsrcdir=@top_srcdir@ +srcdir=@srcdir@ +VPATH=@srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = snav + +DIRS = public src + +include $(topsrcdir)/config/rules.mk + diff --git a/mozilla/extensions/spatialnavigation/public/Makefile.in b/mozilla/extensions/spatialnavigation/public/Makefile.in new file mode 100755 index 00000000000..fff8b626196 --- /dev/null +++ b/mozilla/extensions/spatialnavigation/public/Makefile.in @@ -0,0 +1,51 @@ +# ***** 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 Spatial Navigation +# +# The Initial Developer of the Original Code is Douglas F. Turner II +# Portions created by the Initial Developer are Copyright (C) 2004 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the terms of +# either 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 ***** + + +DEPTH=../../.. +topsrcdir=@top_srcdir@ +srcdir=@srcdir@ +VPATH=@srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE=snav +XPIDL_MODULE=snav + +XPIDLSRCS = nsISpatialNavigation.idl \ + $(NULL) + +include $(topsrcdir)/config/rules.mk + diff --git a/mozilla/extensions/spatialnavigation/public/nsISpatialNavigation.idl b/mozilla/extensions/spatialnavigation/public/nsISpatialNavigation.idl new file mode 100755 index 00000000000..c2285dd7c01 --- /dev/null +++ b/mozilla/extensions/spatialnavigation/public/nsISpatialNavigation.idl @@ -0,0 +1,54 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape 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/NPL/ + * + * 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 Spatial Navigation + * + * The Initial Developer of the Original Code is + * Douglas F. Turner II + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either 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 NPL, 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 NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsISupports.idl" + +interface nsIDOMWindow; + +[scriptable, uuid(ef7cb437-9eb3-47c7-9d89-71679bfefcc1)] +interface nsISpatialNavigation: nsISupports +{ + void init(in nsIDOMWindow aWindow); + void shutdown(); + + void up(); + void down(); + void left(); + void right(); + + readonly attribute nsIDOMWindow attachedWindow; +}; diff --git a/mozilla/extensions/spatialnavigation/src/Makefile.in b/mozilla/extensions/spatialnavigation/src/Makefile.in new file mode 100755 index 00000000000..bee01384573 --- /dev/null +++ b/mozilla/extensions/spatialnavigation/src/Makefile.in @@ -0,0 +1,82 @@ +# ***** 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 Spatial Navigation +# +# The Initial Developer of the Original Code is Douglas F. Turner II +# Portions created by the Initial Developer are Copyright (C) 2004 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the terms of +# either 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 ***** + + +DEPTH=../../.. +topsrcdir=@top_srcdir@ +srcdir=@srcdir@ +VPATH=@srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = snav +LIBRARY_NAME = snav +IS_COMPONENT = 1 + +REQUIRES = xpcom \ + string \ + pref \ + js \ + dom \ + dom_base \ + gfx \ + layout \ + content \ + widget \ + locale \ + necko \ + docshell \ + view \ + webshell \ + windowwatcher \ + webbrwsr \ + $(NULL) + +CPPSRCS= \ + nsSpatialNavigation.cpp \ + nsSpatialNavigationUtils.cpp \ + nsSpatialNavigationService.cpp \ + $(NULL) + +DEFINES = -DMOZILLA_INTERNAL_API + +EXTRA_DSO_LIBS = gkgfx + +EXTRA_DSO_LDOPTS += $(LIBS_DIR) \ + $(EXTRA_DSO_LIBS) \ + $(MOZ_COMPONENT_LIBS)\ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/mozilla/extensions/spatialnavigation/src/nsSpatialNavigation.cpp b/mozilla/extensions/spatialnavigation/src/nsSpatialNavigation.cpp new file mode 100755 index 00000000000..a9047347df7 --- /dev/null +++ b/mozilla/extensions/spatialnavigation/src/nsSpatialNavigation.cpp @@ -0,0 +1,737 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape 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/NPL/ + * + * 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 Spatial Navigation + * + * The Initial Developer of the Original Code is + * Douglas F. Turner II + * Portions created by the Initial Developer are Copyright (C) 2004-2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either 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 NPL, 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 NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsSpatialNavigationPrivate.h" + +PRInt32 gRectFudge = 20; +PRInt32 gDirectionalBias = 3; + +NS_INTERFACE_MAP_BEGIN(nsSpatialNavigation) + NS_INTERFACE_MAP_ENTRY(nsISpatialNavigation) + NS_INTERFACE_MAP_ENTRY(nsIDOMKeyListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMKeyListener) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsSpatialNavigation) +NS_IMPL_RELEASE(nsSpatialNavigation) + + +nsSpatialNavigation::nsSpatialNavigation(nsSpatialNavigationService* aService) +{ + NS_ASSERTION(aService, "Should not create this object without a valid service"); + + mService = aService; // back pointer -- no reference + + mNavigationFramesState = PR_FALSE; +} + +nsSpatialNavigation::~nsSpatialNavigation() +{ +} + +NS_IMETHODIMP +nsSpatialNavigation::HandleEvent(nsIDOMEvent* aEvent) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSpatialNavigation::KeyDown(nsIDOMEvent* aEvent) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSpatialNavigation::KeyUp(nsIDOMEvent* aEvent) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSpatialNavigation::KeyPress(nsIDOMEvent* aEvent) +{ + + // check to see if we are in a text field. + + if (!mService->mIgnoreTextFields) + { + // based on nsTypeAheadFind. + + //nsEvent should be renamed. + nsCOMPtr nsEvent = do_QueryInterface(aEvent); + if (!nsEvent) + return NS_ERROR_FAILURE; + + nsCOMPtr domEventTarget; + nsEvent->GetOriginalTarget(getter_AddRefs(domEventTarget)); + + nsCOMPtr targetContent = do_QueryInterface(domEventTarget); + + if (targetContent->IsContentOfType(nsIContent::eHTML_FORM_CONTROL)) + { + nsCOMPtr formControl(do_QueryInterface(targetContent)); + PRInt32 controlType = formControl->GetType(); + + if (controlType == NS_FORM_TEXTAREA || + controlType == NS_FORM_INPUT_TEXT || + controlType == NS_FORM_INPUT_PASSWORD || + controlType == NS_FORM_INPUT_FILE) + { + return NS_OK; + } + } + else if (targetContent->IsContentOfType(nsIContent::eHTML)) + { + // Test for isindex, a deprecated kind of text field. We're using a string + // compare because is not considered a form control, so it does + // not support nsIFormControl or eHTML_FORM_CONTROL, and it's not worth + // having a table of atoms just for it. + + if (isContentOfType(targetContent, "isindex")) + return NS_OK; + } + } + + PRUint32 keyCode; + PRBool isModifier; + nsCOMPtr keyEvent(do_QueryInterface(aEvent)); + + if (!keyEvent) + return NS_ERROR_FAILURE; + + if (NS_FAILED(keyEvent->GetKeyCode(&keyCode))) + return NS_ERROR_FAILURE; + + // figure out what modifier to use + + /************************************************ + Value of the keyCodeModifier is + + SHIFT = 0x00100000 + CONTROL = 0x00001100 + ALT = 0x00000012 + *************************************************/ + + + if (mService->mKeyCodeModifier & 0x00100000) + { + if (NS_FAILED(keyEvent->GetShiftKey(&isModifier))) + return NS_ERROR_FAILURE; + if (!isModifier) + return NS_OK; + } + + if (mService->mKeyCodeModifier & 0x00001100) + { + if (NS_FAILED(keyEvent->GetCtrlKey(&isModifier))) + return NS_ERROR_FAILURE; + if (!isModifier) + return NS_OK; + } + + if (mService->mKeyCodeModifier & 0x00000012) + { + if (NS_FAILED(keyEvent->GetAltKey(&isModifier))) + return NS_ERROR_FAILURE; + if (!isModifier) + return NS_OK; + } + + + if (keyCode == mService->mKeyCodeLeft) + { + // We're using this key, no one else should + aEvent->StopPropagation(); + aEvent->PreventDefault(); + return Left(); + } + + if (keyCode == mService->mKeyCodeRight) + { + aEvent->StopPropagation(); + aEvent->PreventDefault(); + return Right(); + } + + if (keyCode == mService->mKeyCodeUp) + { + aEvent->StopPropagation(); + aEvent->PreventDefault(); + return Up(); + } + + if (keyCode == mService->mKeyCodeDown) + { + aEvent->StopPropagation(); // We're using this key, no one else should + aEvent->PreventDefault(); + return Down(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSpatialNavigation::Init(nsIDOMWindow *aWindow) +{ + mTopWindow = aWindow; + + nsCOMPtr target; + nsCOMPtr systemGroup; + nsresult rv = getEventTargetFromWindow(aWindow, getter_AddRefs(target), getter_AddRefs(systemGroup)); + if (NS_FAILED(rv)) + return rv; + + target->AddGroupedEventListener(NS_LITERAL_STRING("keypress"), + NS_STATIC_CAST(nsIDOMKeyListener*, this), + PR_FALSE, + systemGroup); + + return NS_OK; +} + +NS_IMETHODIMP +nsSpatialNavigation::Shutdown() +{ + nsCOMPtr target; + nsCOMPtr systemGroup; + nsresult rv = getEventTargetFromWindow(mTopWindow, getter_AddRefs(target), getter_AddRefs(systemGroup)); + if (NS_FAILED(rv)) + return rv; + + target->RemoveGroupedEventListener(NS_LITERAL_STRING("keypress"), + NS_STATIC_CAST(nsIDOMKeyListener*, this), + PR_FALSE, + systemGroup); + mTopWindow = nsnull; + + return NS_OK; +} + +NS_IMETHODIMP +nsSpatialNavigation::Up() +{ + return handleMove(eNavUp); +} + +NS_IMETHODIMP +nsSpatialNavigation::Down() +{ + return handleMove(eNavDown); +} + +NS_IMETHODIMP +nsSpatialNavigation::Left() +{ + return handleMove(eNavLeft); +} + +NS_IMETHODIMP +nsSpatialNavigation::Right() +{ + return handleMove(eNavRight); +} + +NS_IMETHODIMP +nsSpatialNavigation::GetAttachedWindow(nsIDOMWindow * *aAttachedWindow) +{ + NS_IF_ADDREF(*aAttachedWindow = mTopWindow); + return NS_OK; +} + +void DoTraversal(int aDirection, + nsIBidirectionalEnumerator* aFrameTraversal, + nsIFrame* aFocusedFrame, + nsRect& aFocusedRect, + PRBool isAREA, + PRBool focusDocuments, + nsPresContext* aPresContext, + PRInt64* aDSoFar, + nsIContent** aCurrentContent) +{ + + *aCurrentContent = nsnull; + + // There are some rects with zero width or height. + // calculating distance from a non point is troublesome. + // we will fix this up here by setting such a point to at + // least 1 px. + + if (aFocusedRect.width == 0) { + aFocusedRect.width = 1; + } + + if (aFocusedRect.height == 0) { + aFocusedRect.height = 1; + } + + nsIFrame* frame; + nsRect frameRect; + + PRInt64 d; + + while (1) + { + aFrameTraversal->Next(); + + nsISupports* currentItem; + aFrameTraversal->CurrentItem(¤tItem); + frame = NS_STATIC_CAST(nsIFrame*, currentItem); + + if (!frame) + break; + + printf("got frame %x\n", frame); + + // So, here we want to make sure that the frame that we + // nav to isn't part of the flow of the currently + // focused frame + + if (!isAREA) + { + nsIFrame* flowFrame = aFocusedFrame; + PRBool currentFrameIsFocusedFrame = PR_FALSE; + while (flowFrame) + { + if (flowFrame == frame) + { + currentFrameIsFocusedFrame = PR_TRUE; + break; + } + flowFrame = flowFrame->GetNextInFlow(); + } + if (currentFrameIsFocusedFrame) + continue; + } + + //////////////////////////////////////////////////////////////////////////////////// + // a special case for area's which are not enumerated by the nsIFrameTraversal + //////////////////////////////////////////////////////////////////////////////////// + if (isMap(frame)) + { + nsIContent* c = frame->GetContent(); + nsCOMPtr element = do_QueryInterface(c); + if (!element) + continue; + + nsCOMPtr mapAreas; + element->GetAreas(getter_AddRefs(mapAreas)); + if (!mapAreas) + continue; + + PRUint32 length; + mapAreas->GetLength(&length); + + for (PRUint32 i = 0; i < length; i++) + { + nsCOMPtr domNode; + mapAreas->Item(i,getter_AddRefs(domNode)); + + nsCOMPtr e = do_QueryInterface(domNode); + nsCOMPtr content= do_QueryInterface(domNode); + getFrameForContent(content, &frame); + + getRectOfAreaElement(frame, e, &frameRect); + + if (!isRectInDirection(aDirection, aFocusedRect, frameRect)) + continue; + + d = spatialDistance(aDirection, aFocusedRect, frameRect); + + if ((*aDSoFar) > d) + { + (*aDSoFar) = d; + NS_IF_RELEASE(*aCurrentContent); + NS_ADDREF(*aCurrentContent = content); + } + } + continue; + } + //////////////////////////////////////////////////////////////////////////////////// + + // we don't want to worry about the same frame if we + // aren't an area + if (frame == aFocusedFrame || + (aFocusedFrame && (frame->GetContent() == aFocusedFrame->GetContent()))) + continue; + + // make sure the frame is targetable for focus + if (!isTargetable(focusDocuments, frame)) + continue; + + // RECT !! + frameRect = makeRectRelativeToGlobalView(frame); + + // no frames without size and be navigated to + // successfully. + if (frameRect.width == 0 || frameRect.height == 0) + continue; + + // deflate the rect to avoid overlapping with other + // rects. + frameRect.Deflate(gRectFudge, gRectFudge); + + if (!isRectInDirection(aDirection, aFocusedRect, frameRect)) + continue; + + d = spatialDistance(aDirection, aFocusedRect, frameRect); + + if ((*aDSoFar) >= d) + { +#ifdef DEBUG_dougt + if (d == 0) + { + printf("there is overlapping content;\n"); + } +#endif + (*aDSoFar) = d; + NS_IF_RELEASE(*aCurrentContent); + NS_ADDREF(*aCurrentContent = frame->GetContent()); + } + } +} + +inline void centerRect(int aDirection, nsRect& aRect) +{ + if (aDirection == eNavLeft) + aRect.x = 1000000; + else if (aDirection == eNavRight) + aRect.x = 0; + else if (aDirection == eNavUp) + aRect.y = 1000000; + else + aRect.y = 0; + + aRect.height = 1; + aRect.width = 1; +} + +nsresult +nsSpatialNavigation::getContentInDirection(int aDirection, + nsPresContext* aPresContext, + nsRect& aFocusedRect, + nsIFrame* aFocusedFrame, + PRBool aIsAREA, + PRBool aFocusDocuments, + nsIContent** aContent) +{ + + // Check to see if we should decend into subdoc + nsIContent* subFrameContent = aFocusedFrame->GetContent(); + nsCOMPtr hhElement = do_QueryInterface(subFrameContent); + nsCOMPtr iFrameElement = do_QueryInterface(subFrameContent); + + if ( (hhElement || iFrameElement) && mNavigationFramesState) + { + aPresContext = getPresContext(subFrameContent); + centerRect(aDirection, aFocusedRect); + } + + + nsCOMPtr frameTraversal; + nsresult result = createFrameTraversal(FOCUS, aPresContext, getter_AddRefs(frameTraversal)); + if (NS_FAILED(result)) + return result; + + nsCOMPtr currentContent; + PRInt64 currentDistance = LL_MaxInt(); + + DoTraversal(aDirection, + frameTraversal, + aFocusedFrame, + aFocusedRect, + aIsAREA, + aFocusDocuments, + aPresContext, + ¤tDistance, + aContent); + + + if ( (hhElement || iFrameElement) && mNavigationFramesState) + { + mNavigationFramesState = PR_FALSE; + } + + return NS_OK; +} + + +nsresult +nsSpatialNavigation::handleMove(int direction) +{ + PRUint32 type = FOCUS; + + if (!mService->mEnabled) + return NS_OK; + + nsCOMPtr focusedContent; + getFocusedContent(direction, getter_AddRefs(focusedContent)); + + if (!focusedContent) + return NS_OK; + + nsPresContext* presContext = getPresContext(focusedContent); + if(!presContext) + return NS_ERROR_NULL_POINTER; + + nsIFrame* focusedFrame; + getFrameForContent(focusedContent, &focusedFrame); + + nsRect focusedRect; + PRBool isAREA = isArea(focusedContent); + if (!isAREA) + { + // RECT !! + focusedRect = makeRectRelativeToGlobalView(focusedFrame); + + // deflate the rect to avoid overlapping with other + // rects. + focusedRect.Deflate(gRectFudge, gRectFudge); + } + else + { + nsCOMPtr e = do_QueryInterface(focusedContent); + getRectOfAreaElement(focusedFrame, e, &focusedRect); + } + + nsCOMPtr c; + getContentInDirection(direction, presContext, focusedRect, focusedFrame, PR_FALSE, isAREA, getter_AddRefs(c)); + + if (c) { + setFocusedContent(c); + return NS_OK; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////// + // do it all again at the parent document + /////////////////////////////////////////////////////////////////////////////////////////////////// + + { + nsCOMPtr contentWindow = getContentWindow(); + if (!contentWindow) + return NS_OK; + + nsCOMPtr domDoc; + contentWindow->GetDocument(getter_AddRefs(domDoc)); + nsCOMPtr doc = do_QueryInterface(domDoc); + + nsIPresShell *shell = doc->GetShellAt(0); + if (!shell) return NS_OK; + + presContext = shell->GetPresContext(); + + nsIFrame* parentFrame = shell->GetRootFrame(); + + nsCOMPtr subdoc = focusedContent->GetDocument(); + if (!subdoc) return NS_OK; + + nsCOMPtr subdomdoc = do_QueryInterface(subdoc); + + nsCOMPtr domWindowInternal; + GetWindowFromDocument(subdomdoc, getter_AddRefs(domWindowInternal)); + if (!domWindowInternal) return NS_OK; + + nsCOMPtr domWindowInternal2 = domWindowInternal; + domWindowInternal2->GetOpener(getter_AddRefs(domWindowInternal)); + if (!domWindowInternal) + domWindowInternal = domWindowInternal2; + + nsCOMPtr subdocWindow = do_QueryInterface(domWindowInternal); + if (!subdocWindow) return NS_OK; + + subdocWindow->GetDocument(getter_AddRefs(subdomdoc)); + if (!subdoc) return NS_OK; + + nsIPresShell *subdocShell = subdoc->GetShellAt(0); + if (!subdocShell) return NS_OK; + + nsPresContext *subdocPresContext = subdocShell->GetPresContext(); + + nsIFrame* subdocFrame = subdocShell->GetRootFrame(); + + nsRect subdocRect = subdocFrame->GetRect(); + + nsPoint frame_offset = subdocFrame->GetOffsetToExternal(parentFrame); + + subdocRect.x = frame_offset.x; + subdocRect.y = frame_offset.y; + + getContentInDirection(direction, presContext, subdocRect, subdocFrame, PR_TRUE, PR_FALSE, getter_AddRefs(c)); + } + + if (c) { + nsCOMPtr subdocContent; + getContentFromFrame(c, getter_AddRefs(subdocContent)); + + if (subdocContent) { + mNavigationFramesState = PR_TRUE; + setFocusedContent(c); + return NS_OK; + } + + setFocusedContent(c); + return NS_OK; + } + + // if everything fails, default is to move the focus just as if the user hit tab. + // presContext->EventStateManager()->ShiftFocus(PR_TRUE, focusedContent); + + /////////////////////////////////////////////////////////////////////////////////////////////////// + + return NS_OK; + +} + +void +nsSpatialNavigation::getFocusedContent(int direction, nsIContent** aContent) +{ + *aContent = nsnull; + + nsCOMPtr contentWindow = getContentWindow(); + if (!contentWindow) + return; + + nsCOMPtr privateWindow = do_QueryInterface(contentWindow); + nsIFocusController *focusController = privateWindow->GetRootFocusController(); + + if (!focusController) + return; + + nsCOMPtr element; + focusController->GetFocusedElement(getter_AddRefs(element)); + + if (element) + { + nsCOMPtr content = do_QueryInterface(element); + NS_IF_ADDREF(*aContent = content); + return; + } + + //xxxx should/can we prevent it from going into chrome??? + if (direction == eNavLeft || direction == eNavUp) + focusController->MoveFocus(PR_FALSE, nsnull); + else + focusController->MoveFocus(PR_TRUE, nsnull); + + // so there is no focused content -- lets make some up, hightlight it and return. + focusController->GetFocusedElement(getter_AddRefs(element)); +} + +void +nsSpatialNavigation::setFocusedContent(nsIContent* c) +{ + if (!c) + return; + + nsCOMPtr subdocContent; + getContentFromFrame(c, getter_AddRefs(subdocContent)); + + if (subdocContent) { + c = subdocContent; + } + + nsIContent* currentContent = c; + nsCOMPtr element = do_QueryInterface(currentContent); + nsCOMPtr node(do_QueryInterface(element)); + + nsCOMPtr contentWindow; + if (mService->mDisableJSWhenFocusing) + contentWindow = getContentWindow(); + + DisableJSScope foopy (contentWindow); + + //#ifdef OLDER_LAYOUT + nsPresContext* presContext = getPresContext(c); + + nsIFrame* frame = nsnull; + nsIPresShell *presShell = presContext->PresShell(); + presShell->GetPrimaryFrameFor(c, &frame); + + if (frame) { + presContext->EventStateManager()->SetContentState(c, NS_EVENT_STATE_FOCUS); + + presShell->ScrollFrameIntoView(frame, + NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE, + NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE); + + presContext->EventStateManager()->MoveCaretToFocus(); + } + + //#else + nsCOMPtr nsElement = do_QueryInterface(element); + if (nsElement) + nsElement->Focus(); + //#endif + +} + + +nsIDOMWindow* +nsSpatialNavigation::getContentWindow() +{ + + nsIDOMWindow* resultWindow = nsnull; + + nsCOMPtr window = do_QueryInterface(mTopWindow); + nsIDOMWindowInternal *rootWindow = window->GetPrivateRoot(); + + nsCOMPtr windowContent; + rootWindow->GetContent(getter_AddRefs(windowContent)); + + if (!windowContent) + return nsnull; + + NS_ADDREF(resultWindow = windowContent); + return resultWindow; +} + + +nsPresContext* +nsSpatialNavigation::getPresContext(nsIContent* content) +{ + if (!content) return nsnull; + + nsCOMPtr doc = content->GetDocument(); + if (!doc) return nsnull; + + // the only case where there could be more shells in printpreview + nsIPresShell *shell = doc->GetShellAt(0); + if (!shell) return nsnull; + + nsPresContext *presContext = shell->GetPresContext(); + return presContext; +} + + diff --git a/mozilla/extensions/spatialnavigation/src/nsSpatialNavigationPrivate.h b/mozilla/extensions/spatialnavigation/src/nsSpatialNavigationPrivate.h new file mode 100755 index 00000000000..ed3f8c8c5e7 --- /dev/null +++ b/mozilla/extensions/spatialnavigation/src/nsSpatialNavigationPrivate.h @@ -0,0 +1,273 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape 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/NPL/ + * + * 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 Spatial Navigation + * + * The Initial Developer of the Original Code is + * Douglas F. Turner II + * Portions created by the Initial Developer are Copyright (C) 2004-2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either 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 NPL, 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 NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "memory.h" +#include "stdlib.h" + +#include "nspr.h" + +#include "nsXPCOM.h" +#include "nsCOMPtr.h" +#include "nsMemory.h" +#include "nsString.h" + +#include "nsISpatialNavigation.h" + +#include "nsArray.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsFrameTraversal.h" +#include "nsHTMLAtoms.h" +#include "nsIArray.h" +#include "nsIBaseWindow.h" +#include "nsICategoryManager.h" +#include "nsIChromeEventHandler.h" +#include "nsIComponentManager.h" +#include "nsIDOM3EventTarget.h" +#include "nsIDOMAbstractView.h" +#include "nsIDOMCSSStyleDeclaration.h" +#include "nsIDOMDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMDocumentView.h" +#include "nsIDOMElementCSSInlineStyle.h" +#include "nsIDOMEvent.h" +#include "nsIDOMEventGroup.h" +#include "nsIDOMEventReceiver.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsIDOMHTMLAreaElement.h" +#include "nsIDOMHTMLCollection.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDOMHTMLFrameElement.h" +#include "nsIDOMHTMLHtmlElement.h" +#include "nsIDOMHTMLIFrameElement.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMHTMLMapElement.h" +#include "nsIDOMHTMLOptionElement.h" +#include "nsIDOMHTMLSelectElement.h" +#include "nsIDOMKeyEvent.h" +#include "nsIDOMKeyListener.h" +#include "nsIDOMNSEvent.h" +#include "nsIDOMNSHTMLElement.h" +#include "nsIDOMNSUIEvent.h" +#include "nsIDOMNode.h" +#include "nsIDOMWindow.h" +#include "nsIDOMWindowInternal.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDocument.h" +#include "nsIDocumentViewer.h" +#include "nsIEventStateManager.h" +#include "nsIFile.h" +#include "nsIFocusController.h" +#include "nsIFormControl.h" +#include "nsIFrameFrame.h" +#include "nsIFrameLoader.h" +#include "nsIFrameTraversal.h" +#include "nsIGenericFactory.h" +#include "nsIHTMLDocument.h" +#include "nsIImageFrame.h" +#include "nsIImageMap.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsILineIterator.h" +#include "nsILocalFile.h" +#include "nsINameSpaceManager.h" +#include "nsINodeInfo.h" +#include "nsIObserver.h" +#include "nsIPrefBranchInternal.h" +#include "nsIPrefService.h" +#include "nsIProperties.h" +#include "nsIScriptContext.h" +#include "nsIScriptGlobalObject.h" +#include "nsIServiceManager.h" +#include "nsISound.h" +#include "nsIView.h" +#include "nsIViewManager.h" +#include "nsIWebBrowser.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWindowWatcher.h" +#include "nsLayoutAtoms.h" +#include "nsLayoutCID.h" +#include "nsPIDOMWindow.h" +#include "nsStyleContext.h" + + +class nsSpatialNavigationService; +class nsSpatialNavigation; + +class nsSpatialNavigation : public nsISpatialNavigation, public nsIDOMKeyListener +{ +public: + + NS_DECL_ISUPPORTS + NS_DECL_NSISPATIALNAVIGATION + + NS_DECL_NSIDOMEVENTLISTENER + + // ----- nsIDOMKeyListener ---------------------------- + NS_IMETHOD KeyDown(nsIDOMEvent* aKeyEvent); + NS_IMETHOD KeyUp(nsIDOMEvent* aKeyEvent); + NS_IMETHOD KeyPress(nsIDOMEvent* aKeyEvent); + + nsSpatialNavigation(nsSpatialNavigationService* aService); + +private: + ~nsSpatialNavigation(); + + nsPresContext* getPresContext(nsIContent* aContent); + + PRInt64 spatialDistance(int direction, nsRect& a, nsRect& b); + + nsIDOMWindow* getContentWindow(); + + void setFocusedContent(nsIContent* aContent); + void getFocusedContent(int direction, nsIContent** aContent); + + nsresult handleMove(int direction); + nsresult getContentInDirection(int direction, nsPresContext* presContext, nsRect& focusedRect, nsIFrame* focusedFrame, PRBool aFocusDocuments, PRBool isAREA, nsIContent** aContent); + + nsCOMPtr mTopWindow; + + nsSpatialNavigationService* mService; + + PRBool mNavigationFramesState; +}; + + + +class nsSpatialNavigationService: public nsIObserver +{ +public: + nsSpatialNavigationService(); + virtual ~nsSpatialNavigationService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsCOMArray mObjects; + + PRBool mEnabled; + PRBool mIgnoreTextFields; + PRBool mDisableJSWhenFocusing; + + PRInt32 mKeyCodeLeft; + PRInt32 mKeyCodeRight; + PRInt32 mKeyCodeUp; + PRInt32 mKeyCodeDown; + PRInt32 mKeyCodeModifier; + +}; + +extern PRInt32 gRectFudge; +extern PRInt32 gDirectionalBias; + +enum { + eNavLeft = 0, + eNavUp, + eNavRight, + eNavDown +}; + + +// Utils + +PRBool is_space (char c); +nscoord* lo_parse_coord_list (char *str, PRInt32* value_cnt); +nsresult createFrameTraversal (PRUint32 type, nsPresContext* presContext, nsIBidirectionalEnumerator** outTraversal); +nsresult getEventTargetFromWindow (nsIDOMWindow* aWindow, nsIDOM3EventTarget** aEventTarget, nsIDOMEventGroup** aSystemGroup); +void getContentFromFrame (nsIContent* c, nsIContent** outContent); +nsresult getFrameForContent (nsIContent* aContent, nsIFrame** aFrame); +PRBool isContentOfType (nsIContent* content, const char* type); +PRBool isArea (nsIContent* content); +PRBool isMap (nsIFrame* frame); +PRBool isTargetable (PRBool focusDocuments, nsIFrame* frame); +nsRect makeRectRelativeToGlobalView (nsIFrame *aFrame); +void poly2Rect (int sides, nscoord* coord, nsRect* rect); +void getRectOfAreaElement (nsIFrame* f, nsIDOMHTMLAreaElement* e, nsRect* r); +PRBool isRectInDirection (int direction, nsRect& focusedRect, nsRect& frameRect); +PRInt64 spatialDistance (int direction, nsRect& a, nsRect& b); +void GetWindowFromDocument (nsIDOMDocument* aDocument, nsIDOMWindowInternal** aWindow); + + + +class DisableJSScope +{ +public: + DisableJSScope(nsIDOMWindow* window) : + isEnabled(PR_FALSE), scriptContext(nsnull) + { + // My passing null, we don't do a thing + if (!window) + return; + + // so, we want to set the focus to the next element, but + // we do not want onFocus to fire. The reason for this is + // that we want to have the "enter" key be able to trigger + // the targeted link after we have focused it. However, + // we have found a popular portal has a toolbar that, on + // onFocus, decides to move the focus as if the targeted + // content was clicked. The only way that I know how to + // do this is to disable javascript during this call. + + nsCOMPtr sgo (do_QueryInterface(window)); + if (!sgo) + return; + + scriptContext = sgo->GetContext(); + if (!scriptContext) + return; + + isEnabled = scriptContext->GetScriptsEnabled(); + + if (isEnabled) + scriptContext->SetScriptsEnabled(PR_FALSE, PR_TRUE); + } + + ~DisableJSScope() + { + if (isEnabled && scriptContext) + { + // enable javascript again.. + scriptContext->SetScriptsEnabled(PR_TRUE, PR_TRUE); + } + + } + PRBool isEnabled; + nsIScriptContext *scriptContext; +}; + + diff --git a/mozilla/extensions/spatialnavigation/src/nsSpatialNavigationService.cpp b/mozilla/extensions/spatialnavigation/src/nsSpatialNavigationService.cpp new file mode 100755 index 00000000000..01e8a924c6e --- /dev/null +++ b/mozilla/extensions/spatialnavigation/src/nsSpatialNavigationService.cpp @@ -0,0 +1,267 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape 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/NPL/ + * + * 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 Spatial Navigation + * + * The Initial Developer of the Original Code is + * Douglas F. Turner II + * Portions created by the Initial Developer are Copyright (C) 2004-2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either 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 NPL, 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 NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsSpatialNavigationPrivate.h" + +nsSpatialNavigationService::nsSpatialNavigationService() +{ + mEnabled = PR_TRUE; + mIgnoreTextFields = PR_TRUE; + mDisableJSWhenFocusing = PR_TRUE; + + mKeyCodeLeft = nsIDOMKeyEvent::DOM_VK_LEFT; + mKeyCodeRight = nsIDOMKeyEvent::DOM_VK_RIGHT; + mKeyCodeUp = nsIDOMKeyEvent::DOM_VK_UP; + mKeyCodeDown = nsIDOMKeyEvent::DOM_VK_DOWN; + + mKeyCodeModifier = 0x00000012 | 0x00100000; // By default ALT and SHIFT +} + +nsSpatialNavigationService::~nsSpatialNavigationService() +{ +} + +NS_IMPL_ISUPPORTS1(nsSpatialNavigationService, nsIObserver) + +NS_IMETHODIMP +nsSpatialNavigationService::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData) +{ + nsresult rv; + + if (!strcmp(aTopic,"domwindowopened")) + { + nsCOMPtr chromeWindow = do_QueryInterface(aSubject); + + nsSpatialNavigation* sn = new nsSpatialNavigation(this); + + if (!sn) + return NS_ERROR_OUT_OF_MEMORY; + + sn->Init(chromeWindow); + + mObjects.AppendObject(sn); // the array owns the only reference to sn. + + return NS_OK; + } + + if (!strcmp(aTopic,"domwindowclosed")) + { + nsCOMPtr chromeWindow = do_QueryInterface(aSubject); + // need to find it in our array + + PRInt32 count = mObjects.Count(); + for (PRInt32 i = 0; i < count; i++) + { + nsISpatialNavigation* sn = mObjects[i]; + nsCOMPtr attachedWindow; + sn->GetAttachedWindow(getter_AddRefs(attachedWindow)); + + if (attachedWindow == chromeWindow) + { + sn->Shutdown(); + mObjects.RemoveObjectAt(i); + return NS_OK; + } + } + return NS_OK; + } + + if (!strcmp(aTopic,"xpcom-startup")) + { + nsCOMPtr windowWatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + windowWatcher->RegisterNotification(this); + + nsCOMPtr prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); + NS_ENSURE_SUCCESS(rv, rv); + + prefBranch->AddObserver("snav.", this, PR_FALSE); + + return NS_OK; + } + + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) + { + nsCOMPtr prefBranch = do_QueryInterface(aSubject); + nsXPIDLCString cstr; + + const char* pref = NS_ConvertUCS2toUTF8(aData).get(); + + if (!strcmp(pref, "snav.enabled")) + { + prefBranch->GetIntPref(pref, &mEnabled); + } + else if (!strcmp(pref, "snav.ignoreTextFields")) + { + prefBranch->GetIntPref(pref, &mIgnoreTextFields); + } + else if (!strcmp(pref, "snav.directionalBias")) + { + prefBranch->GetIntPref(pref, &gDirectionalBias); + if (gDirectionalBias == 0) + gDirectionalBias = 1; + } + else if (!strcmp(pref, "snav.disableJS")) + { + prefBranch->GetIntPref(pref, &mDisableJSWhenFocusing); + } + else if (!strcmp(pref, "snav.rectFudge")) + { + prefBranch->GetIntPref(pref, &gRectFudge); + } + else if (!strcmp(pref, "snav.keyCode.left")) + { + prefBranch->GetIntPref(pref, &mKeyCodeLeft); + } + else if (!strcmp(pref, "snav.keyCode.right")) + { + prefBranch->GetIntPref(pref, &mKeyCodeRight); + } + else if (!strcmp(pref, "snav.keyCode.up")) + { + prefBranch->GetIntPref(pref, &mKeyCodeUp); + } + else if (!strcmp(pref, "snav.keyCode.down")) + { + prefBranch->GetIntPref(pref, &mKeyCodeDown); + } + else if (!strcmp(pref, "snav.keyCode.modifier")) + { + prefBranch->GetIntPref(pref, &mKeyCodeModifier); + } + + return NS_OK; + } + + return NS_OK; +} + + +//------------------------------------------------------------------------------ +// XPCOM REGISTRATION BELOW +//------------------------------------------------------------------------------ + +#define SpatialNavigation_CID \ +{ 0xd1b91385, 0xe1c1, 0x46ec, \ +{0x8d, 0x15, 0x88, 0x0c, 0x45, 0xbe, 0x8e, 0x0e} } + +#define SpatialNavigation_ContractID "@mozilla.org/spatialNavigation/service;1" + +#define SpatialNavigationService_CID \ +{ 0x4125624b, 0xaf22, 0x4d50, \ +{ 0x87, 0xf6, 0x40, 0x19, 0xc9, 0x85, 0x7b, 0x58} } + +#define SpatialNavigationService_ContractID "@mozilla.org/spatialnavigation/service" + + +static NS_METHOD SpatialNavigationServiceRegistration(nsIComponentManager *aCompMgr, + nsIFile *aPath, + const char *registryLocation, + const char *componentType, + const nsModuleComponentInfo *info) +{ + nsresult rv; + + nsCOMPtr servman = do_QueryInterface((nsISupports*)aCompMgr, &rv); + if (NS_FAILED(rv)) + return rv; + + + nsCOMPtr catman; + servman->GetServiceByContractID(NS_CATEGORYMANAGER_CONTRACTID, + NS_GET_IID(nsICategoryManager), + getter_AddRefs(catman)); + + if (NS_FAILED(rv)) + return rv; + + char* previous = nsnull; + rv = catman->AddCategoryEntry("xpcom-startup", + "SpatialNavigationService", + SpatialNavigationService_ContractID, + PR_TRUE, + PR_TRUE, + &previous); + if (previous) + nsMemory::Free(previous); + + return rv; +} + +static NS_METHOD SpatialNavigationServiceUnregistration(nsIComponentManager *aCompMgr, + nsIFile *aPath, + const char *registryLocation, + const nsModuleComponentInfo *info) +{ + nsresult rv; + + nsCOMPtr servman = do_QueryInterface((nsISupports*)aCompMgr, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr catman; + servman->GetServiceByContractID(NS_CATEGORYMANAGER_CONTRACTID, + NS_GET_IID(nsICategoryManager), + getter_AddRefs(catman)); + + if (NS_FAILED(rv)) + return rv; + + rv = catman->DeleteCategoryEntry("xpcom-startup", + "SpatialNavigationService", + PR_TRUE); + + return rv; +} + + + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSpatialNavigationService) + + + static const nsModuleComponentInfo components[] = +{ + { "SpatialNavigationService", + SpatialNavigationService_CID, + SpatialNavigationService_ContractID, + nsSpatialNavigationServiceConstructor, + SpatialNavigationServiceRegistration, + SpatialNavigationServiceUnregistration + } + +}; + +NS_IMPL_NSGETMODULE(SpatialNavigationModule, components) diff --git a/mozilla/extensions/spatialnavigation/src/nsSpatialNavigationUtils.cpp b/mozilla/extensions/spatialnavigation/src/nsSpatialNavigationUtils.cpp new file mode 100755 index 00000000000..f6e5f0c130d --- /dev/null +++ b/mozilla/extensions/spatialnavigation/src/nsSpatialNavigationUtils.cpp @@ -0,0 +1,774 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape 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/NPL/ + * + * 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 Spatial Navigation + * + * The Initial Developer of the Original Code is + * Douglas F. Turner II + * Portions created by the Initial Developer are Copyright (C) 2004-2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either 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 NPL, 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 NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsSpatialNavigationPrivate.h" +#include "nsIFrameDebug.h" + +PRBool is_space(char c) +{ + return (c == ' ' || c == '\f' || c == '\n' || + c == '\r' || c == '\t' || c == '\v'); +} + +nscoord* lo_parse_coord_list(char *str, PRInt32* value_cnt) +{ + char *tptr; + char *n_str; + PRInt32 i, cnt; + PRInt32 *value_list; + + /* + * Nothing in an empty list + */ + *value_cnt = 0; + if (!str || *str == '\0') + { + return nsnull; + } + + /* + * Skip beginning whitespace, all whitespace is empty list. + */ + n_str = str; + while (is_space(*n_str)) + { + n_str++; + } + if (*n_str == '\0') + { + return nsnull; + } + + /* + * Make a pass where any two numbers separated by just whitespace + * are given a comma separator. Count entries while passing. + */ + cnt = 0; + while (*n_str != '\0') + { + PRBool has_comma; + + /* + * Skip to a separator + */ + tptr = n_str; + while (!is_space(*tptr) && *tptr != ',' && *tptr != '\0') + { + tptr++; + } + n_str = tptr; + + /* + * If no more entries, break out here + */ + if (*n_str == '\0') + { + break; + } + + /* + * Skip to the end of the separator, noting if we have a + * comma. + */ + has_comma = PR_FALSE; + while (is_space(*tptr) || *tptr == ',') + { + if (*tptr == ',') + { + if (has_comma == PR_FALSE) + { + has_comma = PR_TRUE; + } + else + { + break; + } + } + tptr++; + } + /* + * If this was trailing whitespace we skipped, we are done. + */ + if ((*tptr == '\0')&&(has_comma == PR_FALSE)) + { + break; + } + /* + * Else if the separator is all whitespace, and this is not the + * end of the string, add a comma to the separator. + */ + else if (has_comma == PR_FALSE) + { + *n_str = ','; + } + + /* + * count the entry skipped. + */ + cnt++; + + n_str = tptr; + } + /* + * count the last entry in the list. + */ + cnt++; + + /* + * Allocate space for the coordinate array. + */ + value_list = new nscoord[cnt]; + if (!value_list) + { + return nsnull; + } + + /* + * Second pass to copy integer values into list. + */ + tptr = str; + for (i=0; iPresShell(); + if (!presShell) + return NS_ERROR_FAILURE; + + nsIFrame* frame = presShell->GetRootFrame(); + + if (!frame) + return NS_ERROR_FAILURE; + + + + nsIFrameDebug* fd; + frame->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**) &fd); + if (fd) + fd->List(frame->GetPresContext(), stdout, 0); + + nsCOMPtr frameTraversal; + + static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); + nsCOMPtr trav(do_CreateInstance(kFrameTraversalCID,&result)); + if (NS_FAILED(result)) + return result; + + result = trav->NewFrameTraversal(getter_AddRefs(frameTraversal), + type, + presContext, + frame); + if (NS_FAILED(result)) + return result; + + NS_IF_ADDREF(*outTraversal = frameTraversal); + return NS_OK; +} + +nsresult getEventTargetFromWindow(nsIDOMWindow* aWindow, nsIDOM3EventTarget** aEventTarget, nsIDOMEventGroup** aSystemGroup) +{ + *aEventTarget = nsnull; + nsCOMPtr privateWindow = do_QueryInterface(aWindow); + + if (!privateWindow) + return NS_ERROR_UNEXPECTED; // assert + + nsIChromeEventHandler *chromeEventHandler = privateWindow->GetChromeEventHandler(); + + nsCOMPtr receiver(do_QueryInterface(chromeEventHandler)); + if (!receiver) + return NS_ERROR_UNEXPECTED; // assert + + nsCOMPtr systemGroup; + receiver->GetSystemEventGroup(getter_AddRefs(systemGroup)); + nsCOMPtr target(do_QueryInterface(receiver)); + + if (!target || !systemGroup) + return NS_ERROR_FAILURE; + + NS_ADDREF(*aEventTarget = target); + NS_ADDREF(*aSystemGroup = systemGroup); + return NS_OK; +} + +void getContentFromFrame(nsIContent* c, nsIContent** outContent) +{ + *outContent = nsnull; + + nsCOMPtr result; + nsCOMPtr contentDocument; + + nsCOMPtr domFrameElement = do_QueryInterface(c); + + if (domFrameElement) { + domFrameElement->GetContentDocument(getter_AddRefs(contentDocument)); + } + else { + nsCOMPtr domIFrameElement = do_QueryInterface(c); + if (domIFrameElement) + domIFrameElement->GetContentDocument(getter_AddRefs(contentDocument)); + } + + if (contentDocument) { + nsCOMPtr documentElement; + contentDocument->GetDocumentElement(getter_AddRefs(documentElement)); + result = do_QueryInterface(documentElement); + } + NS_IF_ADDREF(*outContent = result); +} + +nsresult getFrameForContent(nsIContent* aContent, nsIFrame** aFrame) +{ + *aFrame = nsnull; + + if (!aContent) + return NS_ERROR_FAILURE; + + nsIDocument* doc = aContent->GetDocument(); + if (!doc) + return NS_ERROR_FAILURE; + + nsIFrame* frame; + nsIPresShell *presShell = doc->GetShellAt(0); + presShell->GetPrimaryFrameFor(aContent, &frame); + + if (!frame) + return NS_ERROR_FAILURE; + + *aFrame = frame; + return NS_OK; +} + +PRBool +isContentOfType(nsIContent* content, const char* type) +{ + if (!content) + return PR_FALSE; + + nsINodeInfo *nodeInfo = content->GetNodeInfo(); + if (nodeInfo) + { + nsIAtom* atom = nodeInfo->NameAtom(); + if (atom) + return atom->EqualsUTF8(nsDependentCString(type)); + } + return PR_FALSE; +} + +PRBool +isArea(nsIContent* content) +{ + if (!content || !content->IsContentOfType(nsIContent::eHTML)) + return PR_FALSE; + + return isContentOfType(content, "area"); +} + +PRBool +isDocument(nsIFrame* frame) +{ + nsIContent* content = frame->GetContent(); + + if (!content || !content->IsContentOfType(nsIContent::eHTML)) + return PR_FALSE; + + return isContentOfType(content, "map"); +} + +PRBool +isMap(nsIFrame* frame) +{ + nsIContent* content = frame->GetContent(); + + if (!content || !content->IsContentOfType(nsIContent::eHTML)) + return PR_FALSE; + + return isContentOfType(content, "map"); +} + +PRBool +isTargetable(PRBool focusDocuments, nsIFrame* frame) +{ + nsIContent* currentContent = frame->GetContent(); + + if (!currentContent) + return PR_FALSE; + + if (!currentContent->IsContentOfType(nsIContent::eHTML)) + return PR_FALSE; + + if (isContentOfType(currentContent, "map")) + return PR_TRUE; + + if (isContentOfType(currentContent, "a")) + { + // an anchor isn't targetable unless it has a non-null href. + nsCOMPtr anchorElement = do_QueryInterface(currentContent); + nsAutoString uri; + anchorElement->GetHref(uri); + if (uri.IsEmpty()) { + return PR_FALSE; + } + return PR_TRUE; + } + + nsCOMPtr frameFrame(do_QueryInterface(frame)); + if (frameFrame) + return PR_TRUE; + + nsCOMPtr iFrameElement = do_QueryInterface(currentContent); + if (iFrameElement) + return PR_TRUE; + + if (focusDocuments) { + nsCOMPtr hhElement(do_QueryInterface(currentContent)); + if (hhElement) + return PR_TRUE; + } + + // need to figure out how to determine if a element is + // either disabled, hidden, or it is inaccessible due to + // its parent being one of these. + + PRBool disabled = PR_FALSE; + + nsCOMPtr selectElement = do_QueryInterface(currentContent); + if (selectElement && NS_SUCCEEDED(selectElement->GetDisabled(&disabled))) + return !disabled; + + nsCOMPtr optionElement = do_QueryInterface(currentContent); + if (optionElement && NS_SUCCEEDED(optionElement->GetDisabled(&disabled))) + return !disabled; + + nsAutoString inputType; + nsCOMPtr inputElement = do_QueryInterface(currentContent); + if (inputElement && NS_SUCCEEDED(inputElement->GetDisabled(&disabled)) && NS_SUCCEEDED(inputElement->GetType(inputType))) + return !disabled && (! inputType.EqualsIgnoreCase("hidden")); + + return PR_FALSE; +} + +nsRect makeRectRelativeToGlobalView(nsIFrame *aFrame) +{ + nsRect result; + result.SetRect(0,0,0,0); + + nsPoint offset; + + if (!aFrame) + return result; + + result = aFrame->GetRect(); + + nsIView* view; + aFrame->GetOffsetFromView(offset, &view); + + nsIView* rootView = nsnull; + if (view) { + nsIViewManager* viewManager = view->GetViewManager(); + NS_ASSERTION(viewManager, "View must have a viewmanager"); + viewManager->GetRootView(rootView); + } + while (view) { + offset += view->GetPosition(); + if (view == rootView) { + break; + } + view = view->GetParent(); + } + + result.MoveTo(offset); + return result; +} + + +void poly2Rect(int sides, nscoord* coord, nsRect* rect) +{ + nscoord x1, x2, y1, y2, xtmp, ytmp; + x1 = x2 = coord[0]; + y1 = y2 = coord[1]; + for (PRInt32 i = 2; i < sides; i += 2) + { + xtmp = coord[i]; + ytmp = coord[i+1]; + x1 = x1 < xtmp ? x1 : xtmp; + y1 = y1 < ytmp ? y1 : ytmp; + x2 = x2 > xtmp ? x2 : xtmp; + y2 = y2 > ytmp ? y2 : ytmp; + } + rect->SetRect(x1, y1, x2, y2); +} + +void getRectOfAreaElement(nsIFrame* f, nsIDOMHTMLAreaElement* e, nsRect* r) +{ + if (!f || !e || !r) + return; + + // RECT !! + nsRect frameRect = makeRectRelativeToGlobalView(f); + + if (frameRect.width == 0) + frameRect.width = 1; + if (frameRect.height == 0) + frameRect.height = 1; + + nsPoint offset; + offset.x = frameRect.x; + offset.y = frameRect.y; + + + nsAutoString coordstr; + e->GetCoords(coordstr); + + NS_ConvertUTF16toUTF8 cp (coordstr); + + PRInt32 count; + nscoord* coords = lo_parse_coord_list((char*) cp.get(), &count); + + // FIX what about other shapes? + + if (count == 4) + { + // x y width height + frameRect.SetRect(coords[0], coords[1], coords[2] - coords[0], coords[3] - coords[1]); + + frameRect.x += offset.x; + frameRect.y += offset.y; + } + else if (count >=6) + poly2Rect(count, coords, &frameRect); + else + printf("area count not supported!! %d\n", count); + + // find the center of the rect. + + frameRect.x = frameRect.x + (frameRect.width / 2) - 1; + frameRect.width = 2; + frameRect.y = frameRect.y + (frameRect.height / 2) - 1; + frameRect.height = 2; + + *r = frameRect; +} + +// we want to determine if going in the |direction| +// direction from focusedRect will intercept the frameRect + +PRBool isRectInDirection(int direction, nsRect& focusedRect, nsRect& frameRect) +{ + if (direction == eNavLeft) + { + return (frameRect.x < focusedRect.x); + } + + if (direction == eNavRight) + { + return (frameRect.x + frameRect.width > focusedRect.x + focusedRect.width); + } + + if (direction == eNavUp) + { + return (frameRect.y < focusedRect.y); + } + + if (direction == eNavDown) + { + return (frameRect.y + frameRect.height > focusedRect.y + focusedRect.height); + } + return PR_FALSE; +} + +PRInt64 spatialDistance(int direction, nsRect& a, nsRect& b) +{ + PRBool inlineNavigation = PR_FALSE; + nsPoint m, n; + + if (direction == eNavLeft) + { + // |---| + // |---| + // + // |---| |---| + // |---| |---| + // + // |---| + // |---| + // + + if (a.y > b.y + b.height) + { + // the b rect is above a. + m.x = a.x; + m.y = a.y; + n.x = b.x + b.width; + n.y = b.y + b.height; + } + else if (a.y + a.height < b.y) + { + // the b rect is below a. + m.x = a.x; + m.y = a.y + a.height; + n.x = b.x + b.width; + n.y = b.y; + } + else + { + m.x = a.x; + m.y = 0; + n.x = b.x + b.width; + n.y = 0; + + // m.x = (a.x + (a.width / 2)); + // m.y = (a.y + (a.height / 2)); + // n.x = (b.x + (b.width / 2)); + // n.y = (b.y + (b.height / 2)); + + } + } + else if (direction == eNavRight) + { + + // |---| + // |---| + // + // |---| |---| + // |---| |---| + // + // |---| + // |---| + // + + if (a.y > b.y + b.height) + { + // the b rect is above a. + m.x = a.x + a.width; + m.y = a.y; + n.x = b.x; + n.y = b.y + b.height; + } + else if (a.y + a.height < b.y) + { + // the b rect is below a. + m.x = a.x + a.width; + m.y = a.y + a.height; + n.x = b.x; + n.y = b.y; + } + else + { + m.x = a.x + a.width; + m.y = 0; + n.x = b.x; + n.y = 0; + + // m.x = (a.x + (a.width / 2)); + // m.y = (a.y + (a.height / 2)); + // n.x = (b.x + (b.width / 2)); + // n.y = (b.y + (b.height / 2)); + } + } + else if (direction == eNavUp) + { + + // |---| |---| |---| + // |---| |---| |---| + // + // |---| + // |---| + // + + if (a.x > b.x + b.width) + { + // the b rect is to the left of a. + m.x = a.x; + m.y = a.y; + n.x = b.x + b.width; + n.y = b.y + b.height; + } + else if (a.x + a.width < b.x) + { + // the b rect is to the right of a + m.x = a.x + a.width; + m.y = a.y; + n.x = b.x; + n.y = b.y + b.height; + } + else + { + // both b and a share some common x's. + m.x = 0; + m.y = a.y; + n.x = 0; + n.y = b.y + b.height; + + // m.x = (a.x + (a.width / 2)); + // m.y = (a.y + (a.height / 2)); + // n.x = (b.x + (b.width / 2)); + // n.y = (b.y + (b.height / 2)); + } + } + else if (direction == eNavDown) + { + // |---| + // |---| + // + // |---| |---| |---| + // |---| |---| |---| + // + + if (a.x > b.x + b.width) + { + // the b rect is to the left of a. + m.x = a.x; + m.y = a.y + a.height; + n.x = b.x + b.width; + n.y = b.y; + } + else if (a.x + a.width < b.x) + { + // the b rect is to the right of a + m.x = a.x + a.width; + m.y = a.y + a.height; + n.x = b.x; + n.y = b.y; + } + else + { + // both b and a share some common x's. + m.x = 0; + m.y = a.y + a.height; + n.x = 0; + n.y = b.y; + + // m.x = (a.x + (a.width / 2)); + // m.y = (a.y + (a.height / 2)); + // n.x = (b.x + (b.width / 2)); + // n.y = (b.y + (b.height / 2)); + } + } + + // a is always the currently focused rect. + + nsRect scopedRect = a; + scopedRect.Inflate(gRectFudge, gRectFudge); + + if (direction == eNavLeft) + { + scopedRect.x = 0; + scopedRect.width = nscoord_MAX; + if (scopedRect.Intersects(b)) + inlineNavigation = PR_TRUE; + } + else if (direction == eNavRight) + { + scopedRect.width = nscoord_MAX; + if (scopedRect.Intersects(b)) + inlineNavigation = PR_TRUE; + } + else if (direction == eNavUp) + { + scopedRect.y = 0; + scopedRect.height = nscoord_MAX; + if (scopedRect.Intersects(b)) + inlineNavigation = PR_TRUE; + } + else if (direction == eNavDown) + { + scopedRect.height = nscoord_MAX; + if (scopedRect.Intersects(b)) + inlineNavigation = PR_TRUE; + } + + PRInt64 d = ((m.x-n.x)*(m.x-n.x)) + ((m.y-n.y)*(m.y-n.y)); + + if(d<0) + d=d*(-1); + + if (inlineNavigation) + d /= gDirectionalBias; + + return d; +} + +void GetWindowFromDocument(nsIDOMDocument* aDocument, nsIDOMWindowInternal** aWindow) +{ + nsCOMPtr docview = do_QueryInterface(aDocument); + + nsCOMPtr view; + docview->GetDefaultView(getter_AddRefs(view)); + if (!view) return; + + nsCOMPtr window = do_QueryInterface(view); + NS_IF_ADDREF(*aWindow = window); +} diff --git a/mozilla/extensions/spatialnavigation/test/Spatial_Navigation_Test_Cases.html b/mozilla/extensions/spatialnavigation/test/Spatial_Navigation_Test_Cases.html new file mode 100755 index 00000000000..a63b1123764 --- /dev/null +++ b/mozilla/extensions/spatialnavigation/test/Spatial_Navigation_Test_Cases.html @@ -0,0 +1,272 @@ + + + + + Spatial Navigation Test Cases + + +
+

Spatial +Navigation Fun

+


+

+

+
+
+
+

3x3 +Centered Table Test

+
+ + + + + + + + + + + + + + + + + + +
testtesttest
testtesttest
testtesttest
+

+
+
+

Vertical Sine Test

+
+
+
test
+
+ + + +test
+
+ + + +test
+
            +            +            +    test
+

+
+
+

Increasing sentence +length

+
+H
+Ho
+
How
+
How
+
How N
+How No
+How Now
+How Now
+How Now B
+How Now Br
+How Now Bro
+How Now Brow
+How Now Brown
+How Now Brown
+How Now Brown C
+How Now Brown Co
+How Now Brown Cow
+
+

Split sentence
+

+
+H                              +  +ow Now Brown Cow
+Ho             +              +     w +Now Brown Cow
+How               +            +      Now Brown Cow
+How N               +                 ow Brown Cow
+How No               +          +       w Brown Cow
+How Now            +          +           Brown Cow
+How Now B             +        +           rown Cow
+How Now Br             +          +         own Cow
+How Now Bro             +          +         wn Cow
+How Now Brow                                +n Cow
+How Now Brown           +            +          Cow
+How Now Brown C           +          +           ow
+How Now Brown Co         +            +           w
+How Now Brown Cow
+
+
+

Line Wrap Test

+
+ + + + + + + +
Which +of the following constanst do you like the most, one is g, one is pi, +and the other is e.
+
3.14
+ 2.71
+ 6.67
+
+
+

Javascript Hover Test

+When going from any link to the following link, you should NOT get an +alert dialog.  However, if you mouse over this link, you should +see a dialog.
+
+Test
+
+

Overlapped Anchors

+
+ + + + + + +
+

+
+
+
+
+
+
+
+
+

Area Tag (Rect)
+

+
+
+
+
+

Area Tag (Circle)
+

+
+
+
+
+

Area Tag (Polygon)
+

+
+
+
+
+
+A +nice poly test case +
+

FrameSets

+
+ + + + + +
+
+

IFrames

+
+
+
+
+

DIVs

+
+
+a +
+a +
+a +
+
+
+
+
+last + + +

+

A select example +

+ + +
+

+

A option example + +

+ +

+ + + + diff --git a/mozilla/extensions/spatialnavigation/test/a.html b/mozilla/extensions/spatialnavigation/test/a.html new file mode 100755 index 00000000000..6e40da9d046 --- /dev/null +++ b/mozilla/extensions/spatialnavigation/test/a.html @@ -0,0 +1,2 @@ +
+a diff --git a/mozilla/extensions/spatialnavigation/test/links.html b/mozilla/extensions/spatialnavigation/test/links.html new file mode 100755 index 00000000000..0d6ed7cc9a4 --- /dev/null +++ b/mozilla/extensions/spatialnavigation/test/links.html @@ -0,0 +1,9 @@ + + + +

+a +a +a + + diff --git a/mozilla/extensions/spatialnavigation/test/mozilla-star.png b/mozilla/extensions/spatialnavigation/test/mozilla-star.png new file mode 100755 index 00000000000..0d071dede14 Binary files /dev/null and b/mozilla/extensions/spatialnavigation/test/mozilla-star.png differ diff --git a/mozilla/extensions/spatialnavigation/test/prefs.txt b/mozilla/extensions/spatialnavigation/test/prefs.txt new file mode 100755 index 00000000000..46890752637 --- /dev/null +++ b/mozilla/extensions/spatialnavigation/test/prefs.txt @@ -0,0 +1,107 @@ +/////////////////////////////////////////////////////////// +// snav.enabled +/////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////// +// snav.ignoreTextFields +/////////////////////////////////////////////////////////// + + This preference when enabled will ensure that text fields +and inputs retain focus even if spatial navigation was +requested. + +Default: TRUE + +/////////////////////////////////////////////////////////// +// snav.directionalBias +/////////////////////////////////////////////////////////// + +When calculating distances between elements on the screen, +elements may be given a bias if the elements are in the +direction of navigation. The bias is a given by a fraction +such that: + + bias distance = true distance / directional bias + +DEFAULT: 3 + +/////////////////////////////////////////////////////////// +// snav.disableJS +/////////////////////////////////////////////////////////// + +Disable onFocus event that an element may recieve during +spatial navigation. On some pages we have found that the +page decides to move the focus as if the targeted content +was clicked. This may lead to an undesirable behavior. + +DEFAULT: TRUE + +/////////////////////////////////////////////////////////// +// snav.rectFudge +/////////////////////////////////////////////////////////// + +To reduce the chance that a element overlaps with another +element, we reduce the elements height and width by this +value. + +DEFAULT: 20 + +/////////////////////////////////////////////////////////// +// snav.keyCode.left +/////////////////////////////////////////////////////////// + +This preference is the key code which corresponds to the +left direction. + +DEFAULT: DOM_VK_LEFT +SEE: nsIDOMKeyEvent + +/////////////////////////////////////////////////////////// +// snav.keyCode.right +/////////////////////////////////////////////////////////// + +This preference is the key code which corresponds to the +right direction. + +DEFAULT: DOM_VK_RIGHT +SEE: nsIDOMKeyEvent + + +/////////////////////////////////////////////////////////// +// snav.keyCode.up +/////////////////////////////////////////////////////////// + +This preference is the key code which corresponds to the +up direction. + +DEFAULT: DOM_VK_UP +SEE: nsIDOMKeyEvent + +/////////////////////////////////////////////////////////// +// snav.keyCode.down +/////////////////////////////////////////////////////////// + +This preference is the key code which corresponds to the +down direction. + +DEFAULT: DOM_VK_DOWN +SEE: nsIDOMKeyEvent + +/////////////////////////////////////////////////////////// +// snav.keyCode.modifier +/////////////////////////////////////////////////////////// + +This preference is the key code which corresponds to the +required modifier keys. These values are given as follows: + + NONE = 0 + SHIFT = 0x00100000 + CONTROL = 0x00001100 + ALT = 0x00000012 + + +DEFAULT: ALT | SHIFT + + +