OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/renderer/extensions/extension_dispatcher.h" | 5 #include "chrome/renderer/extensions/extension_dispatcher.h" |
6 | 6 |
7 #include "base/callback.h" | 7 #include "base/callback.h" |
8 #include "base/command_line.h" | 8 #include "base/command_line.h" |
9 #include "base/memory/scoped_ptr.h" | 9 #include "base/memory/scoped_ptr.h" |
10 #include "base/string_piece.h" | 10 #include "base/string_piece.h" |
(...skipping 22 matching lines...) Expand all Loading... |
33 #include "chrome/renderer/extensions/extension_request_sender.h" | 33 #include "chrome/renderer/extensions/extension_request_sender.h" |
34 #include "chrome/renderer/extensions/file_browser_handler_custom_bindings.h" | 34 #include "chrome/renderer/extensions/file_browser_handler_custom_bindings.h" |
35 #include "chrome/renderer/extensions/file_browser_private_custom_bindings.h" | 35 #include "chrome/renderer/extensions/file_browser_private_custom_bindings.h" |
36 #include "chrome/renderer/extensions/i18n_custom_bindings.h" | 36 #include "chrome/renderer/extensions/i18n_custom_bindings.h" |
37 #include "chrome/renderer/extensions/media_gallery_custom_bindings.h" | 37 #include "chrome/renderer/extensions/media_gallery_custom_bindings.h" |
38 #include "chrome/renderer/extensions/miscellaneous_bindings.h" | 38 #include "chrome/renderer/extensions/miscellaneous_bindings.h" |
39 #include "chrome/renderer/extensions/page_actions_custom_bindings.h" | 39 #include "chrome/renderer/extensions/page_actions_custom_bindings.h" |
40 #include "chrome/renderer/extensions/page_capture_custom_bindings.h" | 40 #include "chrome/renderer/extensions/page_capture_custom_bindings.h" |
41 #include "chrome/renderer/extensions/send_request_natives.h" | 41 #include "chrome/renderer/extensions/send_request_natives.h" |
42 #include "chrome/renderer/extensions/set_icon_natives.h" | 42 #include "chrome/renderer/extensions/set_icon_natives.h" |
| 43 #include "chrome/renderer/extensions/tab_finder.h" |
43 #include "chrome/renderer/extensions/tabs_custom_bindings.h" | 44 #include "chrome/renderer/extensions/tabs_custom_bindings.h" |
44 #include "chrome/renderer/extensions/tts_custom_bindings.h" | 45 #include "chrome/renderer/extensions/tts_custom_bindings.h" |
45 #include "chrome/renderer/extensions/user_script_slave.h" | 46 #include "chrome/renderer/extensions/user_script_slave.h" |
46 #include "chrome/renderer/extensions/web_request_custom_bindings.h" | 47 #include "chrome/renderer/extensions/web_request_custom_bindings.h" |
47 #include "chrome/renderer/extensions/webstore_bindings.h" | 48 #include "chrome/renderer/extensions/webstore_bindings.h" |
48 #include "chrome/renderer/module_system.h" | 49 #include "chrome/renderer/module_system.h" |
49 #include "chrome/renderer/native_handler.h" | 50 #include "chrome/renderer/native_handler.h" |
50 #include "chrome/renderer/resource_bundle_source_map.h" | 51 #include "chrome/renderer/resource_bundle_source_map.h" |
51 #include "content/public/renderer/render_thread.h" | 52 #include "content/public/renderer/render_thread.h" |
52 #include "content/public/renderer/render_view.h" | 53 #include "content/public/renderer/render_view.h" |
(...skipping 12 matching lines...) Expand all Loading... |
65 | 66 |
66 using WebKit::WebDataSource; | 67 using WebKit::WebDataSource; |
67 using WebKit::WebDocument; | 68 using WebKit::WebDocument; |
68 using WebKit::WebFrame; | 69 using WebKit::WebFrame; |
69 using WebKit::WebScopedUserGesture; | 70 using WebKit::WebScopedUserGesture; |
70 using WebKit::WebSecurityPolicy; | 71 using WebKit::WebSecurityPolicy; |
71 using WebKit::WebString; | 72 using WebKit::WebString; |
72 using WebKit::WebVector; | 73 using WebKit::WebVector; |
73 using WebKit::WebView; | 74 using WebKit::WebView; |
74 using content::RenderThread; | 75 using content::RenderThread; |
| 76 using content::RenderView; |
75 using extensions::ApiDefinitionsNatives; | 77 using extensions::ApiDefinitionsNatives; |
76 using extensions::AppWindowCustomBindings; | 78 using extensions::AppWindowCustomBindings; |
77 using extensions::ContextMenusCustomBindings; | 79 using extensions::ContextMenusCustomBindings; |
78 using extensions::Extension; | 80 using extensions::Extension; |
79 using extensions::ExperimentalAppCustomBindings; | 81 using extensions::ExperimentalAppCustomBindings; |
80 using extensions::ExperimentalUsbCustomBindings; | 82 using extensions::ExperimentalUsbCustomBindings; |
81 using extensions::ExtensionAPI; | 83 using extensions::ExtensionAPI; |
82 using extensions::ExtensionCustomBindings; | 84 using extensions::ExtensionCustomBindings; |
83 using extensions::Feature; | 85 using extensions::Feature; |
84 using extensions::FileBrowserHandlerCustomBindings; | 86 using extensions::FileBrowserHandlerCustomBindings; |
85 using extensions::FileBrowserPrivateCustomBindings; | 87 using extensions::FileBrowserPrivateCustomBindings; |
86 using extensions::I18NCustomBindings; | 88 using extensions::I18NCustomBindings; |
87 using extensions::MiscellaneousBindings; | 89 using extensions::MiscellaneousBindings; |
88 using extensions::MediaGalleryCustomBindings; | 90 using extensions::MediaGalleryCustomBindings; |
89 using extensions::PageActionsCustomBindings; | 91 using extensions::PageActionsCustomBindings; |
90 using extensions::PageCaptureCustomBindings; | 92 using extensions::PageCaptureCustomBindings; |
91 using extensions::SendRequestNatives; | 93 using extensions::SendRequestNatives; |
92 using extensions::SetIconNatives; | 94 using extensions::SetIconNatives; |
93 using extensions::TTSCustomBindings; | 95 using extensions::TTSCustomBindings; |
| 96 using extensions::TabFinder; |
94 using extensions::TabsCustomBindings; | 97 using extensions::TabsCustomBindings; |
95 using extensions::UpdatedExtensionPermissionsInfo; | 98 using extensions::UpdatedExtensionPermissionsInfo; |
96 using extensions::WebRequestCustomBindings; | 99 using extensions::WebRequestCustomBindings; |
97 | 100 |
98 namespace { | 101 namespace { |
99 | 102 |
100 static const int64 kInitialExtensionIdleHandlerDelayMs = 5*1000; | 103 static const int64 kInitialExtensionIdleHandlerDelayMs = 5*1000; |
101 static const int64 kMaxExtensionIdleHandlerDelayMs = 5*60*1000; | 104 static const int64 kMaxExtensionIdleHandlerDelayMs = 5*60*1000; |
102 static const char kEventDispatchFunction[] = "Event.dispatchJSON"; | 105 static const char kEventDispatchFunction[] = "Event.dispatchJSON"; |
103 static const char kOnUnloadEvent[] = "runtime.onBackgroundPageUnloadingSoon"; | 106 static const char kOnUnloadEvent[] = "runtime.onBackgroundPageUnloadingSoon"; |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
146 RouteFunction("DecrementKeepaliveCount", | 149 RouteFunction("DecrementKeepaliveCount", |
147 base::Bind(&LazyBackgroundPageNativeHandler::DecrementKeepaliveCount, | 150 base::Bind(&LazyBackgroundPageNativeHandler::DecrementKeepaliveCount, |
148 base::Unretained(this))); | 151 base::Unretained(this))); |
149 } | 152 } |
150 | 153 |
151 v8::Handle<v8::Value> IncrementKeepaliveCount(const v8::Arguments& args) { | 154 v8::Handle<v8::Value> IncrementKeepaliveCount(const v8::Arguments& args) { |
152 ChromeV8Context* context = | 155 ChromeV8Context* context = |
153 extension_dispatcher()->v8_context_set().GetCurrent(); | 156 extension_dispatcher()->v8_context_set().GetCurrent(); |
154 if (!context) | 157 if (!context) |
155 return v8::Undefined(); | 158 return v8::Undefined(); |
156 content::RenderView* render_view = context->GetRenderView(); | 159 RenderView* render_view = context->GetRenderView(); |
157 if (IsContextLazyBackgroundPage(render_view, context->extension())) { | 160 if (IsContextLazyBackgroundPage(render_view, context->extension())) { |
158 render_view->Send(new ExtensionHostMsg_IncrementLazyKeepaliveCount( | 161 render_view->Send(new ExtensionHostMsg_IncrementLazyKeepaliveCount( |
159 render_view->GetRoutingID())); | 162 render_view->GetRoutingID())); |
160 } | 163 } |
161 return v8::Undefined(); | 164 return v8::Undefined(); |
162 } | 165 } |
163 | 166 |
164 v8::Handle<v8::Value> DecrementKeepaliveCount(const v8::Arguments& args) { | 167 v8::Handle<v8::Value> DecrementKeepaliveCount(const v8::Arguments& args) { |
165 ChromeV8Context* context = | 168 ChromeV8Context* context = |
166 extension_dispatcher()->v8_context_set().GetCurrent(); | 169 extension_dispatcher()->v8_context_set().GetCurrent(); |
167 if (!context) | 170 if (!context) |
168 return v8::Undefined(); | 171 return v8::Undefined(); |
169 content::RenderView* render_view = context->GetRenderView(); | 172 RenderView* render_view = context->GetRenderView(); |
170 if (IsContextLazyBackgroundPage(render_view, context->extension())) { | 173 if (IsContextLazyBackgroundPage(render_view, context->extension())) { |
171 render_view->Send(new ExtensionHostMsg_DecrementLazyKeepaliveCount( | 174 render_view->Send(new ExtensionHostMsg_DecrementLazyKeepaliveCount( |
172 render_view->GetRoutingID())); | 175 render_view->GetRoutingID())); |
173 } | 176 } |
174 return v8::Undefined(); | 177 return v8::Undefined(); |
175 } | 178 } |
176 | 179 |
177 private: | 180 private: |
178 bool IsContextLazyBackgroundPage(content::RenderView* render_view, | 181 bool IsContextLazyBackgroundPage(RenderView* render_view, |
179 const Extension* extension) { | 182 const Extension* extension) { |
180 if (!render_view) | 183 if (!render_view) |
181 return false; | 184 return false; |
182 | 185 |
183 ExtensionHelper* helper = ExtensionHelper::Get(render_view); | 186 ExtensionHelper* helper = ExtensionHelper::Get(render_view); |
184 return (extension && extension->has_lazy_background_page() && | 187 return (extension && extension->has_lazy_background_page() && |
185 helper->view_type() == chrome::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); | 188 helper->view_type() == chrome::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE); |
186 } | 189 } |
187 }; | 190 }; |
188 | 191 |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
258 IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnDeliverMessage) | 261 IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnDeliverMessage) |
259 IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnDisconnect, | 262 IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnDisconnect, |
260 OnDispatchOnDisconnect) | 263 OnDispatchOnDisconnect) |
261 IPC_MESSAGE_HANDLER(ExtensionMsg_SetFunctionNames, OnSetFunctionNames) | 264 IPC_MESSAGE_HANDLER(ExtensionMsg_SetFunctionNames, OnSetFunctionNames) |
262 IPC_MESSAGE_HANDLER(ExtensionMsg_Loaded, OnLoaded) | 265 IPC_MESSAGE_HANDLER(ExtensionMsg_Loaded, OnLoaded) |
263 IPC_MESSAGE_HANDLER(ExtensionMsg_Unloaded, OnUnloaded) | 266 IPC_MESSAGE_HANDLER(ExtensionMsg_Unloaded, OnUnloaded) |
264 IPC_MESSAGE_HANDLER(ExtensionMsg_SetScriptingWhitelist, | 267 IPC_MESSAGE_HANDLER(ExtensionMsg_SetScriptingWhitelist, |
265 OnSetScriptingWhitelist) | 268 OnSetScriptingWhitelist) |
266 IPC_MESSAGE_HANDLER(ExtensionMsg_ActivateExtension, OnActivateExtension) | 269 IPC_MESSAGE_HANDLER(ExtensionMsg_ActivateExtension, OnActivateExtension) |
267 IPC_MESSAGE_HANDLER(ExtensionMsg_UpdatePermissions, OnUpdatePermissions) | 270 IPC_MESSAGE_HANDLER(ExtensionMsg_UpdatePermissions, OnUpdatePermissions) |
| 271 IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateTabSpecificPermissions, |
| 272 OnUpdateTabSpecificPermissions) |
| 273 IPC_MESSAGE_HANDLER(ExtensionMsg_ClearTabSpecificPermissions, |
| 274 OnClearTabSpecificPermissions) |
268 IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts) | 275 IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts) |
269 IPC_MESSAGE_HANDLER(ExtensionMsg_UsingWebRequestAPI, OnUsingWebRequestAPI) | 276 IPC_MESSAGE_HANDLER(ExtensionMsg_UsingWebRequestAPI, OnUsingWebRequestAPI) |
270 IPC_MESSAGE_HANDLER(ExtensionMsg_ShouldUnload, OnShouldUnload) | 277 IPC_MESSAGE_HANDLER(ExtensionMsg_ShouldUnload, OnShouldUnload) |
271 IPC_MESSAGE_HANDLER(ExtensionMsg_Unload, OnUnload) | 278 IPC_MESSAGE_HANDLER(ExtensionMsg_Unload, OnUnload) |
272 IPC_MESSAGE_UNHANDLED(handled = false) | 279 IPC_MESSAGE_UNHANDLED(handled = false) |
273 IPC_END_MESSAGE_MAP() | 280 IPC_END_MESSAGE_MAP() |
274 | 281 |
275 return handled; | 282 return handled; |
276 } | 283 } |
277 | 284 |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
339 if (is_extension_process_) { | 346 if (is_extension_process_) { |
340 RenderThread::Get()->ScheduleIdleHandler( | 347 RenderThread::Get()->ScheduleIdleHandler( |
341 kInitialExtensionIdleHandlerDelayMs); | 348 kInitialExtensionIdleHandlerDelayMs); |
342 } | 349 } |
343 | 350 |
344 // Tell the browser process when an event has been dispatched with a lazy | 351 // Tell the browser process when an event has been dispatched with a lazy |
345 // background page active. | 352 // background page active. |
346 const Extension* extension = extensions_.GetByID(extension_id); | 353 const Extension* extension = extensions_.GetByID(extension_id); |
347 if (extension && extension->has_lazy_background_page() && | 354 if (extension && extension->has_lazy_background_page() && |
348 function_name == kEventDispatchFunction) { | 355 function_name == kEventDispatchFunction) { |
349 content::RenderView* background_view = | 356 RenderView* background_view = |
350 ExtensionHelper::GetBackgroundPage(extension_id); | 357 ExtensionHelper::GetBackgroundPage(extension_id); |
351 if (background_view) { | 358 if (background_view) { |
352 background_view->Send(new ExtensionHostMsg_EventAck( | 359 background_view->Send(new ExtensionHostMsg_EventAck( |
353 background_view->GetRoutingID())); | 360 background_view->GetRoutingID())); |
354 } | 361 } |
355 } | 362 } |
356 } | 363 } |
357 | 364 |
358 void ExtensionDispatcher::OnDispatchOnConnect( | 365 void ExtensionDispatcher::OnDispatchOnConnect( |
359 int target_port_id, | 366 int target_port_id, |
(...skipping 412 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
772 // whitelist entries need to be updated when the kManagement permission | 779 // whitelist entries need to be updated when the kManagement permission |
773 // changes. | 780 // changes. |
774 if (extension->HasAPIPermission(ExtensionAPIPermission::kManagement)) { | 781 if (extension->HasAPIPermission(ExtensionAPIPermission::kManagement)) { |
775 WebSecurityPolicy::addOriginAccessWhitelistEntry( | 782 WebSecurityPolicy::addOriginAccessWhitelistEntry( |
776 extension->url(), | 783 extension->url(), |
777 WebString::fromUTF8(chrome::kChromeUIScheme), | 784 WebString::fromUTF8(chrome::kChromeUIScheme), |
778 WebString::fromUTF8(chrome::kChromeUIExtensionIconHost), | 785 WebString::fromUTF8(chrome::kChromeUIExtensionIconHost), |
779 false); | 786 false); |
780 } | 787 } |
781 | 788 |
782 UpdateOriginPermissions(UpdatedExtensionPermissionsInfo::ADDED, | 789 AddOrRemoveOriginPermissions( |
783 extension, | 790 UpdatedExtensionPermissionsInfo::ADDED, |
784 extension->GetActivePermissions()->explicit_hosts()); | 791 extension, |
| 792 extension->GetActivePermissions()->explicit_hosts()); |
785 } | 793 } |
786 | 794 |
787 void ExtensionDispatcher::UpdateOriginPermissions( | 795 void ExtensionDispatcher::AddOrRemoveOriginPermissions( |
788 UpdatedExtensionPermissionsInfo::Reason reason, | 796 UpdatedExtensionPermissionsInfo::Reason reason, |
789 const Extension* extension, | 797 const Extension* extension, |
790 const URLPatternSet& origins) { | 798 const URLPatternSet& origins) { |
791 for (URLPatternSet::const_iterator i = origins.begin(); | 799 for (URLPatternSet::const_iterator i = origins.begin(); |
792 i != origins.end(); ++i) { | 800 i != origins.end(); ++i) { |
793 const char* schemes[] = { | 801 const char* schemes[] = { |
794 chrome::kHttpScheme, | 802 chrome::kHttpScheme, |
795 chrome::kHttpsScheme, | 803 chrome::kHttpsScheme, |
796 chrome::kFileScheme, | 804 chrome::kFileScheme, |
797 chrome::kChromeUIScheme, | 805 chrome::kChromeUIScheme, |
(...skipping 23 matching lines...) Expand all Loading... |
821 return; | 829 return; |
822 | 830 |
823 scoped_refptr<const ExtensionPermissionSet> delta = | 831 scoped_refptr<const ExtensionPermissionSet> delta = |
824 new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts); | 832 new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts); |
825 scoped_refptr<const ExtensionPermissionSet> old_active = | 833 scoped_refptr<const ExtensionPermissionSet> old_active = |
826 extension->GetActivePermissions(); | 834 extension->GetActivePermissions(); |
827 UpdatedExtensionPermissionsInfo::Reason reason = | 835 UpdatedExtensionPermissionsInfo::Reason reason = |
828 static_cast<UpdatedExtensionPermissionsInfo::Reason>(reason_id); | 836 static_cast<UpdatedExtensionPermissionsInfo::Reason>(reason_id); |
829 | 837 |
830 const ExtensionPermissionSet* new_active = NULL; | 838 const ExtensionPermissionSet* new_active = NULL; |
831 if (reason == UpdatedExtensionPermissionsInfo::ADDED) { | 839 switch (reason) { |
832 new_active = ExtensionPermissionSet::CreateUnion(old_active, delta); | 840 case UpdatedExtensionPermissionsInfo::ADDED: |
833 } else { | 841 new_active = ExtensionPermissionSet::CreateUnion(old_active, delta); |
834 CHECK_EQ(UpdatedExtensionPermissionsInfo::REMOVED, reason); | 842 break; |
835 new_active = ExtensionPermissionSet::CreateDifference(old_active, delta); | 843 case UpdatedExtensionPermissionsInfo::REMOVED: |
| 844 new_active = ExtensionPermissionSet::CreateDifference(old_active, delta); |
| 845 break; |
836 } | 846 } |
837 | 847 |
838 extension->SetActivePermissions(new_active); | 848 extension->SetActivePermissions(new_active); |
839 UpdateOriginPermissions(reason, extension, explicit_hosts); | 849 AddOrRemoveOriginPermissions(reason, extension, explicit_hosts); |
| 850 } |
| 851 |
| 852 void ExtensionDispatcher::OnUpdateTabSpecificPermissions( |
| 853 int page_id, |
| 854 int tab_id, |
| 855 const std::string& extension_id, |
| 856 const URLPatternSet& origin_set) { |
| 857 RenderView* view = TabFinder::Find(tab_id); |
| 858 |
| 859 // For now, the message should only be sent to the render view that contains |
| 860 // the target tab. This may change. Either way, if this is the target tab it |
| 861 // gives us the chance to check against the page ID to avoid races. |
| 862 DCHECK(view); |
| 863 if (view && view->GetPageId() != page_id) |
| 864 return; |
| 865 |
| 866 const Extension* extension = extensions_.GetByID(extension_id); |
| 867 if (!extension) |
| 868 return; |
| 869 |
| 870 extension->SetTabSpecificHostPermissions(tab_id, origin_set); |
| 871 } |
| 872 |
| 873 void ExtensionDispatcher::OnClearTabSpecificPermissions( |
| 874 int tab_id, |
| 875 const std::vector<std::string>& extension_ids) { |
| 876 for (std::vector<std::string>::const_iterator it = extension_ids.begin(); |
| 877 it != extension_ids.end(); ++it) { |
| 878 const Extension* extension = extensions_.GetByID(*it); |
| 879 if (extension) |
| 880 extension->ClearTabSpecificHostPermissions(tab_id); |
| 881 } |
840 } | 882 } |
841 | 883 |
842 void ExtensionDispatcher::OnUpdateUserScripts( | 884 void ExtensionDispatcher::OnUpdateUserScripts( |
843 base::SharedMemoryHandle scripts) { | 885 base::SharedMemoryHandle scripts) { |
844 DCHECK(base::SharedMemory::IsHandleValid(scripts)) << "Bad scripts handle"; | 886 DCHECK(base::SharedMemory::IsHandleValid(scripts)) << "Bad scripts handle"; |
845 user_script_slave_->UpdateScripts(scripts); | 887 user_script_slave_->UpdateScripts(scripts); |
846 UpdateActiveExtensions(); | 888 UpdateActiveExtensions(); |
847 } | 889 } |
848 | 890 |
849 void ExtensionDispatcher::UpdateActiveExtensions() { | 891 void ExtensionDispatcher::UpdateActiveExtensions() { |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
961 // APIs, they don't get extension bindings injected. If we end up here it | 1003 // APIs, they don't get extension bindings injected. If we end up here it |
962 // means that a sandboxed page somehow managed to invoke an API anyway, so | 1004 // means that a sandboxed page somehow managed to invoke an API anyway, so |
963 // we should abort. | 1005 // we should abort. |
964 WebKit::WebFrame* frame = context->web_frame(); | 1006 WebKit::WebFrame* frame = context->web_frame(); |
965 ExtensionURLInfo url_info(frame->document().securityOrigin(), | 1007 ExtensionURLInfo url_info(frame->document().securityOrigin(), |
966 UserScriptSlave::GetDataSourceURLForFrame(frame)); | 1008 UserScriptSlave::GetDataSourceURLForFrame(frame)); |
967 CHECK(!extensions_.IsSandboxedPage(url_info)); | 1009 CHECK(!extensions_.IsSandboxedPage(url_info)); |
968 | 1010 |
969 return true; | 1011 return true; |
970 } | 1012 } |
OLD | NEW |