1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 // vim:cindent:tabstop=4:expandtab:shiftwidth=4:
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 "nsLayoutDebuggingTools.h"
8 
9 #include "nsIDocShell.h"
10 #include "nsPIDOMWindow.h"
11 #include "nsIContentViewer.h"
12 
13 #include "nsIServiceManager.h"
14 #include "nsIAtom.h"
15 #include "nsQuickSort.h"
16 
17 #include "nsIContent.h"
18 #include "nsIDocument.h"
19 #include "nsIDOMDocument.h"
20 
21 #include "nsIPresShell.h"
22 #include "nsViewManager.h"
23 #include "nsIFrame.h"
24 
25 #include "nsILayoutDebugger.h"
26 #include "nsLayoutCID.h"
27 static NS_DEFINE_CID(kLayoutDebuggerCID, NS_LAYOUT_DEBUGGER_CID);
28 
29 #include "nsISelectionController.h"
30 #include "mozilla/dom/Element.h"
31 #include "mozilla/Preferences.h"
32 
33 using namespace mozilla;
34 
35 static already_AddRefed<nsIContentViewer>
doc_viewer(nsIDocShell * aDocShell)36 doc_viewer(nsIDocShell *aDocShell)
37 {
38     if (!aDocShell)
39         return nullptr;
40     nsCOMPtr<nsIContentViewer> result;
41     aDocShell->GetContentViewer(getter_AddRefs(result));
42     return result.forget();
43 }
44 
45 static already_AddRefed<nsIPresShell>
pres_shell(nsIDocShell * aDocShell)46 pres_shell(nsIDocShell *aDocShell)
47 {
48     nsCOMPtr<nsIContentViewer> cv = doc_viewer(aDocShell);
49     if (!cv)
50         return nullptr;
51     nsCOMPtr<nsIPresShell> result;
52     cv->GetPresShell(getter_AddRefs(result));
53     return result.forget();
54 }
55 
56 static nsViewManager*
view_manager(nsIDocShell * aDocShell)57 view_manager(nsIDocShell *aDocShell)
58 {
59     nsCOMPtr<nsIPresShell> shell(pres_shell(aDocShell));
60     if (!shell)
61         return nullptr;
62     return shell->GetViewManager();
63 }
64 
65 #ifdef DEBUG
66 static already_AddRefed<nsIDocument>
document(nsIDocShell * aDocShell)67 document(nsIDocShell *aDocShell)
68 {
69     nsCOMPtr<nsIContentViewer> cv(doc_viewer(aDocShell));
70     if (!cv)
71         return nullptr;
72     nsCOMPtr<nsIDOMDocument> domDoc;
73     cv->GetDOMDocument(getter_AddRefs(domDoc));
74     if (!domDoc)
75         return nullptr;
76     nsCOMPtr<nsIDocument> result = do_QueryInterface(domDoc);
77     return result.forget();
78 }
79 #endif
80 
nsLayoutDebuggingTools()81 nsLayoutDebuggingTools::nsLayoutDebuggingTools()
82   : mPaintFlashing(false),
83     mPaintDumping(false),
84     mInvalidateDumping(false),
85     mEventDumping(false),
86     mMotionEventDumping(false),
87     mCrossingEventDumping(false),
88     mReflowCounts(false)
89 {
90     NewURILoaded();
91 }
92 
~nsLayoutDebuggingTools()93 nsLayoutDebuggingTools::~nsLayoutDebuggingTools()
94 {
95 }
96 
NS_IMPL_ISUPPORTS(nsLayoutDebuggingTools,nsILayoutDebuggingTools)97 NS_IMPL_ISUPPORTS(nsLayoutDebuggingTools, nsILayoutDebuggingTools)
98 
99 NS_IMETHODIMP
100 nsLayoutDebuggingTools::Init(mozIDOMWindow* aWin)
101 {
102     if (!Preferences::GetService()) {
103         return NS_ERROR_UNEXPECTED;
104     }
105 
106     {
107         if (!aWin)
108             return NS_ERROR_UNEXPECTED;
109         auto* window = nsPIDOMWindowInner::From(aWin);
110         mDocShell = window->GetDocShell();
111     }
112     NS_ENSURE_TRUE(mDocShell, NS_ERROR_UNEXPECTED);
113 
114     mPaintFlashing =
115         Preferences::GetBool("nglayout.debug.paint_flashing", mPaintFlashing);
116     mPaintDumping =
117         Preferences::GetBool("nglayout.debug.paint_dumping", mPaintDumping);
118     mInvalidateDumping =
119         Preferences::GetBool("nglayout.debug.invalidate_dumping", mInvalidateDumping);
120     mEventDumping =
121         Preferences::GetBool("nglayout.debug.event_dumping", mEventDumping);
122     mMotionEventDumping =
123         Preferences::GetBool("nglayout.debug.motion_event_dumping",
124                              mMotionEventDumping);
125     mCrossingEventDumping =
126         Preferences::GetBool("nglayout.debug.crossing_event_dumping",
127                              mCrossingEventDumping);
128     mReflowCounts =
129         Preferences::GetBool("layout.reflow.showframecounts", mReflowCounts);
130 
131     {
132         nsCOMPtr<nsILayoutDebugger> ld = do_GetService(kLayoutDebuggerCID);
133         if (ld) {
134             ld->GetShowFrameBorders(&mVisualDebugging);
135             ld->GetShowEventTargetFrameBorder(&mVisualEventDebugging);
136         }
137     }
138 
139     return NS_OK;
140 }
141 
142 NS_IMETHODIMP
NewURILoaded()143 nsLayoutDebuggingTools::NewURILoaded()
144 {
145     NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
146     // Reset all the state that should be reset between pages.
147 
148     // XXX Some of these should instead be transferred between pages!
149     mEditorMode = false;
150     mVisualDebugging = false;
151     mVisualEventDebugging = false;
152 
153     mReflowCounts = false;
154 
155     ForceRefresh();
156     return NS_OK;
157 }
158 
159 NS_IMETHODIMP
GetVisualDebugging(bool * aVisualDebugging)160 nsLayoutDebuggingTools::GetVisualDebugging(bool *aVisualDebugging)
161 {
162     *aVisualDebugging = mVisualDebugging;
163     return NS_OK;
164 }
165 
166 NS_IMETHODIMP
SetVisualDebugging(bool aVisualDebugging)167 nsLayoutDebuggingTools::SetVisualDebugging(bool aVisualDebugging)
168 {
169     nsCOMPtr<nsILayoutDebugger> ld = do_GetService(kLayoutDebuggerCID);
170     if (!ld)
171         return NS_ERROR_UNEXPECTED;
172     mVisualDebugging = aVisualDebugging;
173     ld->SetShowFrameBorders(aVisualDebugging);
174     ForceRefresh();
175     return NS_OK;
176 }
177 
178 NS_IMETHODIMP
GetVisualEventDebugging(bool * aVisualEventDebugging)179 nsLayoutDebuggingTools::GetVisualEventDebugging(bool *aVisualEventDebugging)
180 {
181     *aVisualEventDebugging = mVisualEventDebugging;
182     return NS_OK;
183 }
184 
185 NS_IMETHODIMP
SetVisualEventDebugging(bool aVisualEventDebugging)186 nsLayoutDebuggingTools::SetVisualEventDebugging(bool aVisualEventDebugging)
187 {
188     nsCOMPtr<nsILayoutDebugger> ld = do_GetService(kLayoutDebuggerCID);
189     if (!ld)
190         return NS_ERROR_UNEXPECTED;
191     mVisualEventDebugging = aVisualEventDebugging;
192     ld->SetShowEventTargetFrameBorder(aVisualEventDebugging);
193     ForceRefresh();
194     return NS_OK;
195 }
196 
197 NS_IMETHODIMP
GetPaintFlashing(bool * aPaintFlashing)198 nsLayoutDebuggingTools::GetPaintFlashing(bool *aPaintFlashing)
199 {
200     *aPaintFlashing = mPaintFlashing;
201     return NS_OK;
202 }
203 
204 NS_IMETHODIMP
SetPaintFlashing(bool aPaintFlashing)205 nsLayoutDebuggingTools::SetPaintFlashing(bool aPaintFlashing)
206 {
207     mPaintFlashing = aPaintFlashing;
208     return SetBoolPrefAndRefresh("nglayout.debug.paint_flashing", mPaintFlashing);
209 }
210 
211 NS_IMETHODIMP
GetPaintDumping(bool * aPaintDumping)212 nsLayoutDebuggingTools::GetPaintDumping(bool *aPaintDumping)
213 {
214     *aPaintDumping = mPaintDumping;
215     return NS_OK;
216 }
217 
218 NS_IMETHODIMP
SetPaintDumping(bool aPaintDumping)219 nsLayoutDebuggingTools::SetPaintDumping(bool aPaintDumping)
220 {
221     mPaintDumping = aPaintDumping;
222     return SetBoolPrefAndRefresh("nglayout.debug.paint_dumping", mPaintDumping);
223 }
224 
225 NS_IMETHODIMP
GetInvalidateDumping(bool * aInvalidateDumping)226 nsLayoutDebuggingTools::GetInvalidateDumping(bool *aInvalidateDumping)
227 {
228     *aInvalidateDumping = mInvalidateDumping;
229     return NS_OK;
230 }
231 
232 NS_IMETHODIMP
SetInvalidateDumping(bool aInvalidateDumping)233 nsLayoutDebuggingTools::SetInvalidateDumping(bool aInvalidateDumping)
234 {
235     mInvalidateDumping = aInvalidateDumping;
236     return SetBoolPrefAndRefresh("nglayout.debug.invalidate_dumping", mInvalidateDumping);
237 }
238 
239 NS_IMETHODIMP
GetEventDumping(bool * aEventDumping)240 nsLayoutDebuggingTools::GetEventDumping(bool *aEventDumping)
241 {
242     *aEventDumping = mEventDumping;
243     return NS_OK;
244 }
245 
246 NS_IMETHODIMP
SetEventDumping(bool aEventDumping)247 nsLayoutDebuggingTools::SetEventDumping(bool aEventDumping)
248 {
249     mEventDumping = aEventDumping;
250     return SetBoolPrefAndRefresh("nglayout.debug.event_dumping", mEventDumping);
251 }
252 
253 NS_IMETHODIMP
GetMotionEventDumping(bool * aMotionEventDumping)254 nsLayoutDebuggingTools::GetMotionEventDumping(bool *aMotionEventDumping)
255 {
256     *aMotionEventDumping = mMotionEventDumping;
257     return NS_OK;
258 }
259 
260 NS_IMETHODIMP
SetMotionEventDumping(bool aMotionEventDumping)261 nsLayoutDebuggingTools::SetMotionEventDumping(bool aMotionEventDumping)
262 {
263     mMotionEventDumping = aMotionEventDumping;
264     return SetBoolPrefAndRefresh("nglayout.debug.motion_event_dumping", mMotionEventDumping);
265 }
266 
267 NS_IMETHODIMP
GetCrossingEventDumping(bool * aCrossingEventDumping)268 nsLayoutDebuggingTools::GetCrossingEventDumping(bool *aCrossingEventDumping)
269 {
270     *aCrossingEventDumping = mCrossingEventDumping;
271     return NS_OK;
272 }
273 
274 NS_IMETHODIMP
SetCrossingEventDumping(bool aCrossingEventDumping)275 nsLayoutDebuggingTools::SetCrossingEventDumping(bool aCrossingEventDumping)
276 {
277     mCrossingEventDumping = aCrossingEventDumping;
278     return SetBoolPrefAndRefresh("nglayout.debug.crossing_event_dumping", mCrossingEventDumping);
279 }
280 
281 NS_IMETHODIMP
GetReflowCounts(bool * aShow)282 nsLayoutDebuggingTools::GetReflowCounts(bool* aShow)
283 {
284     *aShow = mReflowCounts;
285     return NS_OK;
286 }
287 
288 NS_IMETHODIMP
SetReflowCounts(bool aShow)289 nsLayoutDebuggingTools::SetReflowCounts(bool aShow)
290 {
291     NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
292     nsCOMPtr<nsIPresShell> shell(pres_shell(mDocShell));
293     if (shell) {
294 #ifdef MOZ_REFLOW_PERF
295         shell->SetPaintFrameCount(aShow);
296         SetBoolPrefAndRefresh("layout.reflow.showframecounts", aShow);
297         mReflowCounts = aShow;
298 #else
299         printf("************************************************\n");
300         printf("Sorry, you have not built with MOZ_REFLOW_PERF=1\n");
301         printf("************************************************\n");
302 #endif
303     }
304     return NS_OK;
305 }
306 
DumpAWebShell(nsIDocShellTreeItem * aShellItem,FILE * out,int32_t aIndent)307 static void DumpAWebShell(nsIDocShellTreeItem* aShellItem, FILE* out, int32_t aIndent)
308 {
309     nsString name;
310     nsCOMPtr<nsIDocShellTreeItem> parent;
311     int32_t i, n;
312 
313     for (i = aIndent; --i >= 0; )
314         fprintf(out, "  ");
315 
316     fprintf(out, "%p '", static_cast<void*>(aShellItem));
317     aShellItem->GetName(name);
318     aShellItem->GetSameTypeParent(getter_AddRefs(parent));
319     fputs(NS_LossyConvertUTF16toASCII(name).get(), out);
320     fprintf(out, "' parent=%p <\n", static_cast<void*>(parent));
321 
322     ++aIndent;
323     aShellItem->GetChildCount(&n);
324     for (i = 0; i < n; ++i) {
325         nsCOMPtr<nsIDocShellTreeItem> child;
326         aShellItem->GetChildAt(i, getter_AddRefs(child));
327         if (child) {
328             DumpAWebShell(child, out, aIndent);
329         }
330     }
331     --aIndent;
332     for (i = aIndent; --i >= 0; )
333         fprintf(out, "  ");
334     fputs(">\n", out);
335 }
336 
337 NS_IMETHODIMP
DumpWebShells()338 nsLayoutDebuggingTools::DumpWebShells()
339 {
340     NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
341     DumpAWebShell(mDocShell, stdout, 0);
342     return NS_OK;
343 }
344 
345 static
346 void
DumpContentRecur(nsIDocShell * aDocShell,FILE * out)347 DumpContentRecur(nsIDocShell* aDocShell, FILE* out)
348 {
349 #ifdef DEBUG
350     if (nullptr != aDocShell) {
351         fprintf(out, "docshell=%p \n", static_cast<void*>(aDocShell));
352         nsCOMPtr<nsIDocument> doc(document(aDocShell));
353         if (doc) {
354             dom::Element *root = doc->GetRootElement();
355             if (root) {
356                 root->List(out);
357             }
358         }
359         else {
360             fputs("no document\n", out);
361         }
362         // dump the frames of the sub documents
363         int32_t i, n;
364         aDocShell->GetChildCount(&n);
365         for (i = 0; i < n; ++i) {
366             nsCOMPtr<nsIDocShellTreeItem> child;
367             aDocShell->GetChildAt(i, getter_AddRefs(child));
368             nsCOMPtr<nsIDocShell> childAsShell(do_QueryInterface(child));
369             if (child) {
370                 DumpContentRecur(childAsShell, out);
371             }
372         }
373     }
374 #endif
375 }
376 
377 NS_IMETHODIMP
DumpContent()378 nsLayoutDebuggingTools::DumpContent()
379 {
380     NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
381     DumpContentRecur(mDocShell, stdout);
382     return NS_OK;
383 }
384 
385 static void
DumpFramesRecur(nsIDocShell * aDocShell,FILE * out)386 DumpFramesRecur(nsIDocShell* aDocShell, FILE* out)
387 {
388 #ifdef DEBUG
389     fprintf(out, "webshell=%p \n", static_cast<void*>(aDocShell));
390     nsCOMPtr<nsIPresShell> shell(pres_shell(aDocShell));
391     if (shell) {
392         nsIFrame* root = shell->GetRootFrame();
393         if (root) {
394             root->List(out);
395         }
396     }
397     else {
398         fputs("null pres shell\n", out);
399     }
400 
401     // dump the frames of the sub documents
402     int32_t i, n;
403     aDocShell->GetChildCount(&n);
404     for (i = 0; i < n; ++i) {
405         nsCOMPtr<nsIDocShellTreeItem> child;
406         aDocShell->GetChildAt(i, getter_AddRefs(child));
407         nsCOMPtr<nsIDocShell> childAsShell(do_QueryInterface(child));
408         if (childAsShell) {
409             DumpFramesRecur(childAsShell, out);
410         }
411     }
412 #endif
413 }
414 
415 NS_IMETHODIMP
DumpFrames()416 nsLayoutDebuggingTools::DumpFrames()
417 {
418     NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
419     DumpFramesRecur(mDocShell, stdout);
420     return NS_OK;
421 }
422 
423 static
424 void
DumpViewsRecur(nsIDocShell * aDocShell,FILE * out)425 DumpViewsRecur(nsIDocShell* aDocShell, FILE* out)
426 {
427 #ifdef DEBUG
428     fprintf(out, "docshell=%p \n", static_cast<void*>(aDocShell));
429     RefPtr<nsViewManager> vm(view_manager(aDocShell));
430     if (vm) {
431         nsView* root = vm->GetRootView();
432         if (root) {
433             root->List(out);
434         }
435     }
436     else {
437         fputs("null view manager\n", out);
438     }
439 
440     // dump the views of the sub documents
441     int32_t i, n;
442     aDocShell->GetChildCount(&n);
443     for (i = 0; i < n; i++) {
444         nsCOMPtr<nsIDocShellTreeItem> child;
445         aDocShell->GetChildAt(i, getter_AddRefs(child));
446         nsCOMPtr<nsIDocShell> childAsShell(do_QueryInterface(child));
447         if (childAsShell) {
448             DumpViewsRecur(childAsShell, out);
449         }
450     }
451 #endif // DEBUG
452 }
453 
454 NS_IMETHODIMP
DumpViews()455 nsLayoutDebuggingTools::DumpViews()
456 {
457     NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
458     DumpViewsRecur(mDocShell, stdout);
459     return NS_OK;
460 }
461 
462 NS_IMETHODIMP
DumpStyleSheets()463 nsLayoutDebuggingTools::DumpStyleSheets()
464 {
465     NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
466 #ifdef DEBUG
467     FILE *out = stdout;
468     nsCOMPtr<nsIPresShell> shell(pres_shell(mDocShell));
469     if (shell)
470         shell->ListStyleSheets(out);
471     else
472         fputs("null pres shell\n", out);
473 #endif
474     return NS_OK;
475 }
476 
477 NS_IMETHODIMP
DumpStyleContexts()478 nsLayoutDebuggingTools::DumpStyleContexts()
479 {
480     NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
481 #ifdef DEBUG
482     FILE *out = stdout;
483     nsCOMPtr<nsIPresShell> shell(pres_shell(mDocShell));
484     if (shell) {
485         nsIFrame* root = shell->GetRootFrame();
486         if (!root) {
487             fputs("null root frame\n", out);
488         } else {
489             shell->ListStyleContexts(root, out);
490         }
491     } else {
492         fputs("null pres shell\n", out);
493     }
494 #endif
495     return NS_OK;
496 }
497 
498 NS_IMETHODIMP
DumpReflowStats()499 nsLayoutDebuggingTools::DumpReflowStats()
500 {
501     NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
502 #ifdef DEBUG
503     nsCOMPtr<nsIPresShell> shell(pres_shell(mDocShell));
504     if (shell) {
505 #ifdef MOZ_REFLOW_PERF
506         shell->DumpReflows();
507 #else
508         printf("************************************************\n");
509         printf("Sorry, you have not built with MOZ_REFLOW_PERF=1\n");
510         printf("************************************************\n");
511 #endif
512     }
513 #endif
514     return NS_OK;
515 }
516 
ForceRefresh()517 void nsLayoutDebuggingTools::ForceRefresh()
518 {
519     RefPtr<nsViewManager> vm(view_manager(mDocShell));
520     if (!vm)
521         return;
522     nsView* root = vm->GetRootView();
523     if (root) {
524         vm->InvalidateView(root);
525     }
526 }
527 
528 nsresult
SetBoolPrefAndRefresh(const char * aPrefName,bool aNewVal)529 nsLayoutDebuggingTools::SetBoolPrefAndRefresh(const char * aPrefName,
530                                               bool aNewVal)
531 {
532     NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED);
533 
534     nsIPrefService* prefService = Preferences::GetService();
535     NS_ENSURE_TRUE(prefService && aPrefName, NS_OK);
536 
537     Preferences::SetBool(aPrefName, aNewVal);
538     prefService->SavePrefFile(nullptr);
539 
540     ForceRefresh();
541 
542     return NS_OK;
543 }
544