| Index: chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc
|
| diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc
|
| index 1526b7d066201a084d9cfb1442b5235fb79253c4..fa2cc8b453117ef9b6b6483d3c78891c22a1c945 100644
|
| --- a/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc
|
| +++ b/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc
|
| @@ -5,14 +5,11 @@
|
| #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
|
|
|
| #include "base/lazy_instance.h"
|
| -#include "chrome/browser/chrome_notification_types.h"
|
| -#include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
|
| +#include "base/values.h"
|
| +#include "chrome/browser/sessions/session_id.h"
|
| #include "components/keyed_service/content/browser_context_dependency_manager.h"
|
| #include "content/public/browser/browser_thread.h"
|
| -#include "content/public/browser/notification_details.h"
|
| -#include "content/public/browser/notification_service.h"
|
| -#include "content/public/browser/notification_source.h"
|
| -#include "content/public/browser/render_view_host.h"
|
| +#include "content/public/browser/render_frame_host.h"
|
| #include "content/public/browser/web_contents.h"
|
| #include "content/public/browser/web_contents_observer.h"
|
| #include "extensions/browser/event_router.h"
|
| @@ -26,89 +23,123 @@ namespace extensions {
|
|
|
| namespace tab_capture = api::tab_capture;
|
|
|
| -class FullscreenObserver : public content::WebContentsObserver {
|
| +// Stores values associated with a tab capture request, maintains lifecycle
|
| +// state, and monitors WebContents for fullscreen transition events and
|
| +// destruction.
|
| +class TabCaptureRegistry::LiveRequest : public content::WebContentsObserver {
|
| public:
|
| - FullscreenObserver(TabCaptureRequest* request,
|
| - const TabCaptureRegistry* registry);
|
| - virtual ~FullscreenObserver() {}
|
| + LiveRequest(content::WebContents* target_contents,
|
| + const std::string& extension_id,
|
| + TabCaptureRegistry* registry)
|
| + : content::WebContentsObserver(target_contents),
|
| + extension_id_(extension_id),
|
| + registry_(registry),
|
| + capture_state_(tab_capture::TAB_CAPTURE_STATE_NONE),
|
| + is_verified_(false),
|
| + // TODO(miu): This initial value for |is_fullscreened_| is a faulty
|
| + // assumption. http://crbug.com/350491
|
| + is_fullscreened_(false),
|
| + render_process_id_(-1),
|
| + render_frame_id_(-1) {
|
| + DCHECK(web_contents());
|
| + DCHECK(registry_);
|
| + }
|
|
|
| - private:
|
| - // content::WebContentsObserver implementation.
|
| - virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE;
|
| - virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE;
|
| + virtual ~LiveRequest() {}
|
|
|
| - TabCaptureRequest* request_;
|
| - const TabCaptureRegistry* registry_;
|
| + // Accessors.
|
| + const content::WebContents* target_contents() const {
|
| + return content::WebContentsObserver::web_contents();
|
| + }
|
| + const std::string& extension_id() const {
|
| + return extension_id_;
|
| + }
|
| + TabCaptureState capture_state() const {
|
| + return capture_state_;
|
| + }
|
| + bool is_verified() const {
|
| + return is_verified_;
|
| + }
|
|
|
| - DISALLOW_COPY_AND_ASSIGN(FullscreenObserver);
|
| -};
|
| + void SetIsVerified() {
|
| + DCHECK(!is_verified_);
|
| + is_verified_ = true;
|
| + }
|
|
|
| -// Holds all the state related to a tab capture stream.
|
| -struct TabCaptureRequest {
|
| - TabCaptureRequest(int render_process_id,
|
| - int render_view_id,
|
| - const std::string& extension_id,
|
| - int tab_id,
|
| - TabCaptureState status);
|
| - ~TabCaptureRequest();
|
| -
|
| - const int render_process_id;
|
| - const int render_view_id;
|
| - const std::string extension_id;
|
| - const int tab_id;
|
| - TabCaptureState status;
|
| - TabCaptureState last_status;
|
| - bool fullscreen;
|
| - scoped_ptr<FullscreenObserver> fullscreen_observer;
|
| -};
|
| + // TODO(miu): See TODO(miu) in VerifyRequest() below.
|
| + void SetOriginallyTargettedRenderFrameID(int render_process_id,
|
| + int render_frame_id) {
|
| + DCHECK_GT(render_frame_id, 0);
|
| + DCHECK_EQ(render_frame_id_, -1); // Setting ID only once.
|
| + render_process_id_ = render_process_id;
|
| + render_frame_id_ = render_frame_id;
|
| + }
|
|
|
| -FullscreenObserver::FullscreenObserver(
|
| - TabCaptureRequest* request,
|
| - const TabCaptureRegistry* registry)
|
| - : request_(request),
|
| - registry_(registry) {
|
| - content::RenderViewHost* const rvh =
|
| - content::RenderViewHost::FromID(request->render_process_id,
|
| - request->render_view_id);
|
| - Observe(rvh ? content::WebContents::FromRenderViewHost(rvh) : NULL);
|
| -}
|
| + bool WasOriginallyTargettingRenderFrameID(int render_process_id,
|
| + int render_frame_id) const {
|
| + return render_process_id_ == render_process_id &&
|
| + render_frame_id_ == render_frame_id;
|
| + }
|
|
|
| -void FullscreenObserver::DidShowFullscreenWidget(
|
| - int routing_id) {
|
| - request_->fullscreen = true;
|
| - registry_->DispatchStatusChangeEvent(request_);
|
| -}
|
| + void UpdateCaptureState(TabCaptureState next_capture_state) {
|
| + // This method can get duplicate calls if both audio and video were
|
| + // requested, so return early to avoid duplicate dispatching of status
|
| + // change events.
|
| + if (capture_state_ == next_capture_state)
|
| + return;
|
|
|
| -void FullscreenObserver::DidDestroyFullscreenWidget(
|
| - int routing_id) {
|
| - request_->fullscreen = false;
|
| - registry_->DispatchStatusChangeEvent(request_);
|
| -}
|
| + capture_state_ = next_capture_state;
|
| + registry_->DispatchStatusChangeEvent(this);
|
| + }
|
|
|
| -TabCaptureRequest::TabCaptureRequest(
|
| - int render_process_id,
|
| - int render_view_id,
|
| - const std::string& extension_id,
|
| - const int tab_id,
|
| - TabCaptureState status)
|
| - : render_process_id(render_process_id),
|
| - render_view_id(render_view_id),
|
| - extension_id(extension_id),
|
| - tab_id(tab_id),
|
| - status(status),
|
| - last_status(status),
|
| - fullscreen(false) {
|
| -}
|
| + void GetCaptureInfo(tab_capture::CaptureInfo* info) const {
|
| + info->tab_id = SessionID::IdForTab(web_contents());
|
| + info->status = capture_state_;
|
| + info->fullscreen = is_fullscreened_;
|
| + }
|
|
|
| -TabCaptureRequest::~TabCaptureRequest() {
|
| -}
|
| + protected:
|
| + virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE {
|
| + is_fullscreened_ = true;
|
| + if (capture_state_ == tab_capture::TAB_CAPTURE_STATE_ACTIVE)
|
| + registry_->DispatchStatusChangeEvent(this);
|
| + }
|
| +
|
| + virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE {
|
| + is_fullscreened_ = false;
|
| + if (capture_state_ == tab_capture::TAB_CAPTURE_STATE_ACTIVE)
|
| + registry_->DispatchStatusChangeEvent(this);
|
| + }
|
| +
|
| + virtual void DidToggleFullscreenModeForTab(bool entered_fullscreen) OVERRIDE {
|
| + is_fullscreened_ = entered_fullscreen;
|
| + if (capture_state_ == tab_capture::TAB_CAPTURE_STATE_ACTIVE)
|
| + registry_->DispatchStatusChangeEvent(this);
|
| + }
|
| +
|
| + virtual void WebContentsDestroyed() OVERRIDE {
|
| + registry_->KillRequest(this); // Deletes |this|.
|
| + }
|
| +
|
| + private:
|
| + const std::string extension_id_;
|
| + TabCaptureRegistry* const registry_;
|
| + TabCaptureState capture_state_;
|
| + bool is_verified_;
|
| + bool is_fullscreened_;
|
| +
|
| + // These reference the originally targetted RenderFrameHost by its ID. The
|
| + // RenderFrameHost may have gone away long before a LiveRequest closes, but
|
| + // calls to OnRequestUpdate() will always refer to this request by this ID.
|
| + int render_process_id_;
|
| + int render_frame_id_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(LiveRequest);
|
| +};
|
|
|
| TabCaptureRegistry::TabCaptureRegistry(content::BrowserContext* context)
|
| : browser_context_(context), extension_registry_observer_(this) {
|
| MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
|
| - registrar_.Add(this,
|
| - chrome::NOTIFICATION_FULLSCREEN_CHANGED,
|
| - content::NotificationService::AllSources());
|
| extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
|
| }
|
|
|
| @@ -130,53 +161,18 @@ TabCaptureRegistry::GetFactoryInstance() {
|
| return g_factory.Pointer();
|
| }
|
|
|
| -const TabCaptureRegistry::RegistryCaptureInfo
|
| - TabCaptureRegistry::GetCapturedTabs(const std::string& extension_id) const {
|
| +void TabCaptureRegistry::GetCapturedTabs(
|
| + const std::string& extension_id,
|
| + base::ListValue* list_of_capture_info) const {
|
| DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - RegistryCaptureInfo list;
|
| - for (ScopedVector<TabCaptureRequest>::const_iterator it = requests_.begin();
|
| + DCHECK(list_of_capture_info);
|
| + list_of_capture_info->Clear();
|
| + for (ScopedVector<LiveRequest>::const_iterator it = requests_.begin();
|
| it != requests_.end(); ++it) {
|
| - if ((*it)->extension_id == extension_id) {
|
| - list.push_back(std::make_pair((*it)->tab_id, (*it)->status));
|
| - }
|
| - }
|
| - return list;
|
| -}
|
| -
|
| -void TabCaptureRegistry::Observe(int type,
|
| - const content::NotificationSource& source,
|
| - const content::NotificationDetails& details) {
|
| - DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - DCHECK_EQ(chrome::NOTIFICATION_FULLSCREEN_CHANGED, type);
|
| - FullscreenController* fullscreen_controller =
|
| - content::Source<FullscreenController>(source).ptr();
|
| - const bool is_fullscreen = *content::Details<bool>(details).ptr();
|
| - for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
|
| - it != requests_.end();
|
| - ++it) {
|
| - // If we are exiting fullscreen mode, we only need to check if any of
|
| - // the requests had the fullscreen flag toggled previously. The
|
| - // fullscreen controller no longer has the reference to the fullscreen
|
| - // web_contents here.
|
| - if (!is_fullscreen) {
|
| - if ((*it)->fullscreen) {
|
| - (*it)->fullscreen = false;
|
| - DispatchStatusChangeEvent(*it);
|
| - break;
|
| - }
|
| - continue;
|
| - }
|
| -
|
| - // If we are entering fullscreen mode, find whether the web_contents we
|
| - // are capturing entered fullscreen mode.
|
| - content::RenderViewHost* const rvh = content::RenderViewHost::FromID(
|
| - (*it)->render_process_id, (*it)->render_view_id);
|
| - if (rvh &&
|
| - fullscreen_controller->IsFullscreenForTabOrPending(
|
| - content::WebContents::FromRenderViewHost(rvh))) {
|
| - (*it)->fullscreen = true;
|
| - DispatchStatusChangeEvent(*it);
|
| - break;
|
| + if ((*it)->is_verified() && (*it)->extension_id() == extension_id) {
|
| + tab_capture::CaptureInfo info;
|
| + (*it)->GetCaptureInfo(&info);
|
| + list_of_capture_info->Append(info.ToValue().release());
|
| }
|
| }
|
| }
|
| @@ -186,9 +182,9 @@ void TabCaptureRegistry::OnExtensionUnloaded(
|
| const Extension* extension,
|
| UnloadedExtensionInfo::Reason reason) {
|
| // Cleanup all the requested media streams for this extension.
|
| - for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
|
| + for (ScopedVector<LiveRequest>::iterator it = requests_.begin();
|
| it != requests_.end();) {
|
| - if ((*it)->extension_id == extension->id()) {
|
| + if ((*it)->extension_id() == extension->id()) {
|
| it = requests_.erase(it);
|
| } else {
|
| ++it;
|
| @@ -196,78 +192,96 @@ void TabCaptureRegistry::OnExtensionUnloaded(
|
| }
|
| }
|
|
|
| -bool TabCaptureRegistry::AddRequest(int render_process_id,
|
| - int render_view_id,
|
| - const std::string& extension_id,
|
| - int tab_id,
|
| - TabCaptureState status) {
|
| - TabCaptureRequest* request = FindCaptureRequest(render_process_id,
|
| - render_view_id);
|
| +bool TabCaptureRegistry::AddRequest(content::WebContents* target_contents,
|
| + const std::string& extension_id) {
|
| + LiveRequest* const request = FindRequest(target_contents);
|
| +
|
| // Currently, we do not allow multiple active captures for same tab.
|
| if (request != NULL) {
|
| - if (request->status != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
|
| - request->status != tab_capture::TAB_CAPTURE_STATE_ERROR) {
|
| + if (request->capture_state() != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
|
| + request->capture_state() != tab_capture::TAB_CAPTURE_STATE_ERROR) {
|
| return false;
|
| } else {
|
| - DeleteCaptureRequest(render_process_id, render_view_id);
|
| + // Delete the request before creating its replacement (below).
|
| + KillRequest(request);
|
| }
|
| }
|
|
|
| - requests_.push_back(new TabCaptureRequest(render_process_id,
|
| - render_view_id,
|
| - extension_id,
|
| - tab_id,
|
| - status));
|
| + requests_.push_back(new LiveRequest(target_contents, extension_id, this));
|
| return true;
|
| }
|
|
|
| -bool TabCaptureRegistry::VerifyRequest(int render_process_id,
|
| - int render_view_id) {
|
| +bool TabCaptureRegistry::VerifyRequest(
|
| + int render_process_id,
|
| + int render_frame_id,
|
| + const std::string& extension_id) {
|
| DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - DVLOG(1) << "Verifying tabCapture request for "
|
| - << render_process_id << ":" << render_view_id;
|
| - // TODO(justinlin): Verify extension too.
|
| - return (FindCaptureRequest(render_process_id, render_view_id) != NULL);
|
| +
|
| + LiveRequest* const request = FindRequest(
|
| + content::WebContents::FromRenderFrameHost(
|
| + content::RenderFrameHost::FromID(
|
| + render_process_id, render_frame_id)));
|
| + if (!request)
|
| + return false; // Unknown RenderFrameHost ID, or frame has gone away.
|
| +
|
| + // TODO(miu): We should probably also verify the origin URL, like the desktop
|
| + // capture API. http://crbug.com/163100
|
| + if (request->is_verified() ||
|
| + request->extension_id() != extension_id ||
|
| + (request->capture_state() != tab_capture::TAB_CAPTURE_STATE_NONE &&
|
| + request->capture_state() != tab_capture::TAB_CAPTURE_STATE_PENDING))
|
| + return false;
|
| +
|
| + // TODO(miu): The RenderFrameHost IDs should be set when LiveRequest is
|
| + // constructed, but ExtensionFunction does not yet support use of
|
| + // render_frame_host() to determine the exact RenderFrameHost for the call to
|
| + // AddRequest() above. Fix tab_capture_api.cc, and then fix this ugly hack.
|
| + // http://crbug.com/304341
|
| + request->SetOriginallyTargettedRenderFrameID(
|
| + render_process_id, render_frame_id);
|
| +
|
| + request->SetIsVerified();
|
| + return true;
|
| }
|
|
|
| void TabCaptureRegistry::OnRequestUpdate(
|
| - int render_process_id,
|
| - int render_view_id,
|
| - const content::MediaStreamDevice& device,
|
| + int original_target_render_process_id,
|
| + int original_target_render_frame_id,
|
| + content::MediaStreamType stream_type,
|
| const content::MediaRequestState new_state) {
|
| DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
| - if (device.type != content::MEDIA_TAB_VIDEO_CAPTURE &&
|
| - device.type != content::MEDIA_TAB_AUDIO_CAPTURE) {
|
| + if (stream_type != content::MEDIA_TAB_VIDEO_CAPTURE &&
|
| + stream_type != content::MEDIA_TAB_AUDIO_CAPTURE) {
|
| return;
|
| }
|
|
|
| - TabCaptureRequest* request = FindCaptureRequest(render_process_id,
|
| - render_view_id);
|
| - if (request == NULL) {
|
| - // TODO(justinlin): This can happen because the extension's renderer does
|
| - // not seem to always cleanup streams correctly.
|
| - LOG(ERROR) << "Receiving updates for deleted capture request.";
|
| - return;
|
| + LiveRequest* request = FindRequest(original_target_render_process_id,
|
| + original_target_render_frame_id);
|
| + if (!request) {
|
| + // Fall-back: Search again using WebContents since this method may have been
|
| + // called before VerifyRequest() set the RenderFrameHost ID. If the
|
| + // RenderFrameHost has gone away, that's okay since the upcoming call to
|
| + // VerifyRequest() will fail, and that means the tracking of request updates
|
| + // doesn't matter anymore.
|
| + request = FindRequest(content::WebContents::FromRenderFrameHost(
|
| + content::RenderFrameHost::FromID(original_target_render_process_id,
|
| + original_target_render_frame_id)));
|
| + if (!request)
|
| + return; // Stale or invalid request update.
|
| }
|
|
|
| - bool opening_stream = false;
|
| - bool stopping_stream = false;
|
| -
|
| TabCaptureState next_state = tab_capture::TAB_CAPTURE_STATE_NONE;
|
| switch (new_state) {
|
| case content::MEDIA_REQUEST_STATE_PENDING_APPROVAL:
|
| next_state = tab_capture::TAB_CAPTURE_STATE_PENDING;
|
| break;
|
| case content::MEDIA_REQUEST_STATE_DONE:
|
| - opening_stream = true;
|
| next_state = tab_capture::TAB_CAPTURE_STATE_ACTIVE;
|
| break;
|
| case content::MEDIA_REQUEST_STATE_CLOSING:
|
| - stopping_stream = true;
|
| next_state = tab_capture::TAB_CAPTURE_STATE_STOPPED;
|
| break;
|
| case content::MEDIA_REQUEST_STATE_ERROR:
|
| - stopping_stream = true;
|
| next_state = tab_capture::TAB_CAPTURE_STATE_ERROR;
|
| break;
|
| case content::MEDIA_REQUEST_STATE_OPENING:
|
| @@ -279,76 +293,68 @@ void TabCaptureRegistry::OnRequestUpdate(
|
| }
|
|
|
| if (next_state == tab_capture::TAB_CAPTURE_STATE_PENDING &&
|
| - request->status != tab_capture::TAB_CAPTURE_STATE_PENDING &&
|
| - request->status != tab_capture::TAB_CAPTURE_STATE_NONE &&
|
| - request->status != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
|
| - request->status != tab_capture::TAB_CAPTURE_STATE_ERROR) {
|
| + request->capture_state() != tab_capture::TAB_CAPTURE_STATE_PENDING &&
|
| + request->capture_state() != tab_capture::TAB_CAPTURE_STATE_NONE &&
|
| + request->capture_state() != tab_capture::TAB_CAPTURE_STATE_STOPPED &&
|
| + request->capture_state() != tab_capture::TAB_CAPTURE_STATE_ERROR) {
|
| // If we end up trying to grab a new stream while the previous one was never
|
| // terminated, then something fishy is going on.
|
| NOTREACHED() << "Trying to capture tab with existing stream.";
|
| return;
|
| }
|
|
|
| - if (opening_stream) {
|
| - request->fullscreen_observer.reset(new FullscreenObserver(request, this));
|
| - }
|
| -
|
| - if (stopping_stream) {
|
| - request->fullscreen_observer.reset();
|
| - }
|
| -
|
| - request->last_status = request->status;
|
| - request->status = next_state;
|
| -
|
| - // We will get duplicate events if we requested both audio and video, so only
|
| - // send new events.
|
| - if (request->last_status != request->status) {
|
| - DispatchStatusChangeEvent(request);
|
| - }
|
| + request->UpdateCaptureState(next_state);
|
| }
|
|
|
| void TabCaptureRegistry::DispatchStatusChangeEvent(
|
| - const TabCaptureRequest* request) const {
|
| + const LiveRequest* request) const {
|
| EventRouter* router = EventRouter::Get(browser_context_);
|
| if (!router)
|
| return;
|
|
|
| - scoped_ptr<tab_capture::CaptureInfo> info(new tab_capture::CaptureInfo());
|
| - info->tab_id = request->tab_id;
|
| - info->status = request->status;
|
| - info->fullscreen = request->fullscreen;
|
| -
|
| scoped_ptr<base::ListValue> args(new base::ListValue());
|
| - args->Append(info->ToValue().release());
|
| + tab_capture::CaptureInfo info;
|
| + request->GetCaptureInfo(&info);
|
| + args->Append(info.ToValue().release());
|
| scoped_ptr<Event> event(new Event(tab_capture::OnStatusChanged::kEventName,
|
| args.Pass()));
|
| event->restrict_to_browser_context = browser_context_;
|
|
|
| - router->DispatchEventToExtension(request->extension_id, event.Pass());
|
| + router->DispatchEventToExtension(request->extension_id(), event.Pass());
|
| }
|
|
|
| -TabCaptureRequest* TabCaptureRegistry::FindCaptureRequest(
|
| - int render_process_id, int render_view_id) const {
|
| - for (ScopedVector<TabCaptureRequest>::const_iterator it = requests_.begin();
|
| +TabCaptureRegistry::LiveRequest* TabCaptureRegistry::FindRequest(
|
| + const content::WebContents* target_contents) const {
|
| + for (ScopedVector<LiveRequest>::const_iterator it = requests_.begin();
|
| it != requests_.end(); ++it) {
|
| - if ((*it)->render_process_id == render_process_id &&
|
| - (*it)->render_view_id == render_view_id) {
|
| + if ((*it)->target_contents() == target_contents)
|
| + return *it;
|
| + }
|
| + return NULL;
|
| +}
|
| +
|
| +TabCaptureRegistry::LiveRequest* TabCaptureRegistry::FindRequest(
|
| + int original_target_render_process_id,
|
| + int original_target_render_frame_id) const {
|
| + for (ScopedVector<LiveRequest>::const_iterator it = requests_.begin();
|
| + it != requests_.end(); ++it) {
|
| + if ((*it)->WasOriginallyTargettingRenderFrameID(
|
| + original_target_render_process_id,
|
| + original_target_render_frame_id))
|
| return *it;
|
| - }
|
| }
|
| return NULL;
|
| }
|
|
|
| -void TabCaptureRegistry::DeleteCaptureRequest(int render_process_id,
|
| - int render_view_id) {
|
| - for (ScopedVector<TabCaptureRequest>::iterator it = requests_.begin();
|
| +void TabCaptureRegistry::KillRequest(LiveRequest* request) {
|
| + for (ScopedVector<LiveRequest>::iterator it = requests_.begin();
|
| it != requests_.end(); ++it) {
|
| - if ((*it)->render_process_id == render_process_id &&
|
| - (*it)->render_view_id == render_view_id) {
|
| + if ((*it) == request) {
|
| requests_.erase(it);
|
| return;
|
| }
|
| }
|
| + NOTREACHED();
|
| }
|
|
|
| } // namespace extensions
|
|
|