1 | /* |
2 | * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) |
3 | * Copyright (C) 2001 Peter Kelly (pmk@post.com) |
4 | * Copyright (C) 2006-2018 Apple Inc. All rights reserved. |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with this library; if not, write to the Free Software |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | */ |
20 | |
21 | #include "config.h" |
22 | #include "WindowProxy.h" |
23 | |
24 | #include "CommonVM.h" |
25 | #include "Frame.h" |
26 | #include "GCController.h" |
27 | #include "JSWindowProxy.h" |
28 | #include "Page.h" |
29 | #include "PageConsoleClient.h" |
30 | #include "PageGroup.h" |
31 | #include "RemoteFrame.h" |
32 | #include "ScriptController.h" |
33 | #include "runtime_root.h" |
34 | #include <JavaScriptCore/JSLock.h> |
35 | #include <JavaScriptCore/WeakGCMapInlines.h> |
36 | #include <wtf/MemoryPressureHandler.h> |
37 | |
38 | namespace WebCore { |
39 | |
40 | using namespace JSC; |
41 | |
42 | static void collectGarbageAfterWindowProxyDestruction() |
43 | { |
44 | // Make sure to GC Extra Soon(tm) during memory pressure conditions |
45 | // to soften high peaks of memory usage during navigation. |
46 | if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) { |
47 | // NOTE: We do the collection on next runloop to ensure that there's no pointer |
48 | // to the window object on the stack. |
49 | GCController::singleton().garbageCollectOnNextRunLoop(); |
50 | } else |
51 | GCController::singleton().garbageCollectSoon(); |
52 | } |
53 | |
54 | WindowProxy::WindowProxy(AbstractFrame& frame) |
55 | : m_frame(&frame) |
56 | { |
57 | } |
58 | |
59 | WindowProxy::~WindowProxy() |
60 | { |
61 | ASSERT(!m_frame); |
62 | ASSERT(m_jsWindowProxies.isEmpty()); |
63 | } |
64 | |
65 | void WindowProxy::detachFromFrame() |
66 | { |
67 | ASSERT(m_frame); |
68 | |
69 | m_frame = nullptr; |
70 | |
71 | // It's likely that destroying windowProxies will create a lot of garbage. |
72 | if (!m_jsWindowProxies.isEmpty()) { |
73 | while (!m_jsWindowProxies.isEmpty()) { |
74 | auto it = m_jsWindowProxies.begin(); |
75 | it->value->window()->setConsoleClient(nullptr); |
76 | destroyJSWindowProxy(*it->key); |
77 | } |
78 | collectGarbageAfterWindowProxyDestruction(); |
79 | } |
80 | } |
81 | |
82 | void WindowProxy::destroyJSWindowProxy(DOMWrapperWorld& world) |
83 | { |
84 | ASSERT(m_jsWindowProxies.contains(&world)); |
85 | m_jsWindowProxies.remove(&world); |
86 | world.didDestroyWindowProxy(this); |
87 | } |
88 | |
89 | JSWindowProxy& WindowProxy::createJSWindowProxy(DOMWrapperWorld& world) |
90 | { |
91 | ASSERT(m_frame); |
92 | |
93 | ASSERT(!m_jsWindowProxies.contains(&world)); |
94 | ASSERT(m_frame->window()); |
95 | |
96 | VM& vm = world.vm(); |
97 | |
98 | Strong<JSWindowProxy> jsWindowProxy(vm, &JSWindowProxy::create(vm, *m_frame->window(), world)); |
99 | Strong<JSWindowProxy> jsWindowProxy2(jsWindowProxy); |
100 | m_jsWindowProxies.add(&world, jsWindowProxy); |
101 | world.didCreateWindowProxy(this); |
102 | return *jsWindowProxy.get(); |
103 | } |
104 | |
105 | Vector<JSC::Strong<JSWindowProxy>> WindowProxy::jsWindowProxiesAsVector() const |
106 | { |
107 | return copyToVector(m_jsWindowProxies.values()); |
108 | } |
109 | |
110 | JSDOMGlobalObject* WindowProxy::globalObject(DOMWrapperWorld& world) |
111 | { |
112 | if (auto* windowProxy = jsWindowProxy(world)) |
113 | return windowProxy->window(); |
114 | return nullptr; |
115 | } |
116 | |
117 | JSWindowProxy& WindowProxy::createJSWindowProxyWithInitializedScript(DOMWrapperWorld& world) |
118 | { |
119 | ASSERT(m_frame); |
120 | |
121 | JSLockHolder lock(world.vm()); |
122 | auto& windowProxy = createJSWindowProxy(world); |
123 | if (is<Frame>(*m_frame)) |
124 | downcast<Frame>(*m_frame).script().initScriptForWindowProxy(windowProxy); |
125 | return windowProxy; |
126 | } |
127 | |
128 | void WindowProxy::clearJSWindowProxiesNotMatchingDOMWindow(AbstractDOMWindow* newDOMWindow, bool goingIntoPageCache) |
129 | { |
130 | if (m_jsWindowProxies.isEmpty()) |
131 | return; |
132 | |
133 | JSLockHolder lock(commonVM()); |
134 | |
135 | for (auto& windowProxy : jsWindowProxiesAsVector()) { |
136 | if (&windowProxy->wrapped() == newDOMWindow) |
137 | continue; |
138 | |
139 | // Clear the debugger and console from the current window before setting the new window. |
140 | windowProxy->attachDebugger(nullptr); |
141 | windowProxy->window()->setConsoleClient(nullptr); |
142 | if (auto* jsDOMWindow = jsDynamicCast<JSDOMWindowBase*>(*windowProxy->vm(), windowProxy->window())) |
143 | jsDOMWindow->willRemoveFromWindowProxy(); |
144 | } |
145 | |
146 | // It's likely that resetting our windows created a lot of garbage, unless |
147 | // it went in a back/forward cache. |
148 | if (!goingIntoPageCache) |
149 | collectGarbageAfterWindowProxyDestruction(); |
150 | } |
151 | |
152 | void WindowProxy::setDOMWindow(AbstractDOMWindow* newDOMWindow) |
153 | { |
154 | ASSERT(newDOMWindow); |
155 | |
156 | if (m_jsWindowProxies.isEmpty()) |
157 | return; |
158 | |
159 | ASSERT(m_frame); |
160 | |
161 | JSLockHolder lock(commonVM()); |
162 | |
163 | for (auto& windowProxy : jsWindowProxiesAsVector()) { |
164 | if (&windowProxy->wrapped() == newDOMWindow) |
165 | continue; |
166 | |
167 | windowProxy->setWindow(*newDOMWindow); |
168 | |
169 | ScriptController* scriptController = nullptr; |
170 | Page* page = nullptr; |
171 | if (is<Frame>(*m_frame)) { |
172 | auto& frame = downcast<Frame>(*m_frame); |
173 | scriptController = &frame.script(); |
174 | page = frame.page(); |
175 | } |
176 | |
177 | // ScriptController's m_cacheableBindingRootObject persists between page navigations |
178 | // so needs to know about the new JSDOMWindow. |
179 | if (auto* cacheableBindingRootObject = scriptController ? scriptController->existingCacheableBindingRootObject() : nullptr) |
180 | cacheableBindingRootObject->updateGlobalObject(windowProxy->window()); |
181 | |
182 | windowProxy->attachDebugger(page ? page->debugger() : nullptr); |
183 | if (page) |
184 | windowProxy->window()->setProfileGroup(page->group().identifier()); |
185 | windowProxy->window()->setConsoleClient(page ? &page->console() : nullptr); |
186 | } |
187 | } |
188 | |
189 | void WindowProxy::attachDebugger(JSC::Debugger* debugger) |
190 | { |
191 | for (auto& windowProxy : m_jsWindowProxies.values()) |
192 | windowProxy->attachDebugger(debugger); |
193 | } |
194 | |
195 | AbstractDOMWindow* WindowProxy::window() const |
196 | { |
197 | return m_frame ? m_frame->window() : nullptr; |
198 | } |
199 | |
200 | } // namespace WebCore |
201 | |