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