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 https://mozilla.org/MPL/2.0/. */
6 
7 #include "ChromeObserver.h"
8 
9 #include "nsIBaseWindow.h"
10 #include "nsIWidget.h"
11 #include "nsIFrame.h"
12 
13 #include "nsContentUtils.h"
14 #include "nsView.h"
15 #include "nsPresContext.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/DocumentInlines.h"
18 #include "mozilla/dom/Element.h"
19 #include "mozilla/dom/MutationEventBinding.h"
20 #include "nsXULElement.h"
21 
22 namespace mozilla {
23 namespace dom {
24 
NS_IMPL_ISUPPORTS(ChromeObserver,nsIMutationObserver)25 NS_IMPL_ISUPPORTS(ChromeObserver, nsIMutationObserver)
26 
27 ChromeObserver::ChromeObserver(Document* aDocument)
28     : nsStubMutationObserver(), mDocument(aDocument) {}
29 
30 ChromeObserver::~ChromeObserver() = default;
31 
Init()32 void ChromeObserver::Init() {
33   mDocument->AddMutationObserver(this);
34   Element* rootElement = mDocument->GetRootElement();
35   if (!rootElement) {
36     return;
37   }
38   nsAutoScriptBlocker scriptBlocker;
39   uint32_t attributeCount = rootElement->GetAttrCount();
40   for (uint32_t i = 0; i < attributeCount; i++) {
41     BorrowedAttrInfo info = rootElement->GetAttrInfoAt(i);
42     const nsAttrName* name = info.mName;
43     if (name->LocalName() == nsGkAtoms::chromemargin) {
44       // Some linux windows managers have an issue when the chrome margin is
45       // applied while the browser is loading (bug 1598848). For now, skip
46       // applying this attribute when initializing.
47       continue;
48     }
49     AttributeChanged(rootElement, name->NamespaceID(), name->LocalName(),
50                      MutationEvent_Binding::ADDITION, nullptr);
51   }
52 }
53 
GetWindowWidget()54 nsIWidget* ChromeObserver::GetWindowWidget() {
55   // only top level chrome documents can set the titlebar color
56   if (mDocument && mDocument->IsRootDisplayDocument()) {
57     nsCOMPtr<nsISupports> container = mDocument->GetContainer();
58     nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
59     if (baseWindow) {
60       nsCOMPtr<nsIWidget> mainWidget;
61       baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
62       return mainWidget;
63     }
64   }
65   return nullptr;
66 }
67 
68 class SetDrawInTitleBarEvent : public Runnable {
69  public:
SetDrawInTitleBarEvent(nsIWidget * aWidget,bool aState)70   SetDrawInTitleBarEvent(nsIWidget* aWidget, bool aState)
71       : mozilla::Runnable("SetDrawInTitleBarEvent"),
72         mWidget(aWidget),
73         mState(aState) {}
74 
Run()75   NS_IMETHOD Run() override {
76     NS_ASSERTION(mWidget,
77                  "You shouldn't call this runnable with a null widget!");
78 
79     mWidget->SetDrawsInTitlebar(mState);
80     return NS_OK;
81   }
82 
83  private:
84   nsCOMPtr<nsIWidget> mWidget;
85   bool mState;
86 };
87 
SetDrawsInTitlebar(bool aState)88 void ChromeObserver::SetDrawsInTitlebar(bool aState) {
89   nsIWidget* mainWidget = GetWindowWidget();
90   if (mainWidget) {
91     nsContentUtils::AddScriptRunner(
92         new SetDrawInTitleBarEvent(mainWidget, aState));
93   }
94 }
95 
SetDrawsTitle(bool aState)96 void ChromeObserver::SetDrawsTitle(bool aState) {
97   nsIWidget* mainWidget = GetWindowWidget();
98   if (mainWidget) {
99     // We can do this synchronously because SetDrawsTitle doesn't have any
100     // synchronous effects apart from a harmless invalidation.
101     mainWidget->SetDrawsTitle(aState);
102   }
103 }
104 
105 class MarginSetter : public Runnable {
106  public:
MarginSetter(nsIWidget * aWidget)107   explicit MarginSetter(nsIWidget* aWidget)
108       : mozilla::Runnable("MarginSetter"),
109         mWidget(aWidget),
110         mMargin(-1, -1, -1, -1) {}
MarginSetter(nsIWidget * aWidget,const LayoutDeviceIntMargin & aMargin)111   MarginSetter(nsIWidget* aWidget, const LayoutDeviceIntMargin& aMargin)
112       : mozilla::Runnable("MarginSetter"), mWidget(aWidget), mMargin(aMargin) {}
113 
Run()114   NS_IMETHOD Run() override {
115     // SetNonClientMargins can dispatch native events, hence doing
116     // it off a script runner.
117     mWidget->SetNonClientMargins(mMargin);
118     return NS_OK;
119   }
120 
121  private:
122   nsCOMPtr<nsIWidget> mWidget;
123   LayoutDeviceIntMargin mMargin;
124 };
125 
SetChromeMargins(const nsAttrValue * aValue)126 void ChromeObserver::SetChromeMargins(const nsAttrValue* aValue) {
127   if (!aValue) return;
128 
129   nsIWidget* mainWidget = GetWindowWidget();
130   if (!mainWidget) return;
131 
132   // top, right, bottom, left - see nsAttrValue
133   nsIntMargin margins;
134   bool gotMargins = false;
135 
136   if (aValue->Type() == nsAttrValue::eIntMarginValue) {
137     gotMargins = aValue->GetIntMarginValue(margins);
138   } else {
139     nsAutoString tmp;
140     aValue->ToString(tmp);
141     gotMargins = nsContentUtils::ParseIntMarginValue(tmp, margins);
142   }
143   if (gotMargins) {
144     nsContentUtils::AddScriptRunner(new MarginSetter(
145         mainWidget, LayoutDeviceIntMargin::FromUnknownMargin(margins)));
146   }
147 }
148 
AttributeChanged(dom::Element * aElement,int32_t aNamespaceID,nsAtom * aName,int32_t aModType,const nsAttrValue * aOldValue)149 void ChromeObserver::AttributeChanged(dom::Element* aElement,
150                                       int32_t aNamespaceID, nsAtom* aName,
151                                       int32_t aModType,
152                                       const nsAttrValue* aOldValue) {
153   // We only care about changes to the root element.
154   if (!mDocument || aElement != mDocument->GetRootElement()) {
155     return;
156   }
157 
158   const nsAttrValue* value = aElement->GetParsedAttr(aName, aNamespaceID);
159   if (value) {
160     // Hide chrome if needed
161     if (aName == nsGkAtoms::hidechrome) {
162       HideWindowChrome(value->Equals(u"true"_ns, eCaseMatters));
163     } else if (aName == nsGkAtoms::chromemargin) {
164       SetChromeMargins(value);
165     }
166     // title and drawintitlebar are settable on
167     // any root node (windows, dialogs, etc)
168     else if (aName == nsGkAtoms::title) {
169       mDocument->NotifyPossibleTitleChange(false);
170     } else if (aName == nsGkAtoms::drawintitlebar) {
171       SetDrawsInTitlebar(value->Equals(u"true"_ns, eCaseMatters));
172     } else if (aName == nsGkAtoms::drawtitle) {
173       SetDrawsTitle(value->Equals(u"true"_ns, eCaseMatters));
174     } else if (aName == nsGkAtoms::localedir) {
175       // if the localedir changed on the root element, reset the document
176       // direction
177       mDocument->ResetDocumentDirection();
178     } else if (aName == nsGkAtoms::lwtheme ||
179                aName == nsGkAtoms::lwthemetextcolor) {
180       // if the lwtheme changed, make sure to reset the document lwtheme
181       // cache
182       mDocument->ResetDocumentLWTheme();
183     }
184   } else {
185     if (aName == nsGkAtoms::hidechrome) {
186       HideWindowChrome(false);
187     } else if (aName == nsGkAtoms::chromemargin) {
188       ResetChromeMargins();
189     } else if (aName == nsGkAtoms::localedir) {
190       // if the localedir changed on the root element, reset the document
191       // direction
192       mDocument->ResetDocumentDirection();
193     } else if ((aName == nsGkAtoms::lwtheme ||
194                 aName == nsGkAtoms::lwthemetextcolor)) {
195       // if the lwtheme changed, make sure to restyle appropriately
196       mDocument->ResetDocumentLWTheme();
197     } else if (aName == nsGkAtoms::drawintitlebar) {
198       SetDrawsInTitlebar(false);
199     } else if (aName == nsGkAtoms::drawtitle) {
200       SetDrawsTitle(false);
201     }
202   }
203 }
204 
NodeWillBeDestroyed(const nsINode * aNode)205 void ChromeObserver::NodeWillBeDestroyed(const nsINode* aNode) {
206   mDocument = nullptr;
207 }
208 
ResetChromeMargins()209 void ChromeObserver::ResetChromeMargins() {
210   nsIWidget* mainWidget = GetWindowWidget();
211   if (!mainWidget) return;
212   // See nsIWidget
213   nsContentUtils::AddScriptRunner(new MarginSetter(mainWidget));
214 }
215 
HideWindowChrome(bool aShouldHide)216 nsresult ChromeObserver::HideWindowChrome(bool aShouldHide) {
217   // only top level chrome documents can hide the window chrome
218   if (!mDocument->IsRootDisplayDocument()) return NS_OK;
219 
220   nsPresContext* presContext = mDocument->GetPresContext();
221 
222   if (presContext && presContext->IsChrome()) {
223     nsIFrame* frame = mDocument->GetDocumentElement()->GetPrimaryFrame();
224 
225     if (frame) {
226       nsView* view = frame->GetClosestView();
227 
228       if (view) {
229         nsIWidget* w = view->GetWidget();
230         NS_ENSURE_STATE(w);
231         w->HideWindowChrome(aShouldHide);
232       }
233     }
234   }
235 
236   return NS_OK;
237 }
238 
239 }  // namespace dom
240 }  // namespace mozilla
241