PageGroup.cpp revision ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddb
1/*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "PageGroup.h"
28
29#include "Chrome.h"
30#include "ChromeClient.h"
31#include "Document.h"
32#include "Frame.h"
33#include "GroupSettings.h"
34#include "IDBFactoryBackendInterface.h"
35#include "Page.h"
36#include "Settings.h"
37#include "StorageNamespace.h"
38
39#if PLATFORM(CHROMIUM)
40#include "PlatformBridge.h"
41#endif
42
43#ifdef ANDROID
44#include "DOMWindow.h"
45#include "FileSystem.h"
46#endif
47
48namespace WebCore {
49
50static unsigned getUniqueIdentifier()
51{
52    static unsigned currentIdentifier = 0;
53    return ++currentIdentifier;
54}
55
56// --------
57
58static bool shouldTrackVisitedLinks = false;
59
60PageGroup::PageGroup(const String& name)
61    : m_name(name)
62    , m_visitedLinksPopulated(false)
63    , m_identifier(getUniqueIdentifier())
64    , m_groupSettings(GroupSettings::create())
65{
66}
67
68PageGroup::PageGroup(Page* page)
69    : m_visitedLinksPopulated(false)
70    , m_identifier(getUniqueIdentifier())
71    , m_groupSettings(GroupSettings::create())
72{
73    ASSERT(page);
74    addPage(page);
75}
76
77PageGroup::~PageGroup()
78{
79    removeAllUserContent();
80}
81
82typedef HashMap<String, PageGroup*> PageGroupMap;
83static PageGroupMap* pageGroups = 0;
84
85PageGroup* PageGroup::pageGroup(const String& groupName)
86{
87    ASSERT(!groupName.isEmpty());
88
89    if (!pageGroups)
90        pageGroups = new PageGroupMap;
91
92    pair<PageGroupMap::iterator, bool> result = pageGroups->add(groupName, 0);
93
94    if (result.second) {
95        ASSERT(!result.first->second);
96        result.first->second = new PageGroup(groupName);
97    }
98
99    ASSERT(result.first->second);
100    return result.first->second;
101}
102
103void PageGroup::closeLocalStorage()
104{
105#if ENABLE(DOM_STORAGE)
106    if (!pageGroups)
107        return;
108
109    PageGroupMap::iterator end = pageGroups->end();
110
111    for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) {
112        if (it->second->hasLocalStorage())
113            it->second->localStorage()->close();
114    }
115#endif
116}
117
118#if ENABLE(DOM_STORAGE) && defined(ANDROID)
119void PageGroup::clearDomStorage()
120{
121    if (!pageGroups)
122        return;
123
124
125    PageGroupMap::iterator end = pageGroups->end();
126
127    for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) {
128        String basePath = "";
129
130        // This is being called as a result of the user explicitly
131        // asking to clear all stored data (e.g. through a settings
132        // dialog. We need a page in the page group to fire a
133        // StorageEvent. There isn't really a correct page to use
134        // as the source (as the clear request hasn't come from a
135        // particular page). One thing we should ensure though is that
136        // we don't try to clear a private browsing mode page as that has no concept
137        // of DOM storage..
138
139        HashSet<Page*> pages = it->second->pages();
140        HashSet<Page*>::iterator pagesEnd = pages.end();
141        Page* page = 0;
142        for(HashSet<Page*>::iterator pit = pages.begin(); pit != pagesEnd; ++pit) {
143            Page* p = *pit;
144
145            // Grab the storage location from an arbitrary page. This is set
146            // to the same value on all private browsing and "normal" pages,
147            // so we can get it from anything.
148            if (basePath.isEmpty())
149                basePath = p->settings()->localStorageDatabasePath();
150
151            // DOM storage is disabled in private browsing pages, so nothing to do if
152            // this is such a page.
153            if (p->settings()->privateBrowsingEnabled())
154                continue;
155
156            // Clear session storage.
157            StorageNamespace* sessionStorage = p->sessionStorage(false);
158            if (sessionStorage)
159                sessionStorage->clear(p);
160
161            // Save this page so we can clear local storage.
162            page = p;
163        }
164
165        // If page is still null at this point, then the only pages that are
166        // open are private browsing pages. Hence no pages are currently using local
167        // storage, so we don't need a page pointer to send any events and the
168        // clear function will handle a 0 input.
169        it->second->localStorage()->clear(page);
170        it->second->localStorage()->close();
171
172        // Closing the storage areas will stop the background thread and so
173        // we need to remove the local storage ref here so that next time
174        // we come to a site that uses it the thread will get started again.
175        it->second->removeLocalStorage();
176
177        // At this point, active local and session storage have been cleared and the
178        // StorageAreas for this PageGroup closed. The final sync will have taken
179        // place. All that is left is to purge the database files.
180        if (!basePath.isEmpty()) {
181            Vector<String> files = listDirectory(basePath, "*.localstorage");
182            Vector<String>::iterator filesEnd = files.end();
183            for (Vector<String>::iterator it = files.begin(); it != filesEnd; ++it)
184                deleteFile(*it);
185        }
186    }
187}
188
189void PageGroup::removeLocalStorage()
190{
191    HashSet<Page*> pages = this->pages();
192    HashSet<Page*>::iterator pagesEnd = pages.end();
193    for(HashSet<Page*>::iterator pit = pages.begin(); pit != pagesEnd; ++pit) {
194        Page* p = *pit;
195        for (Frame* frame = p->mainFrame(); frame; frame = frame->tree()->traverseNext())
196            frame->document()->domWindow()->clearDOMStorage();
197    }
198
199    m_localStorage = 0;
200}
201#endif
202
203void PageGroup::addPage(Page* page)
204{
205    ASSERT(page);
206    ASSERT(!m_pages.contains(page));
207    m_pages.add(page);
208}
209
210void PageGroup::removePage(Page* page)
211{
212    ASSERT(page);
213    ASSERT(m_pages.contains(page));
214    m_pages.remove(page);
215}
216
217bool PageGroup::isLinkVisited(LinkHash visitedLinkHash)
218{
219#if PLATFORM(CHROMIUM)
220    // Use Chromium's built-in visited link database.
221    return PlatformBridge::isLinkVisited(visitedLinkHash);
222#else
223    if (!m_visitedLinksPopulated) {
224        m_visitedLinksPopulated = true;
225        ASSERT(!m_pages.isEmpty());
226        (*m_pages.begin())->chrome()->client()->populateVisitedLinks();
227    }
228    return m_visitedLinkHashes.contains(visitedLinkHash);
229#endif
230}
231
232void PageGroup::addVisitedLinkHash(LinkHash hash)
233{
234    if (shouldTrackVisitedLinks)
235        addVisitedLink(hash);
236}
237
238inline void PageGroup::addVisitedLink(LinkHash hash)
239{
240    ASSERT(shouldTrackVisitedLinks);
241#if !PLATFORM(CHROMIUM)
242    if (!m_visitedLinkHashes.add(hash).second)
243        return;
244#endif
245    Page::visitedStateChanged(this, hash);
246}
247
248void PageGroup::addVisitedLink(const KURL& url)
249{
250    if (!shouldTrackVisitedLinks)
251        return;
252    ASSERT(!url.isEmpty());
253    addVisitedLink(visitedLinkHash(url.string().characters(), url.string().length()));
254}
255
256void PageGroup::addVisitedLink(const UChar* characters, size_t length)
257{
258    if (!shouldTrackVisitedLinks)
259        return;
260    addVisitedLink(visitedLinkHash(characters, length));
261}
262
263void PageGroup::removeVisitedLinks()
264{
265    m_visitedLinksPopulated = false;
266    if (m_visitedLinkHashes.isEmpty())
267        return;
268    m_visitedLinkHashes.clear();
269    Page::allVisitedStateChanged(this);
270}
271
272void PageGroup::removeAllVisitedLinks()
273{
274    Page::removeAllVisitedLinks();
275}
276
277void PageGroup::setShouldTrackVisitedLinks(bool shouldTrack)
278{
279    if (shouldTrackVisitedLinks == shouldTrack)
280        return;
281    shouldTrackVisitedLinks = shouldTrack;
282    if (!shouldTrackVisitedLinks)
283        removeAllVisitedLinks();
284}
285
286#if ENABLE(DOM_STORAGE)
287StorageNamespace* PageGroup::localStorage()
288{
289    if (!m_localStorage) {
290        // Need a page in this page group to query the settings for the local storage database path.
291        // Having these parameters attached to the page settings is unfortunate since these settings are
292        // not per-page (and, in fact, we simply grab the settings from some page at random), but
293        // at this point we're stuck with it.
294        Page* page = *m_pages.begin();
295        const String& path = page->settings()->localStorageDatabasePath();
296        unsigned quota = m_groupSettings->localStorageQuotaBytes();
297        m_localStorage = StorageNamespace::localStorageNamespace(path, quota);
298    }
299
300    return m_localStorage.get();
301}
302#endif
303
304#if ENABLE(INDEXED_DATABASE)
305IDBFactoryBackendInterface* PageGroup::idbFactory()
306{
307    // Do not add page setting based access control here since this object is shared by all pages in
308    // the group and having per-page controls is misleading.
309    if (!m_factoryBackend)
310        m_factoryBackend = IDBFactoryBackendInterface::create();
311    return m_factoryBackend.get();
312}
313#endif
314
315void PageGroup::addUserScriptToWorld(DOMWrapperWorld* world, const String& source, const KURL& url,
316                                     PassOwnPtr<Vector<String> > whitelist, PassOwnPtr<Vector<String> > blacklist,
317                                     UserScriptInjectionTime injectionTime, UserContentInjectedFrames injectedFrames)
318{
319    ASSERT_ARG(world, world);
320
321    OwnPtr<UserScript> userScript(new UserScript(source, url, whitelist, blacklist, injectionTime, injectedFrames));
322    if (!m_userScripts)
323        m_userScripts.set(new UserScriptMap);
324    UserScriptVector*& scriptsInWorld = m_userScripts->add(world, 0).first->second;
325    if (!scriptsInWorld)
326        scriptsInWorld = new UserScriptVector;
327    scriptsInWorld->append(userScript.release());
328}
329
330void PageGroup::addUserStyleSheetToWorld(DOMWrapperWorld* world, const String& source, const KURL& url,
331                                         PassOwnPtr<Vector<String> > whitelist, PassOwnPtr<Vector<String> > blacklist,
332                                         UserContentInjectedFrames injectedFrames,
333                                         UserStyleLevel level,
334                                         UserStyleInjectionTime injectionTime)
335{
336    ASSERT_ARG(world, world);
337
338    OwnPtr<UserStyleSheet> userStyleSheet(new UserStyleSheet(source, url, whitelist, blacklist, injectedFrames, level));
339    if (!m_userStyleSheets)
340        m_userStyleSheets.set(new UserStyleSheetMap);
341    UserStyleSheetVector*& styleSheetsInWorld = m_userStyleSheets->add(world, 0).first->second;
342    if (!styleSheetsInWorld)
343        styleSheetsInWorld = new UserStyleSheetVector;
344    styleSheetsInWorld->append(userStyleSheet.release());
345
346    if (injectionTime == InjectInExistingDocuments)
347        resetUserStyleCacheInAllFrames();
348}
349
350void PageGroup::removeUserScriptFromWorld(DOMWrapperWorld* world, const KURL& url)
351{
352    ASSERT_ARG(world, world);
353
354    if (!m_userScripts)
355        return;
356
357    UserScriptMap::iterator it = m_userScripts->find(world);
358    if (it == m_userScripts->end())
359        return;
360
361    UserScriptVector* scripts = it->second;
362    for (int i = scripts->size() - 1; i >= 0; --i) {
363        if (scripts->at(i)->url() == url)
364            scripts->remove(i);
365    }
366
367    if (!scripts->isEmpty())
368        return;
369
370    delete it->second;
371    m_userScripts->remove(it);
372}
373
374void PageGroup::removeUserStyleSheetFromWorld(DOMWrapperWorld* world, const KURL& url)
375{
376    ASSERT_ARG(world, world);
377
378    if (!m_userStyleSheets)
379        return;
380
381    UserStyleSheetMap::iterator it = m_userStyleSheets->find(world);
382    bool sheetsChanged = false;
383    if (it == m_userStyleSheets->end())
384        return;
385
386    UserStyleSheetVector* stylesheets = it->second;
387    for (int i = stylesheets->size() - 1; i >= 0; --i) {
388        if (stylesheets->at(i)->url() == url) {
389            stylesheets->remove(i);
390            sheetsChanged = true;
391        }
392    }
393
394    if (!sheetsChanged)
395        return;
396
397    if (!stylesheets->isEmpty()) {
398        delete it->second;
399        m_userStyleSheets->remove(it);
400    }
401
402    resetUserStyleCacheInAllFrames();
403}
404
405void PageGroup::removeUserScriptsFromWorld(DOMWrapperWorld* world)
406{
407    ASSERT_ARG(world, world);
408
409    if (!m_userScripts)
410        return;
411
412    UserScriptMap::iterator it = m_userScripts->find(world);
413    if (it == m_userScripts->end())
414        return;
415
416    delete it->second;
417    m_userScripts->remove(it);
418}
419
420void PageGroup::removeUserStyleSheetsFromWorld(DOMWrapperWorld* world)
421{
422    ASSERT_ARG(world, world);
423
424    if (!m_userStyleSheets)
425        return;
426
427    UserStyleSheetMap::iterator it = m_userStyleSheets->find(world);
428    if (it == m_userStyleSheets->end())
429        return;
430
431    delete it->second;
432    m_userStyleSheets->remove(it);
433
434    resetUserStyleCacheInAllFrames();
435}
436
437void PageGroup::removeAllUserContent()
438{
439    if (m_userScripts) {
440        deleteAllValues(*m_userScripts);
441        m_userScripts.clear();
442    }
443
444    if (m_userStyleSheets) {
445        deleteAllValues(*m_userStyleSheets);
446        m_userStyleSheets.clear();
447        resetUserStyleCacheInAllFrames();
448    }
449}
450
451void PageGroup::resetUserStyleCacheInAllFrames()
452{
453    // Clear our cached sheets and have them just reparse.
454    HashSet<Page*>::const_iterator end = m_pages.end();
455    for (HashSet<Page*>::const_iterator it = m_pages.begin(); it != end; ++it) {
456        for (Frame* frame = (*it)->mainFrame(); frame; frame = frame->tree()->traverseNext())
457            frame->document()->updatePageGroupUserSheets();
458    }
459}
460
461} // namespace WebCore
462