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