1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "extensions/renderer/extension_frame_helper.h"
6 
7 #include <set>
8 
9 #include "base/metrics/histogram_macros.h"
10 #include "base/strings/string_util.h"
11 #include "base/timer/elapsed_timer.h"
12 #include "content/public/renderer/render_frame.h"
13 #include "content/public/renderer/render_view.h"
14 #include "extensions/common/api/messaging/message.h"
15 #include "extensions/common/api/messaging/port_id.h"
16 #include "extensions/common/constants.h"
17 #include "extensions/common/extension_messages.h"
18 #include "extensions/common/manifest_handlers/background_info.h"
19 #include "extensions/renderer/api/automation/automation_api_helper.h"
20 #include "extensions/renderer/console.h"
21 #include "extensions/renderer/dispatcher.h"
22 #include "extensions/renderer/native_extension_bindings_system.h"
23 #include "extensions/renderer/native_renderer_messaging_service.h"
24 #include "extensions/renderer/script_context.h"
25 #include "extensions/renderer/script_context_set.h"
26 #include "third_party/blink/public/platform/web_security_origin.h"
27 #include "third_party/blink/public/web/web_console_message.h"
28 #include "third_party/blink/public/web/web_document.h"
29 #include "third_party/blink/public/web/web_document_loader.h"
30 #include "third_party/blink/public/web/web_local_frame.h"
31 #include "third_party/blink/public/web/web_settings.h"
32 #include "third_party/blink/public/web/web_view.h"
33 
34 namespace extensions {
35 
36 namespace {
37 
38 constexpr int kMainWorldId = 0;
39 
40 base::LazyInstance<std::set<const ExtensionFrameHelper*>>::DestructorAtExit
41     g_frame_helpers = LAZY_INSTANCE_INITIALIZER;
42 
43 // Returns true if the render frame corresponding with |frame_helper| matches
44 // the given criteria.
RenderFrameMatches(const ExtensionFrameHelper * frame_helper,ViewType match_view_type,int match_window_id,int match_tab_id,const std::string & match_extension_id)45 bool RenderFrameMatches(const ExtensionFrameHelper* frame_helper,
46                         ViewType match_view_type,
47                         int match_window_id,
48                         int match_tab_id,
49                         const std::string& match_extension_id) {
50   if (match_view_type != VIEW_TYPE_INVALID &&
51       frame_helper->view_type() != match_view_type)
52     return false;
53 
54   // Not all frames have a valid ViewType, e.g. devtools, most GuestViews, and
55   // unclassified detached WebContents.
56   if (frame_helper->view_type() == VIEW_TYPE_INVALID)
57     return false;
58 
59   // This logic matches ExtensionWebContentsObserver::GetExtensionFromFrame.
60   blink::WebSecurityOrigin origin =
61       frame_helper->render_frame()->GetWebFrame()->GetSecurityOrigin();
62   if (origin.IsOpaque() ||
63       !base::EqualsASCII(origin.Protocol().Utf16(), kExtensionScheme) ||
64       !base::EqualsASCII(origin.Host().Utf16(), match_extension_id.c_str()))
65     return false;
66 
67   if (match_window_id != extension_misc::kUnknownWindowId &&
68       frame_helper->browser_window_id() != match_window_id)
69     return false;
70 
71   if (match_tab_id != extension_misc::kUnknownTabId &&
72       frame_helper->tab_id() != match_tab_id)
73     return false;
74 
75   return true;
76 }
77 
78 // Runs every callback in |callbacks_to_be_run_and_cleared| while |frame_helper|
79 // is valid, and clears |callbacks_to_be_run_and_cleared|.
RunCallbacksWhileFrameIsValid(base::WeakPtr<ExtensionFrameHelper> frame_helper,std::vector<base::Closure> * callbacks_to_be_run_and_cleared)80 void RunCallbacksWhileFrameIsValid(
81     base::WeakPtr<ExtensionFrameHelper> frame_helper,
82     std::vector<base::Closure>* callbacks_to_be_run_and_cleared) {
83   // The JavaScript code can cause re-entrancy. To avoid a deadlock, don't run
84   // callbacks that are added during the iteration.
85   std::vector<base::Closure> callbacks;
86   callbacks_to_be_run_and_cleared->swap(callbacks);
87   for (auto& callback : callbacks) {
88     callback.Run();
89     if (!frame_helper.get())
90       return;  // Frame and ExtensionFrameHelper invalidated by callback.
91   }
92 }
93 
94 enum class PortType {
95   EXTENSION,
96   TAB,
97   NATIVE_APP,
98 };
99 
100 // Returns an extension hosted in the |render_frame| (or nullptr if the frame
101 // doesn't host an extension).
GetExtensionFromFrame(content::RenderFrame * render_frame)102 const Extension* GetExtensionFromFrame(content::RenderFrame* render_frame) {
103   DCHECK(render_frame);
104   ScriptContext* context =
105       ScriptContextSet::GetMainWorldContextForFrame(render_frame);
106   return context ? context->effective_extension() : nullptr;
107 }
108 
109 }  // namespace
110 
ExtensionFrameHelper(content::RenderFrame * render_frame,Dispatcher * extension_dispatcher)111 ExtensionFrameHelper::ExtensionFrameHelper(content::RenderFrame* render_frame,
112                                            Dispatcher* extension_dispatcher)
113     : content::RenderFrameObserver(render_frame),
114       content::RenderFrameObserverTracker<ExtensionFrameHelper>(render_frame),
115       view_type_(VIEW_TYPE_INVALID),
116       tab_id_(-1),
117       browser_window_id_(-1),
118       extension_dispatcher_(extension_dispatcher),
119       did_create_current_document_element_(false) {
120   g_frame_helpers.Get().insert(this);
121   if (render_frame->IsMainFrame()) {
122     // Manages its own lifetime.
123     new AutomationApiHelper(render_frame);
124   }
125 }
126 
~ExtensionFrameHelper()127 ExtensionFrameHelper::~ExtensionFrameHelper() {
128   g_frame_helpers.Get().erase(this);
129 }
130 
131 // static
GetExtensionFrames(const std::string & extension_id,int browser_window_id,int tab_id,ViewType view_type)132 std::vector<content::RenderFrame*> ExtensionFrameHelper::GetExtensionFrames(
133     const std::string& extension_id,
134     int browser_window_id,
135     int tab_id,
136     ViewType view_type) {
137   std::vector<content::RenderFrame*> render_frames;
138   for (const ExtensionFrameHelper* helper : g_frame_helpers.Get()) {
139     if (RenderFrameMatches(helper, view_type, browser_window_id, tab_id,
140                            extension_id))
141       render_frames.push_back(helper->render_frame());
142   }
143   return render_frames;
144 }
145 
146 // static
GetV8MainFrames(v8::Local<v8::Context> context,const std::string & extension_id,int browser_window_id,int tab_id,ViewType view_type)147 v8::Local<v8::Array> ExtensionFrameHelper::GetV8MainFrames(
148     v8::Local<v8::Context> context,
149     const std::string& extension_id,
150     int browser_window_id,
151     int tab_id,
152     ViewType view_type) {
153   // WebFrame::ScriptCanAccess uses the isolate's current context. We need to
154   // make sure that the current context is the one we're expecting.
155   DCHECK(context == context->GetIsolate()->GetCurrentContext());
156   std::vector<content::RenderFrame*> render_frames =
157       GetExtensionFrames(extension_id, browser_window_id, tab_id, view_type);
158   v8::Local<v8::Array> v8_frames = v8::Array::New(context->GetIsolate());
159 
160   int v8_index = 0;
161   for (content::RenderFrame* frame : render_frames) {
162     if (!frame->IsMainFrame())
163       continue;
164 
165     blink::WebLocalFrame* web_frame = frame->GetWebFrame();
166     if (!blink::WebFrame::ScriptCanAccess(web_frame))
167       continue;
168 
169     v8::Local<v8::Context> frame_context = web_frame->MainWorldScriptContext();
170     if (!frame_context.IsEmpty()) {
171       v8::Local<v8::Value> window = frame_context->Global();
172       CHECK(!window.IsEmpty());
173       v8::Maybe<bool> maybe =
174           v8_frames->CreateDataProperty(context, v8_index++, window);
175       CHECK(maybe.IsJust() && maybe.FromJust());
176     }
177   }
178 
179   return v8_frames;
180 }
181 
182 // static
GetBackgroundPageFrame(const std::string & extension_id)183 content::RenderFrame* ExtensionFrameHelper::GetBackgroundPageFrame(
184     const std::string& extension_id) {
185   for (const ExtensionFrameHelper* helper : g_frame_helpers.Get()) {
186     if (RenderFrameMatches(helper, VIEW_TYPE_EXTENSION_BACKGROUND_PAGE,
187                            extension_misc::kUnknownWindowId,
188                            extension_misc::kUnknownTabId, extension_id)) {
189       blink::WebLocalFrame* web_frame = helper->render_frame()->GetWebFrame();
190       // Check if this is the top frame.
191       if (web_frame->Top() == web_frame)
192         return helper->render_frame();
193     }
194   }
195   return nullptr;
196 }
197 
GetV8BackgroundPageMainFrame(v8::Isolate * isolate,const std::string & extension_id)198 v8::Local<v8::Value> ExtensionFrameHelper::GetV8BackgroundPageMainFrame(
199     v8::Isolate* isolate,
200     const std::string& extension_id) {
201   content::RenderFrame* main_frame = GetBackgroundPageFrame(extension_id);
202 
203   v8::Local<v8::Value> background_page;
204   blink::WebLocalFrame* web_frame =
205       main_frame ? main_frame->GetWebFrame() : nullptr;
206   if (web_frame && blink::WebFrame::ScriptCanAccess(web_frame))
207     background_page = web_frame->MainWorldScriptContext()->Global();
208   else
209     background_page = v8::Undefined(isolate);
210 
211   return background_page;
212 }
213 
214 // static
FindFrame(content::RenderFrame * relative_to_frame,const std::string & name)215 content::RenderFrame* ExtensionFrameHelper::FindFrame(
216     content::RenderFrame* relative_to_frame,
217     const std::string& name) {
218   // Only pierce browsing instance boundaries if |relative_to_frame| is an
219   // extension.
220   const Extension* extension = GetExtensionFromFrame(relative_to_frame);
221   if (!extension)
222     return nullptr;
223 
224   for (const ExtensionFrameHelper* target : g_frame_helpers.Get()) {
225     // Skip frames with a mismatched name.
226     if (target->render_frame()->GetWebFrame()->AssignedName().Utf8() != name)
227       continue;
228 
229     // Only pierce browsing instance boundaries if the target frame is from the
230     // same extension (but not when another extension shares the same renderer
231     // process because of reuse trigerred by process limit).
232     if (extension != GetExtensionFromFrame(target->render_frame()))
233       continue;
234 
235     return target->render_frame();
236   }
237 
238   return nullptr;
239 }
240 
241 // static
IsContextForEventPage(const ScriptContext * context)242 bool ExtensionFrameHelper::IsContextForEventPage(const ScriptContext* context) {
243   content::RenderFrame* render_frame = context->GetRenderFrame();
244   return context->extension() && render_frame &&
245          BackgroundInfo::HasLazyBackgroundPage(context->extension()) &&
246          ExtensionFrameHelper::Get(render_frame)->view_type() ==
247               VIEW_TYPE_EXTENSION_BACKGROUND_PAGE;
248 }
249 
DidCreateDocumentElement()250 void ExtensionFrameHelper::DidCreateDocumentElement() {
251   did_create_current_document_element_ = true;
252   extension_dispatcher_->DidCreateDocumentElement(
253       render_frame()->GetWebFrame());
254 }
255 
DidCreateNewDocument()256 void ExtensionFrameHelper::DidCreateNewDocument() {
257   did_create_current_document_element_ = false;
258 }
259 
RunScriptsAtDocumentStart()260 void ExtensionFrameHelper::RunScriptsAtDocumentStart() {
261   DCHECK(did_create_current_document_element_);
262   RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
263                                 &document_element_created_callbacks_);
264   // |this| might be dead by now.
265 }
266 
RunScriptsAtDocumentEnd()267 void ExtensionFrameHelper::RunScriptsAtDocumentEnd() {
268   RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
269                                 &document_load_finished_callbacks_);
270   // |this| might be dead by now.
271 }
272 
RunScriptsAtDocumentIdle()273 void ExtensionFrameHelper::RunScriptsAtDocumentIdle() {
274   RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
275                                 &document_idle_callbacks_);
276   // |this| might be dead by now.
277 }
278 
ScheduleAtDocumentStart(const base::Closure & callback)279 void ExtensionFrameHelper::ScheduleAtDocumentStart(
280     const base::Closure& callback) {
281   document_element_created_callbacks_.push_back(callback);
282 }
283 
ScheduleAtDocumentEnd(const base::Closure & callback)284 void ExtensionFrameHelper::ScheduleAtDocumentEnd(
285     const base::Closure& callback) {
286   document_load_finished_callbacks_.push_back(callback);
287 }
288 
ScheduleAtDocumentIdle(const base::Closure & callback)289 void ExtensionFrameHelper::ScheduleAtDocumentIdle(
290     const base::Closure& callback) {
291   document_idle_callbacks_.push_back(callback);
292 }
293 
ReadyToCommitNavigation(blink::WebDocumentLoader * document_loader)294 void ExtensionFrameHelper::ReadyToCommitNavigation(
295     blink::WebDocumentLoader* document_loader) {
296   // New window created by chrome.app.window.create() must not start parsing the
297   // document immediately. The chrome.app.window.create() callback (if any)
298   // needs to be called prior to the new window's 'load' event. The parser will
299   // be resumed when it happens. It doesn't apply to sandboxed pages.
300   if (view_type_ == VIEW_TYPE_APP_WINDOW && render_frame()->IsMainFrame() &&
301       !has_started_first_navigation_ &&
302       GURL(document_loader->GetUrl()).SchemeIs(kExtensionScheme) &&
303       !ScriptContext::IsSandboxedPage(document_loader->GetUrl())) {
304     document_loader->BlockParser();
305   }
306 
307   has_started_first_navigation_ = true;
308 
309   if (!delayed_main_world_script_initialization_)
310     return;
311 
312   delayed_main_world_script_initialization_ = false;
313   v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
314   v8::Local<v8::Context> context =
315       render_frame()->GetWebFrame()->MainWorldScriptContext();
316   v8::Context::Scope context_scope(context);
317   // Normally we would use Document's URL for all kinds of checks, e.g. whether
318   // to inject a content script. However, when committing a navigation, we
319   // should use the URL of a Document being committed instead. This URL is
320   // accessible through WebDocumentLoader::GetURL().
321   // The scope below temporary maps a frame to a document loader, so that places
322   // which retrieve URL can use the right one. Ideally, we would plumb the
323   // correct URL (or maybe WebDocumentLoader) through the callchain, but there
324   // are many callers which will have to pass nullptr.
325   ScriptContext::ScopedFrameDocumentLoader scoped_document_loader(
326       render_frame()->GetWebFrame(), document_loader);
327   extension_dispatcher_->DidCreateScriptContext(render_frame()->GetWebFrame(),
328                                                 context, kMainWorldId);
329   // TODO(devlin): Add constants for main world id, no extension group.
330 }
331 
DidCommitProvisionalLoad(ui::PageTransition transition)332 void ExtensionFrameHelper::DidCommitProvisionalLoad(
333     ui::PageTransition transition) {
334   // Grant cross browsing instance frame lookup if we are an extension. This
335   // should match the conditions in FindFrame.
336   content::RenderFrame* frame = render_frame();
337   if (GetExtensionFromFrame(frame))
338     frame->SetAllowsCrossBrowsingInstanceFrameLookup();
339 }
340 
DidCreateScriptContext(v8::Local<v8::Context> context,int32_t world_id)341 void ExtensionFrameHelper::DidCreateScriptContext(
342     v8::Local<v8::Context> context,
343     int32_t world_id) {
344   if (world_id == kMainWorldId) {
345     if (render_frame()->IsBrowserSideNavigationPending()) {
346       // Defer initializing the extensions script context now because it depends
347       // on having the URL of the provisional load which isn't available at this
348       // point.
349       // We can come here twice in the case of window.open(url): first for
350       // about:blank empty document, then possibly for the actual url load
351       // (depends on whoever triggers window proxy init), before getting
352       // ReadyToCommitNavigation.
353       delayed_main_world_script_initialization_ = true;
354       return;
355     }
356     // Sometimes DidCreateScriptContext comes before ReadyToCommitNavigation.
357     // In this case we don't have to wait until ReadyToCommitNavigation.
358     // TODO(dgozman): ensure consistent call order between
359     // DidCreateScriptContext and ReadyToCommitNavigation.
360     delayed_main_world_script_initialization_ = false;
361   }
362   extension_dispatcher_->DidCreateScriptContext(render_frame()->GetWebFrame(),
363                                                 context, world_id);
364 }
365 
WillReleaseScriptContext(v8::Local<v8::Context> context,int32_t world_id)366 void ExtensionFrameHelper::WillReleaseScriptContext(
367     v8::Local<v8::Context> context,
368     int32_t world_id) {
369   extension_dispatcher_->WillReleaseScriptContext(
370       render_frame()->GetWebFrame(), context, world_id);
371 }
372 
OnMessageReceived(const IPC::Message & message)373 bool ExtensionFrameHelper::OnMessageReceived(const IPC::Message& message) {
374   bool handled = true;
375   IPC_BEGIN_MESSAGE_MAP(ExtensionFrameHelper, message)
376     IPC_MESSAGE_HANDLER(ExtensionMsg_ValidateMessagePort,
377                         OnExtensionValidateMessagePort)
378     IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnConnect,
379                         OnExtensionDispatchOnConnect)
380     IPC_MESSAGE_HANDLER(ExtensionMsg_DeliverMessage, OnExtensionDeliverMessage)
381     IPC_MESSAGE_HANDLER(ExtensionMsg_DispatchOnDisconnect,
382                         OnExtensionDispatchOnDisconnect)
383     IPC_MESSAGE_HANDLER(ExtensionMsg_SetTabId, OnExtensionSetTabId)
384     IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateBrowserWindowId,
385                         OnUpdateBrowserWindowId)
386     IPC_MESSAGE_HANDLER(ExtensionMsg_NotifyRenderViewType,
387                         OnNotifyRendererViewType)
388     IPC_MESSAGE_HANDLER(ExtensionMsg_Response, OnExtensionResponse)
389     IPC_MESSAGE_HANDLER(ExtensionMsg_MessageInvoke, OnExtensionMessageInvoke)
390     IPC_MESSAGE_HANDLER(ExtensionMsg_SetFrameName, OnSetFrameName)
391     IPC_MESSAGE_HANDLER(ExtensionMsg_AppWindowClosed, OnAppWindowClosed)
392     IPC_MESSAGE_HANDLER(ExtensionMsg_SetSpatialNavigationEnabled,
393                         OnSetSpatialNavigationEnabled)
394     IPC_MESSAGE_UNHANDLED(handled = false)
395   IPC_END_MESSAGE_MAP()
396   return handled;
397 }
398 
OnExtensionValidateMessagePort(int worker_thread_id,const PortId & id)399 void ExtensionFrameHelper::OnExtensionValidateMessagePort(int worker_thread_id,
400                                                           const PortId& id) {
401   DCHECK_EQ(kMainThreadId, worker_thread_id);
402   extension_dispatcher_->bindings_system()
403       ->messaging_service()
404       ->ValidateMessagePort(
405           extension_dispatcher_->script_context_set_iterator(), id,
406           render_frame());
407 }
408 
OnExtensionDispatchOnConnect(int worker_thread_id,const PortId & target_port_id,const std::string & channel_name,const ExtensionMsg_TabConnectionInfo & source,const ExtensionMsg_ExternalConnectionInfo & info)409 void ExtensionFrameHelper::OnExtensionDispatchOnConnect(
410     int worker_thread_id,
411     const PortId& target_port_id,
412     const std::string& channel_name,
413     const ExtensionMsg_TabConnectionInfo& source,
414     const ExtensionMsg_ExternalConnectionInfo& info) {
415   DCHECK_EQ(kMainThreadId, worker_thread_id);
416   extension_dispatcher_->bindings_system()
417       ->messaging_service()
418       ->DispatchOnConnect(extension_dispatcher_->script_context_set_iterator(),
419                           target_port_id, channel_name, source, info,
420                           render_frame());
421 }
422 
OnExtensionDeliverMessage(int worker_thread_id,const PortId & target_id,const Message & message)423 void ExtensionFrameHelper::OnExtensionDeliverMessage(int worker_thread_id,
424                                                      const PortId& target_id,
425                                                      const Message& message) {
426   DCHECK_EQ(kMainThreadId, worker_thread_id);
427   extension_dispatcher_->bindings_system()->messaging_service()->DeliverMessage(
428       extension_dispatcher_->script_context_set_iterator(), target_id, message,
429       render_frame());
430 }
431 
OnExtensionDispatchOnDisconnect(int worker_thread_id,const PortId & id,const std::string & error_message)432 void ExtensionFrameHelper::OnExtensionDispatchOnDisconnect(
433     int worker_thread_id,
434     const PortId& id,
435     const std::string& error_message) {
436   DCHECK_EQ(kMainThreadId, worker_thread_id);
437   extension_dispatcher_->bindings_system()
438       ->messaging_service()
439       ->DispatchOnDisconnect(
440           extension_dispatcher_->script_context_set_iterator(), id,
441           error_message, render_frame());
442 }
443 
OnExtensionSetTabId(int tab_id)444 void ExtensionFrameHelper::OnExtensionSetTabId(int tab_id) {
445   CHECK_EQ(tab_id_, -1);
446   CHECK_GE(tab_id, 0);
447   tab_id_ = tab_id;
448 }
449 
OnUpdateBrowserWindowId(int browser_window_id)450 void ExtensionFrameHelper::OnUpdateBrowserWindowId(int browser_window_id) {
451   browser_window_id_ = browser_window_id;
452 }
453 
OnNotifyRendererViewType(ViewType type)454 void ExtensionFrameHelper::OnNotifyRendererViewType(ViewType type) {
455   // TODO(devlin): It'd be really nice to be able to
456   // DCHECK_EQ(VIEW_TYPE_INVALID, view_type_) here.
457   view_type_ = type;
458 }
459 
OnExtensionResponse(int request_id,bool success,const base::ListValue & response,const std::string & error)460 void ExtensionFrameHelper::OnExtensionResponse(int request_id,
461                                                bool success,
462                                                const base::ListValue& response,
463                                                const std::string& error) {
464   extension_dispatcher_->OnExtensionResponse(request_id,
465                                              success,
466                                              response,
467                                              error);
468 }
469 
OnExtensionMessageInvoke(const std::string & extension_id,const std::string & module_name,const std::string & function_name,const base::ListValue & args)470 void ExtensionFrameHelper::OnExtensionMessageInvoke(
471     const std::string& extension_id,
472     const std::string& module_name,
473     const std::string& function_name,
474     const base::ListValue& args) {
475   extension_dispatcher_->InvokeModuleSystemMethod(
476       render_frame(), extension_id, module_name, function_name, args);
477 }
478 
OnSetFrameName(const std::string & name)479 void ExtensionFrameHelper::OnSetFrameName(const std::string& name) {
480   render_frame()->GetWebFrame()->SetName(blink::WebString::FromUTF8(name));
481 }
482 
OnAppWindowClosed(bool send_onclosed)483 void ExtensionFrameHelper::OnAppWindowClosed(bool send_onclosed) {
484   DCHECK(render_frame()->IsMainFrame());
485 
486   if (!send_onclosed)
487     return;
488 
489   v8::HandleScope scope(v8::Isolate::GetCurrent());
490   v8::Local<v8::Context> v8_context =
491       render_frame()->GetWebFrame()->MainWorldScriptContext();
492   ScriptContext* script_context =
493       ScriptContextSet::GetContextByV8Context(v8_context);
494   if (!script_context)
495     return;
496   script_context->module_system()->CallModuleMethodSafe("app.window",
497                                                         "onAppWindowClosed");
498 }
499 
OnSetSpatialNavigationEnabled(bool enabled)500 void ExtensionFrameHelper::OnSetSpatialNavigationEnabled(bool enabled) {
501   render_frame()
502       ->GetRenderView()
503       ->GetWebView()
504       ->GetSettings()
505       ->SetSpatialNavigationEnabled(enabled);
506 }
507 
OnDestruct()508 void ExtensionFrameHelper::OnDestruct() {
509   delete this;
510 }
511 
DraggableRegionsChanged()512 void ExtensionFrameHelper::DraggableRegionsChanged() {
513   if (!render_frame()->IsMainFrame())
514     return;
515 
516   blink::WebVector<blink::WebDraggableRegion> webregions =
517       render_frame()->GetWebFrame()->GetDocument().DraggableRegions();
518   std::vector<DraggableRegion> regions;
519   for (blink::WebDraggableRegion& webregion : webregions) {
520     render_frame()->ConvertViewportToWindow(&webregion.bounds);
521 
522     regions.push_back(DraggableRegion());
523     DraggableRegion& region = regions.back();
524     region.bounds = webregion.bounds;
525     region.draggable = webregion.draggable;
526   }
527   Send(new ExtensionHostMsg_UpdateDraggableRegions(routing_id(), regions));
528 }
529 
530 }  // namespace extensions
531