netsurf/desktop/browser_history.c
2019-08-01 19:51:19 +01:00

696 lines
17 KiB
C

/*
* Copyright 2006 James Bursa <bursa@users.sourceforge.net>
* Copyright 2005 Richard Wilson <info@tinct.net>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* NetSurf is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* NetSurf is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* \file
* Browser history tree implementation.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "utils/log.h"
#include "utils/utils.h"
#include "netsurf/layout.h"
#include "netsurf/content.h"
#include "netsurf/window.h"
#include "netsurf/browser_window.h"
#include "content/hlcache.h"
#include "content/urldb.h"
#include "netsurf/bitmap.h"
#include "desktop/gui_internal.h"
#include "desktop/browser_private.h"
#include "desktop/browser_history.h"
#define WIDTH 100
#define HEIGHT 86
#define RIGHT_MARGIN 50
#define BOTTOM_MARGIN 30
/**
* Clone a history entry
*
* \param history opaque history structure, as returned by history_create()
* \param entry entry to clone
* \return A cloned history entry or NULL on error
*/
static struct history_entry *
browser_window_history__clone_entry(struct history *history,
struct history_entry *entry)
{
struct history_entry *child;
struct history_entry *new_child;
struct history_entry *prev = NULL;
struct history_entry *new_entry;
assert(entry);
assert(entry->page.url);
assert(entry->page.title);
/* clone the entry */
new_entry = calloc(1, sizeof *entry);
if (!new_entry) {
return NULL;
}
/* copy page information */
new_entry->page.title = strdup(entry->page.title);
if (new_entry->page.title == NULL) {
free(new_entry);
return NULL;
}
new_entry->page.url = nsurl_ref(entry->page.url);
if (new_entry->page.url == NULL) {
free(new_entry->page.title);
free(new_entry);
return NULL;
}
if (entry->page.frag_id == NULL) {
new_entry->page.frag_id = NULL;
} else {
new_entry->page.frag_id = lwc_string_ref(entry->page.frag_id);
if (new_entry->page.frag_id == NULL) {
nsurl_unref(new_entry->page.url);
free(new_entry);
return NULL;
}
}
if (entry->page.bitmap == NULL) {
new_entry->page.bitmap = NULL;
} else {
/* create a new bitmap and copy original into it */
unsigned char *bmsrc_data;
unsigned char *bmdst_data;
size_t bmsize;
new_entry->page.bitmap = guit->bitmap->create(WIDTH, HEIGHT,
BITMAP_NEW | BITMAP_OPAQUE);
if (new_entry->page.bitmap != NULL) {
bmsrc_data = guit->bitmap->get_buffer(entry->page.bitmap);
bmdst_data = guit->bitmap->get_buffer(new_entry->page.bitmap);
bmsize = guit->bitmap->get_rowstride(new_entry->page.bitmap) *
guit->bitmap->get_height(new_entry->page.bitmap);
memcpy(bmdst_data, bmsrc_data, bmsize);
}
}
/* copy tree values */
new_entry->back = entry->back;
new_entry->next = entry->next;
new_entry->forward = entry->forward;
new_entry->forward_pref = entry->forward_pref;
new_entry->forward_last = entry->forward_last;
/* recurse for all children */
for (child = new_entry->forward; child != NULL; child = child->next) {
new_child = browser_window_history__clone_entry(history, child);
if (new_child == NULL) {
nsurl_unref(new_entry->page.url);
if (new_entry->page.frag_id) {
lwc_string_unref(new_entry->page.frag_id);
}
free(new_entry->page.title);
if (entry->page.bitmap != NULL) {
guit->bitmap->destroy(entry->page.bitmap);
}
free(new_entry);
return NULL;
}
new_child->back = new_entry;
if (prev)
prev->next = new_child;
if (new_entry->forward == child)
new_entry->forward = new_child;
if (new_entry->forward_pref == child)
new_entry->forward_pref = new_child;
if (new_entry->forward_last == child)
new_entry->forward_last = new_child;
prev = new_child;
}
/* update references */
if (history->current == entry) {
history->current = new_entry;
}
return new_entry;
}
/**
* Free an entry in the tree recursively.
*/
static void browser_window_history__free_entry(struct history_entry *entry)
{
if (entry != NULL) {
browser_window_history__free_entry(entry->forward);
browser_window_history__free_entry(entry->next);
nsurl_unref(entry->page.url);
if (entry->page.frag_id) {
lwc_string_unref(entry->page.frag_id);
}
free(entry->page.title);
if (entry->page.bitmap != NULL) {
guit->bitmap->destroy(entry->page.bitmap);
}
free(entry);
}
}
/**
* Recursively position a subtree.
*
* \param history history being laid out
* \param entry subtree to position
* \param x x position for entry
* \param y smallest available y
* \return greatest y used by subtree
*/
static int browser_window_history__layout_subtree(struct history *history,
struct history_entry *entry, int x, int y)
{
struct history_entry *child;
int y1 = y;
if (history->width < x + WIDTH)
history->width = x + WIDTH;
if (!entry->forward) {
entry->x = x;
entry->y = y;
return y + HEIGHT;
}
/* layout child subtrees below each other */
for (child = entry->forward; child; child = child->next) {
y1 = browser_window_history__layout_subtree(history, child,
x + WIDTH + RIGHT_MARGIN, y1);
if (child->next)
y1 += BOTTOM_MARGIN;
}
/* place ourselves in the middle */
entry->x = x;
entry->y = (y + y1) / 2 - HEIGHT / 2;
return y1;
}
/**
* Compute node positions.
*
* \param history history to layout
*
* Each node's x and y are filled in.
*/
static void browser_window_history__layout(struct history *history)
{
if (!history)
return;
history->width = 0;
if (history->start)
history->height = browser_window_history__layout_subtree(
history, history->start,
RIGHT_MARGIN / 2, BOTTOM_MARGIN / 2);
else
history->height = 0;
history->width += RIGHT_MARGIN / 2;
history->height += BOTTOM_MARGIN / 2;
}
/**
* Enumerate subentries in history
* See also history_enumerate()
*
* \param bw The browser window to enumerate history of
* \param entry entry to start enumeration at
* \param cb callback function
* \param ud context pointer passed to cb
* \return true to continue enumeration, false to cancel
*/
static bool browser_window_history__enumerate_entry(
const struct browser_window *bw,
const struct history_entry *entry,
browser_window_history_enumerate_cb cb,
void *ud)
{
const struct history_entry *child;
if (!cb(bw, entry->x, entry->y,
entry->x + WIDTH, entry->y + HEIGHT,
entry, ud))
return false;
for (child = entry->forward; child; child = child->next) {
if (!browser_window_history__enumerate_entry(bw, child,
cb, ud))
return false;
}
return true;
}
/* -------------------------------------------------------------------------- */
/* exported interface documented in desktop/browser_history.h */
nserror browser_window_history_create(struct browser_window *bw)
{
struct history *history;
bw->history = NULL;
history = calloc(1, sizeof *history);
if (history == NULL) {
return NSERROR_NOMEM;
}
history->width = RIGHT_MARGIN / 2;
history->height = BOTTOM_MARGIN / 2;
bw->history = history;
return NSERROR_OK;
}
/* exported interface documented in desktop/browser_history.h */
nserror browser_window_history_clone(const struct browser_window *existing,
struct browser_window *clone)
{
struct history *new_history;
clone->history = NULL;
if (existing == NULL || existing->history == NULL ||
existing->history->start == NULL)
/* Nothing to clone, create new history for clone window */
return browser_window_history_create(clone);
/* Make cloned history */
new_history = malloc(sizeof *new_history);
if (!new_history)
return NSERROR_NOMEM;
clone->history = new_history;
memcpy(new_history, existing->history, sizeof *new_history);
new_history->start = browser_window_history__clone_entry(new_history,
new_history->start);
if (!new_history->start) {
NSLOG(netsurf, INFO, "Insufficient memory to clone history");
browser_window_history_destroy(clone);
clone->history = NULL;
return NSERROR_NOMEM;
}
return NSERROR_OK;
}
/* exported interface documented in desktop/browser_history.h */
nserror
browser_window_history_add(struct browser_window *bw,
struct hlcache_handle *content,
lwc_string *frag_id)
{
struct history *history;
struct history_entry *entry;
char *title;
nserror ret;
assert(bw);
assert(bw->history);
assert(content);
history = bw->history;
entry = malloc(sizeof *entry);
if (entry == NULL) {
return NSERROR_NOMEM;
}
/* page information */
title = strdup(content_get_title(content));
if (title == NULL) {
free(entry);
return NSERROR_NOMEM;
}
entry->page.url = nsurl_ref(hlcache_handle_get_url(content));
entry->page.frag_id = frag_id ? lwc_string_ref(frag_id) : NULL;
entry->page.title = title;
entry->page.scroll_x = 0.0f;
entry->page.scroll_y = 0.0f;
/* create thumbnail for localhistory view */
NSLOG(netsurf, DEBUG,
"Creating thumbnail for %s", nsurl_access(entry->page.url));
entry->page.bitmap = guit->bitmap->create(WIDTH, HEIGHT,
BITMAP_NEW | BITMAP_CLEAR_MEMORY | BITMAP_OPAQUE);
if (entry->page.bitmap != NULL) {
ret = guit->bitmap->render(entry->page.bitmap, content);
if (ret != NSERROR_OK) {
/* Thumbnail render failed */
NSLOG(netsurf, WARNING, "Thumbnail render failed");
}
}
/* insert into tree */
entry->back = history->current;
entry->next = NULL;
entry->forward = entry->forward_pref = entry->forward_last = NULL;
entry->children = 0;
if (history->current) {
if (history->current->forward_last) {
history->current->forward_last->next = entry;
} else {
history->current->forward = entry;
}
history->current->forward_pref = entry;
history->current->forward_last = entry;
history->current->children++;
} else {
history->start = entry;
}
history->current = entry;
browser_window_history__layout(history);
return NSERROR_OK;
}
/* exported interface documented in desktop/browser_history.h */
nserror browser_window_history_update(struct browser_window *bw,
struct hlcache_handle *content)
{
struct history *history;
char *title;
int sx, sy;
assert(bw != NULL);
history = bw->history;
if (!history ||
!history->current ||
!history->current->page.bitmap) {
return NSERROR_INVALID;
}
assert(history->current->page.url);
assert(history->current->page.title);
title = strdup(content_get_title(content));
if (title == NULL) {
return NSERROR_NOMEM;
}
NSLOG(netsurf, INFO, "Updating history entry for %s", title);
free(history->current->page.title);
history->current->page.title = title;
if (history->current->page.bitmap != NULL) {
guit->bitmap->render(history->current->page.bitmap, content);
}
if (bw->window != NULL &&
guit->window->get_scroll(bw->window, &sx, &sy)) {
int content_height = content_get_height(content);
int content_width = content_get_width(content);
/* clamp width and height values */
if (content_height < 1) {
content_height = 1;
}
if (content_width < 1) {
content_width = 1;
}
/* Successfully got scroll offsets, update the entry */
history->current->page.scroll_x = \
(float)sx / (float)content_width;
history->current->page.scroll_y = \
(float)sy / (float)content_height;
NSLOG(netsurf, INFO, "Updated scroll offsets to %g by %g",
history->current->page.scroll_x,
history->current->page.scroll_y);
}
return NSERROR_OK;
}
/* exported interface documented in desktop/browser_private.h */
nserror
browser_window_history_get_scroll(struct browser_window *bw,
float *sx, float *sy)
{
struct history *history;
assert(bw != NULL);
history = bw->history;
if (!history ||
!history->current ||
!history->current->page.bitmap) {
return NSERROR_INVALID;
}
*sx = history->current->page.scroll_x;
*sy = history->current->page.scroll_y;
return NSERROR_OK;
}
/* exported interface documented in desktop/browser_history.h */
void browser_window_history_destroy(struct browser_window *bw)
{
assert(bw != NULL);
if (bw->history == NULL)
return;
browser_window_history__free_entry(bw->history->start);
free(bw->history);
bw->history = NULL;
}
/* exported interface documented in desktop/browser_history.h */
nserror browser_window_history_back(struct browser_window *bw, bool new_window)
{
if (!bw || !bw->history || !bw->history->current ||
!bw->history->current->back) {
return NSERROR_BAD_PARAMETER;
}
return browser_window_history_go(bw, bw->history->current->back,
new_window);
}
/* exported interface documented in desktop/browser_history.h */
nserror browser_window_history_forward(struct browser_window *bw,
bool new_window)
{
if (!bw || !bw->history || !bw->history->current ||
!bw->history->current->forward_pref) {
return NSERROR_BAD_PARAMETER;
}
return browser_window_history_go(bw, bw->history->current->forward_pref,
new_window);
}
/* exported interface documented in desktop/browser_history.h */
bool browser_window_history_back_available(struct browser_window *bw)
{
return (bw && bw->history && bw->history->current &&
bw->history->current->back);
}
/* exported interface documented in desktop/browser_history.h */
bool browser_window_history_forward_available(struct browser_window *bw)
{
return (bw && bw->history && bw->history->current &&
bw->history->current->forward_pref);
}
/* exported interface documented in desktop/browser_history.h */
nserror
browser_window_history_get_thumbnail(struct browser_window *bw,
struct bitmap **bitmap_out)
{
struct bitmap *bitmap;
if (!bw || !bw->history || !bw->history->current) {
return NSERROR_INVALID;
}
if (bw->history->current->page.bitmap == NULL) {
bitmap = content_get_bitmap(bw->current_content);
} else {
bitmap = bw->history->current->page.bitmap;
}
*bitmap_out = bitmap;
return NSERROR_OK;
}
/* exported interface documented in desktop/browser_history.h */
nserror browser_window_history_go(struct browser_window *bw,
struct history_entry *entry, bool new_window)
{
struct history *history;
nsurl *url;
struct history_entry *current;
nserror error;
assert(bw != NULL);
history = bw->history;
if (entry->page.frag_id) {
error = nsurl_refragment(entry->page.url,
entry->page.frag_id, &url);
if (error != NSERROR_OK) {
return error;
}
} else {
url = nsurl_ref(entry->page.url);
}
if (new_window) {
current = history->current;
history->current = entry;
error = browser_window_create(BW_CREATE_CLONE,
url, NULL, bw, NULL);
history->current = current;
} else {
if (bw->current_content != NULL) {
browser_window_history_update(bw, bw->current_content);
}
history->current = entry;
error = browser_window_navigate(bw, url, NULL,
BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE,
NULL, NULL, NULL);
}
nsurl_unref(url);
return error;
}
/* exported interface documented in desktop/browser_history.h */
void browser_window_history_enumerate_forward(const struct browser_window *bw,
browser_window_history_enumerate_cb cb, void *user_data)
{
struct history_entry *e;
if (bw == NULL || bw->history == NULL || bw->history->current == NULL)
return;
e = bw->history->current->forward_pref;
for (; e != NULL; e = e->forward_pref) {
if (!cb(bw, e->x, e->y, e->x + WIDTH, e->y + HEIGHT,
e, user_data))
break;
}
}
/* exported interface documented in desktop/browser_history.h */
void browser_window_history_enumerate_back(const struct browser_window *bw,
browser_window_history_enumerate_cb cb, void *user_data)
{
struct history_entry *e;
if (bw == NULL || bw->history == NULL || bw->history->current == NULL)
return;
for (e = bw->history->current->back; e != NULL; e = e->back) {
if (!cb(bw, e->x, e->y, e->x + WIDTH, e->y + HEIGHT,
e, user_data))
break;
}
}
/* exported interface documented in desktop/browser_history.h */
void browser_window_history_enumerate(const struct browser_window *bw,
browser_window_history_enumerate_cb cb, void *user_data)
{
if (bw == NULL || bw->history == NULL)
return;
browser_window_history__enumerate_entry(bw,
bw->history->start, cb, user_data);
}
/* exported interface documented in desktop/browser_history.h */
nsurl *browser_window_history_entry_get_url(const struct history_entry *entry)
{
return nsurl_ref(entry->page.url);
}
/* exported interface documented in desktop/browser_history.h */
const char *browser_window_history_entry_get_fragment_id(
const struct history_entry *entry)
{
return (entry->page.frag_id) ? lwc_string_data(entry->page.frag_id) : 0;
}
/* exported interface documented in desktop/browser_history.h */
const char *browser_window_history_entry_get_title(
const struct history_entry *entry)
{
return entry->page.title;
}