1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsMathMLmactionFrame.h"
8 #include "nsCOMPtr.h"
9 #include "nsDocShell.h"
10 #include "nsPresContext.h"
11 #include "nsNameSpaceManager.h"
12 #include "nsIDocShell.h"
13 #include "nsIDocShellTreeOwner.h"
14 #include "nsIWebBrowserChrome.h"
15 #include "nsIInterfaceRequestorUtils.h"
16 #include "nsTextFragment.h"
17 #include "mozilla/PresShell.h"
18 #include "mozilla/gfx/2D.h"
19 #include "mozilla/dom/Event.h"
20 
21 using namespace mozilla;
22 using mozilla::dom::Event;
23 
24 //
25 // <maction> -- bind actions to a subexpression - implementation
26 //
27 
28 enum nsMactionActionTypes {
29   NS_MATHML_ACTION_TYPE_CLASS_ERROR = 0x10,
30   NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION = 0x20,
31   NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION = 0x40,
32   NS_MATHML_ACTION_TYPE_CLASS_BITMASK = 0xF0,
33 
34   NS_MATHML_ACTION_TYPE_NONE = NS_MATHML_ACTION_TYPE_CLASS_ERROR | 0x01,
35 
36   NS_MATHML_ACTION_TYPE_TOGGLE =
37       NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION | 0x01,
38   NS_MATHML_ACTION_TYPE_UNKNOWN =
39       NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION | 0x02,
40 
41   NS_MATHML_ACTION_TYPE_STATUSLINE =
42       NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION | 0x01,
43   NS_MATHML_ACTION_TYPE_TOOLTIP =
44       NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION | 0x02
45 };
46 
47 // helper function to parse actiontype attribute
GetActionType(nsIContent * aContent)48 static int32_t GetActionType(nsIContent* aContent) {
49   nsAutoString value;
50 
51   if (aContent) {
52     if (!aContent->IsElement() ||
53         !aContent->AsElement()->GetAttr(kNameSpaceID_None,
54                                         nsGkAtoms::actiontype_, value))
55       return NS_MATHML_ACTION_TYPE_NONE;
56   }
57 
58   if (value.EqualsLiteral("toggle")) return NS_MATHML_ACTION_TYPE_TOGGLE;
59   if (value.EqualsLiteral("statusline"))
60     return NS_MATHML_ACTION_TYPE_STATUSLINE;
61   if (value.EqualsLiteral("tooltip")) return NS_MATHML_ACTION_TYPE_TOOLTIP;
62 
63   return NS_MATHML_ACTION_TYPE_UNKNOWN;
64 }
65 
NS_NewMathMLmactionFrame(PresShell * aPresShell,ComputedStyle * aStyle)66 nsIFrame* NS_NewMathMLmactionFrame(PresShell* aPresShell,
67                                    ComputedStyle* aStyle) {
68   return new (aPresShell)
69       nsMathMLmactionFrame(aStyle, aPresShell->GetPresContext());
70 }
71 
NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmactionFrame)72 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmactionFrame)
73 
74 nsMathMLmactionFrame::~nsMathMLmactionFrame() {
75   // unregister us as a mouse event listener ...
76   //  printf("maction:%p unregistering as mouse event listener ...\n", this);
77   if (mListener) {
78     mContent->RemoveSystemEventListener(NS_LITERAL_STRING("click"), mListener,
79                                         false);
80     mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseover"),
81                                         mListener, false);
82     mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseout"),
83                                         mListener, false);
84   }
85 }
86 
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)87 void nsMathMLmactionFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
88                                 nsIFrame* aPrevInFlow) {
89   // Init our local attributes
90 
91   mChildCount = -1;  // these will be updated in GetSelectedFrame()
92   mActionType = GetActionType(aContent);
93 
94   // Let the base class do the rest
95   return nsMathMLSelectedFrame::Init(aContent, aParent, aPrevInFlow);
96 }
97 
ChildListChanged(int32_t aModType)98 nsresult nsMathMLmactionFrame::ChildListChanged(int32_t aModType) {
99   // update cached values
100   mChildCount = -1;
101   mSelectedFrame = nullptr;
102 
103   return nsMathMLSelectedFrame::ChildListChanged(aModType);
104 }
105 
106 // return the frame whose number is given by the attribute selection="number"
GetSelectedFrame()107 nsIFrame* nsMathMLmactionFrame::GetSelectedFrame() {
108   nsAutoString value;
109   int32_t selection;
110 
111   if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) ==
112       NS_MATHML_ACTION_TYPE_CLASS_ERROR) {
113     mSelection = -1;
114     mInvalidMarkup = true;
115     mSelectedFrame = nullptr;
116     return mSelectedFrame;
117   }
118 
119   // Selection is not applied to tooltip and statusline.
120   // Thereby return the first child.
121   if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) ==
122       NS_MATHML_ACTION_TYPE_CLASS_IGNORE_SELECTION) {
123     // We don't touch mChildCount here. It's incorrect to assign it 1,
124     // and it's inefficient to count the children. It's fine to leave
125     // it be equal -1 because it's not used with other actiontypes.
126     mSelection = 1;
127     mInvalidMarkup = false;
128     mSelectedFrame = mFrames.FirstChild();
129     return mSelectedFrame;
130   }
131 
132   mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::selection_,
133                                  value);
134   if (!value.IsEmpty()) {
135     nsresult errorCode;
136     selection = value.ToInteger(&errorCode);
137     if (NS_FAILED(errorCode)) selection = 1;
138   } else
139     selection = 1;  // default is first frame
140 
141   if (-1 != mChildCount) {  // we have been in this function before...
142     // cater for invalid user-supplied selection
143     if (selection > mChildCount || selection < 1) selection = -1;
144     // quick return if it is identical with our cache
145     if (selection == mSelection) return mSelectedFrame;
146   }
147 
148   // get the selected child and cache new values...
149   int32_t count = 0;
150   nsIFrame* childFrame = mFrames.FirstChild();
151   while (childFrame) {
152     if (!mSelectedFrame) mSelectedFrame = childFrame;  // default is first child
153     if (++count == selection) mSelectedFrame = childFrame;
154 
155     childFrame = childFrame->GetNextSibling();
156   }
157   // cater for invalid user-supplied selection
158   if (selection > count || selection < 1) selection = -1;
159 
160   mChildCount = count;
161   mSelection = selection;
162   mInvalidMarkup = (mSelection == -1);
163   TransmitAutomaticData();
164 
165   return mSelectedFrame;
166 }
167 
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)168 void nsMathMLmactionFrame::SetInitialChildList(ChildListID aListID,
169                                                nsFrameList& aChildList) {
170   nsMathMLSelectedFrame::SetInitialChildList(aListID, aChildList);
171 
172   if (!mSelectedFrame) {
173     mActionType = NS_MATHML_ACTION_TYPE_NONE;
174   } else {
175     // create mouse event listener and register it
176     mListener = new nsMathMLmactionFrame::MouseListener(this);
177     // printf("maction:%p registering as mouse event listener ...\n", this);
178     mContent->AddSystemEventListener(NS_LITERAL_STRING("click"), mListener,
179                                      false, false);
180     mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseover"), mListener,
181                                      false, false);
182     mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseout"), mListener,
183                                      false, false);
184   }
185 }
186 
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)187 nsresult nsMathMLmactionFrame::AttributeChanged(int32_t aNameSpaceID,
188                                                 nsAtom* aAttribute,
189                                                 int32_t aModType) {
190   bool needsReflow = false;
191 
192   InvalidateFrame();
193 
194   if (aAttribute == nsGkAtoms::actiontype_) {
195     // updating mActionType ...
196     int32_t oldActionType = mActionType;
197     mActionType = GetActionType(mContent);
198 
199     // Initiate a reflow when actiontype classes are different.
200     if ((oldActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) !=
201         (mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK)) {
202       needsReflow = true;
203     }
204   } else if (aAttribute == nsGkAtoms::selection_) {
205     if ((mActionType & NS_MATHML_ACTION_TYPE_CLASS_BITMASK) ==
206         NS_MATHML_ACTION_TYPE_CLASS_USE_SELECTION) {
207       needsReflow = true;
208     }
209   } else {
210     // let the base class handle other attribute changes
211     return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
212                                                     aModType);
213   }
214 
215   if (needsReflow) {
216     PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
217                                   NS_FRAME_IS_DIRTY);
218   }
219 
220   return NS_OK;
221 }
222 
223 // ################################################################
224 // Event handlers
225 // ################################################################
226 
NS_IMPL_ISUPPORTS(nsMathMLmactionFrame::MouseListener,nsIDOMEventListener)227 NS_IMPL_ISUPPORTS(nsMathMLmactionFrame::MouseListener, nsIDOMEventListener)
228 
229 // helper to show a msg on the status bar
230 // curled from nsPluginFrame.cpp ...
231 static void ShowStatus(nsPresContext* aPresContext, nsString& aStatusMsg) {
232   nsCOMPtr<nsIDocShellTreeItem> docShellItem(aPresContext->GetDocShell());
233   if (docShellItem) {
234     nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
235     docShellItem->GetTreeOwner(getter_AddRefs(treeOwner));
236     if (treeOwner) {
237       nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(treeOwner));
238       if (browserChrome) {
239         browserChrome->SetLinkStatus(aStatusMsg);
240       }
241     }
242   }
243 }
244 
245 NS_IMETHODIMP
HandleEvent(Event * aEvent)246 nsMathMLmactionFrame::MouseListener::HandleEvent(Event* aEvent) {
247   nsAutoString eventType;
248   aEvent->GetType(eventType);
249   if (eventType.EqualsLiteral("mouseover")) {
250     mOwner->MouseOver();
251   } else if (eventType.EqualsLiteral("click")) {
252     mOwner->MouseClick();
253   } else if (eventType.EqualsLiteral("mouseout")) {
254     mOwner->MouseOut();
255   } else {
256     MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
257   }
258 
259   return NS_OK;
260 }
261 
MouseOver()262 void nsMathMLmactionFrame::MouseOver() {
263   // see if we should display a status message
264   if (NS_MATHML_ACTION_TYPE_STATUSLINE == mActionType) {
265     // retrieve content from a second child if it exists
266     nsIFrame* childFrame = mFrames.FrameAt(1);
267     if (!childFrame) return;
268 
269     nsIContent* content = childFrame->GetContent();
270     if (!content) return;
271 
272     // check whether the content is mtext or not
273     if (content->IsMathMLElement(nsGkAtoms::mtext_)) {
274       // get the text to be displayed
275       content = content->GetFirstChild();
276       if (!content) return;
277 
278       const nsTextFragment* textFrg = content->GetText();
279       if (!textFrg) return;
280 
281       nsAutoString text;
282       textFrg->AppendTo(text);
283       // collapse whitespaces as listed in REC, section 3.2.6.1
284       text.CompressWhitespace();
285       ShowStatus(PresContext(), text);
286     }
287   }
288 }
289 
MouseOut()290 void nsMathMLmactionFrame::MouseOut() {
291   // see if we should remove the status message
292   if (NS_MATHML_ACTION_TYPE_STATUSLINE == mActionType) {
293     nsAutoString value;
294     value.SetLength(0);
295     ShowStatus(PresContext(), value);
296   }
297 }
298 
MouseClick()299 void nsMathMLmactionFrame::MouseClick() {
300   if (NS_MATHML_ACTION_TYPE_TOGGLE == mActionType) {
301     if (mChildCount > 1) {
302       int32_t selection = (mSelection == mChildCount) ? 1 : mSelection + 1;
303       nsAutoString value;
304       value.AppendInt(selection);
305       bool notify = false;  // don't yet notify the document
306       mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::selection_,
307                                      value, notify);
308 
309       // Now trigger a content-changed reflow...
310       PresShell()->FrameNeedsReflow(mSelectedFrame, IntrinsicDirty::TreeChange,
311                                     NS_FRAME_IS_DIRTY);
312     }
313   }
314 }
315